diff options
| -rw-r--r-- | data/etc/android.hardware.camera.concurrent.xml | 22 | ||||
| -rw-r--r-- | libs/gui/Surface.cpp | 28 | ||||
| -rw-r--r-- | libs/gui/include/gui/Surface.h | 6 | ||||
| -rw-r--r-- | libs/nativewindow/include/system/window.h | 35 | ||||
| -rw-r--r-- | services/surfaceflinger/RefreshRateOverlay.cpp | 20 | ||||
| -rw-r--r-- | services/surfaceflinger/RefreshRateOverlay.h | 26 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/LayerInfoV2.cpp | 13 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.cpp | 94 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.h | 15 | ||||
| -rw-r--r-- | services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp | 41 |
10 files changed, 235 insertions, 65 deletions
diff --git a/data/etc/android.hardware.camera.concurrent.xml b/data/etc/android.hardware.camera.concurrent.xml new file mode 100644 index 0000000000..2cbb263341 --- /dev/null +++ b/data/etc/android.hardware.camera.concurrent.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- This is the set of features required for a camera2 device that supports concurrent operation + of front and back cameras --> +<permissions> + <feature name="android.hardware.camera.front" /> + <feature name="android.hardware.camera.concurrent" /> +</permissions> diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index f911e70ebf..2bf8ff7581 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -57,7 +57,8 @@ bool isInterceptorRegistrationOp(int op) { return op == NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR || op == NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR || op == NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR || - op == NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR; + op == NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR || + op == NATIVE_WINDOW_SET_QUERY_INTERCEPTOR; } } // namespace @@ -501,6 +502,19 @@ int Surface::performInternal(ANativeWindow* window, int operation, va_list args) int Surface::hook_query(const ANativeWindow* window, int what, int* value) { const Surface* c = getSelf(window); + { + std::shared_lock<std::shared_mutex> lock(c->mInterceptorMutex); + if (c->mQueryInterceptor != nullptr) { + auto interceptor = c->mQueryInterceptor; + auto data = c->mQueryInterceptorData; + return interceptor(window, Surface::queryInternal, data, what, value); + } + } + return c->query(what, value); +} + +int Surface::queryInternal(const ANativeWindow* window, int what, int* value) { + const Surface* c = getSelf(window); return c->query(what, value); } @@ -1177,6 +1191,9 @@ int Surface::perform(int operation, va_list args) case NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR: res = dispatchAddQueueInterceptor(args); break; + case NATIVE_WINDOW_SET_QUERY_INTERCEPTOR: + res = dispatchAddQueryInterceptor(args); + break; case NATIVE_WINDOW_ALLOCATE_BUFFERS: allocateBuffers(); res = NO_ERROR; @@ -1457,6 +1474,15 @@ int Surface::dispatchAddQueueInterceptor(va_list args) { return NO_ERROR; } +int Surface::dispatchAddQueryInterceptor(va_list args) { + ANativeWindow_queryInterceptor interceptor = va_arg(args, ANativeWindow_queryInterceptor); + void* data = va_arg(args, void*); + std::lock_guard<std::shared_mutex> lock(mInterceptorMutex); + mQueryInterceptor = interceptor; + mQueryInterceptorData = data; + return NO_ERROR; +} + int Surface::dispatchGetLastQueuedBuffer(va_list args) { AHardwareBuffer** buffer = va_arg(args, AHardwareBuffer**); int* fence = va_arg(args, int*); diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h index 917c0d4831..49c83da319 100644 --- a/libs/gui/include/gui/Surface.h +++ b/libs/gui/include/gui/Surface.h @@ -209,6 +209,7 @@ private: int* fenceFd); static int performInternal(ANativeWindow* window, int operation, va_list args); static int queueBufferInternal(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd); + static int queryInternal(const ANativeWindow* window, int what, int* value); static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer); @@ -261,6 +262,7 @@ private: int dispatchAddDequeueInterceptor(va_list args); int dispatchAddPerformInterceptor(va_list args); int dispatchAddQueueInterceptor(va_list args); + int dispatchAddQueryInterceptor(va_list args); int dispatchGetLastQueuedBuffer(va_list args); bool transformToDisplayInverse(); @@ -468,7 +470,7 @@ protected: mutable Mutex mMutex; // mInterceptorMutex is the mutex guarding interceptors. - std::shared_mutex mInterceptorMutex; + mutable std::shared_mutex mInterceptorMutex; ANativeWindow_cancelBufferInterceptor mCancelInterceptor = nullptr; void* mCancelInterceptorData = nullptr; @@ -478,6 +480,8 @@ protected: void* mPerformInterceptorData = nullptr; ANativeWindow_queueBufferInterceptor mQueueInterceptor = nullptr; void* mQueueInterceptorData = nullptr; + ANativeWindow_queryInterceptor mQueryInterceptor = nullptr; + void* mQueryInterceptorData = nullptr; // must be used from the lock/unlock thread sp<GraphicBuffer> mLockedBuffer; diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h index 869ca9ebe3..b78fc5dbbc 100644 --- a/libs/nativewindow/include/system/window.h +++ b/libs/nativewindow/include/system/window.h @@ -254,6 +254,7 @@ enum { NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44, /* private */ NATIVE_WINDOW_ALLOCATE_BUFFERS = 45, /* private */ NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46, /* private */ + NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47, /* private */ // clang-format on }; @@ -1062,4 +1063,38 @@ static inline int64_t ANativeWindow_getNextFrameId(ANativeWindow* window) { return value; } +/** + * Prototype of the function that an ANativeWindow implementation would call + * when ANativeWindow_query is called. + */ +typedef int (*ANativeWindow_queryFn)(const ANativeWindow* window, int what, int* value); + +/** + * Prototype of the function that intercepts an invocation of + * ANativeWindow_queryFn, along with a data pointer that's passed by the + * caller who set the interceptor, as well as arguments that would be + * passed to ANativeWindow_queryFn if it were to be called. + */ +typedef int (*ANativeWindow_queryInterceptor)(const ANativeWindow* window, + ANativeWindow_queryFn perform, void* data, + int what, int* value); + +/** + * Registers an interceptor for ANativeWindow_query. Instead of calling + * the underlying query function, instead the provided interceptor is + * called, which may optionally call the underlying query function. An + * optional data pointer is also provided to side-channel additional arguments. + * + * Note that usage of this should only be used for specialized use-cases by + * either the system partition or to Mainline modules. This should never be + * exposed to NDK or LL-NDK. + * + * Returns NO_ERROR on success, -errno if registration failed. + */ +static inline int ANativeWindow_setQueryInterceptor(ANativeWindow* window, + ANativeWindow_queryInterceptor interceptor, + void* data) { + return window->perform(window, NATIVE_WINDOW_SET_QUERY_INTERCEPTOR, interceptor, data); +} + __END_DECLS diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index 436a83b3a2..f602412930 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -200,26 +200,22 @@ void RefreshRateOverlay::primeCache() { } } -void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) { - const auto display = mFlinger.getDefaultDisplayDeviceLocked(); - if (!display) { - return; - } +void RefreshRateOverlay::setViewport(ui::Size viewport) { + Rect frame(viewport.width >> 3, viewport.height >> 5); + frame.offsetBy(viewport.width >> 5, viewport.height >> 4); + mLayer->setFrame(frame); - const int32_t left = display->getWidth() / 32; - const int32_t top = display->getHeight() / 16; - const int32_t right = left + display->getWidth() / 8; - const int32_t buttom = top + display->getHeight() / 32; + mFlinger.mTransactionFlags.fetch_or(eTransactionMask); +} +void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) { auto buffer = mBufferCache[refreshRate.getFps()]; mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {}); - mLayer->setFrame(Rect(left, top, right, buttom)); - mFlinger.mTransactionFlags.fetch_or(eTransactionMask); } -}; // namespace android +} // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues #pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index 6d34df220c..35c80201d7 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -13,19 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #pragma once -#include "SurfaceFlinger.h" +#include <unordered_map> + +#include <math/vec4.h> +#include <ui/Rect.h> +#include <ui/Size.h> +#include <utils/StrongPointer.h> + +#include "Scheduler/RefreshRateConfigs.h" namespace android { +class Client; +class GraphicBuffer; +class IBinder; +class IGraphicBufferProducer; +class Layer; +class SurfaceFlinger; + using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate; class RefreshRateOverlay { public: - RefreshRateOverlay(SurfaceFlinger& flinger); + explicit RefreshRateOverlay(SurfaceFlinger&); - void changeRefreshRate(const RefreshRate& refreshRate); + void setViewport(ui::Size); + void changeRefreshRate(const RefreshRate&); private: class SevenSegmentDrawer { @@ -56,7 +72,7 @@ private: void primeCache(); SurfaceFlinger& mFlinger; - sp<Client> mClient; + const sp<Client> mClient; sp<Layer> mLayer; sp<IBinder> mIBinder; sp<IGraphicBufferProducer> mGbp; @@ -68,4 +84,4 @@ private: const half3 HIGH_FPS_COLOR = half3(0.0f, 1.0f, 0.0f); }; -}; // namespace android +} // namespace android diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp index bf1fb88de7..44b4264f20 100644 --- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp @@ -111,21 +111,28 @@ std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() { // Calculate the refresh rate by finding the average delta between frames nsecs_t totalPresentTimeDeltas = 0; + int numFrames = 0; for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) { // If there are no presentation timestamp provided we can't calculate the refresh rate if (it->presetTime == 0 || (it + 1)->presetTime == 0) { - return std::nullopt; + continue; } totalPresentTimeDeltas += std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod); + numFrames++; + } + if (numFrames == 0) { + return std::nullopt; } - const float averageFrameTime = - static_cast<float>(totalPresentTimeDeltas) / (mFrameTimes.size() - 1); + const float averageFrameTime = static_cast<float>(totalPresentTimeDeltas) / numFrames; // Now once we calculated the refresh rate we need to make sure that all the frames we captured // are evenly distributed and we don't calculate the average across some burst of frames. for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) { + if (it->presetTime == 0 || (it + 1)->presetTime == 0) { + continue; + } const nsecs_t presentTimeDeltas = std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod); if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index ee978c5440..2daecaebca 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -582,14 +582,13 @@ void SurfaceFlinger::bootFinished() LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC))); - static_cast<void>(schedule([this]() NO_THREAD_SAFETY_ANALYSIS { + static_cast<void>(schedule([this] { readPersistentProperties(); mPowerAdvisor.onBootFinished(); mBootStage = BootStage::FINISHED; if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) { - mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this); - mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate()); + enableRefreshRateOverlay(true); } })); } @@ -935,9 +934,8 @@ int SurfaceFlinger::getActiveConfig(const sp<IBinder>& displayToken) { } if (isPrimary) { - std::lock_guard<std::mutex> lock(mActiveConfigLock); - if (mDesiredActiveConfigChanged) { - return mDesiredActiveConfig.configId.value(); + if (const auto config = getDesiredActiveConfig()) { + return config->configId.value(); } } @@ -1064,14 +1062,7 @@ void SurfaceFlinger::performSetActiveConfig() { ATRACE_CALL(); ALOGV("performSetActiveConfig"); // Store the local variable to release the lock. - const auto desiredActiveConfig = [&]() -> std::optional<ActiveConfigInfo> { - std::lock_guard<std::mutex> lock(mActiveConfigLock); - if (mDesiredActiveConfigChanged) { - return mDesiredActiveConfig; - } - return std::nullopt; - }(); - + const auto desiredActiveConfig = getDesiredActiveConfig(); if (!desiredActiveConfig) { // No desired active config pending to be applied return; @@ -1606,7 +1597,7 @@ void SurfaceFlinger::changeRefreshRateLocked(const RefreshRate& refreshRate, } void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hal::HWDisplayId hwcDisplayId, - hal::Connection connection) { + hal::Connection connection) NO_THREAD_SAFETY_ANALYSIS { ALOGV("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId, connection == hal::Connection::CONNECTED ? "connected" : "disconnected"); @@ -2680,9 +2671,14 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken, if (currentState.width != drawingState.width || currentState.height != drawingState.height) { display->setDisplaySize(currentState.width, currentState.height); + if (display->isPrimary()) { mScheduler->onPrimaryDisplayAreaChanged(currentState.width * currentState.height); } + + if (mRefreshRateOverlay) { + mRefreshRateOverlay->setViewport(display->getSize()); + } } } } @@ -5230,15 +5226,15 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r return NO_ERROR; } case 1034: { - n = data.readInt32(); - if (n == 1 && !mRefreshRateOverlay) { - mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this); - auto& current = mRefreshRateConfigs->getCurrentRefreshRate(); - mRefreshRateOverlay->changeRefreshRate(current); - } else if (n == 0) { - mRefreshRateOverlay.reset(); - } else { - reply->writeBool(mRefreshRateOverlay != nullptr); + switch (n = data.readInt32()) { + case 0: + case 1: + enableRefreshRateOverlay(static_cast<bool>(n)); + break; + default: { + Mutex::Autolock lock(mStateLock); + reply->writeBool(mRefreshRateOverlay != nullptr); + } } return NO_ERROR; } @@ -5286,29 +5282,26 @@ void SurfaceFlinger::repaintEverythingForHWC() { void SurfaceFlinger::kernelTimerChanged(bool expired) { static bool updateOverlay = property_get_bool("debug.sf.kernel_idle_timer_update_overlay", true); - if (!updateOverlay || !mRefreshRateOverlay) return; + if (!updateOverlay) return; + if (Mutex::Autolock lock(mStateLock); !mRefreshRateOverlay) return; // Update the overlay on the main thread to avoid race conditions with // mRefreshRateConfigs->getCurrentRefreshRate() - static_cast<void>(schedule([=]() NO_THREAD_SAFETY_ANALYSIS { - if (mRefreshRateOverlay) { + static_cast<void>(schedule([=] { + const auto desiredActiveConfig = getDesiredActiveConfig(); + const auto& current = desiredActiveConfig + ? mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig->configId) + : mRefreshRateConfigs->getCurrentRefreshRate(); + const auto& min = mRefreshRateConfigs->getMinRefreshRate(); + + if (current != min) { const auto kernelTimerEnabled = property_get_bool(KERNEL_IDLE_TIMER_PROP, false); const bool timerExpired = kernelTimerEnabled && expired; - const auto& current = [this]() -> const RefreshRate& { - std::lock_guard<std::mutex> lock(mActiveConfigLock); - if (mDesiredActiveConfigChanged) { - return mRefreshRateConfigs->getRefreshRateFromConfigId( - mDesiredActiveConfig.configId); - } - - return mRefreshRateConfigs->getCurrentRefreshRate(); - }(); - const auto& min = mRefreshRateConfigs->getMinRefreshRate(); - if (current != min) { + if (Mutex::Autolock lock(mStateLock); mRefreshRateOverlay) { mRefreshRateOverlay->changeRefreshRate(timerExpired ? min : current); - mEventQueue->invalidate(); } + mEventQueue->invalidate(); } })); } @@ -6225,6 +6218,29 @@ void SurfaceFlinger::onFrameRateFlexibilityTokenReleased() { })); } +void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { + static_cast<void>(schedule([=] { + std::unique_ptr<RefreshRateOverlay> overlay; + if (enable) { + overlay = std::make_unique<RefreshRateOverlay>(*this); + } + + { + Mutex::Autolock lock(mStateLock); + + // Destroy the layer of the current overlay, if any, outside the lock. + mRefreshRateOverlay.swap(overlay); + if (!mRefreshRateOverlay) return; + + if (const auto display = getDefaultDisplayDeviceLocked()) { + mRefreshRateOverlay->setViewport(display->getSize()); + } + + mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate()); + } + })); +} + } // namespace android #if defined(__gl_h_) diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index f3984ed8d4..715d5f78ea 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -829,13 +829,13 @@ private: const DisplayDeviceState& state, const sp<compositionengine::DisplaySurface>& displaySurface, const sp<IGraphicBufferProducer>& producer); - void processDisplayChangesLocked(); + void processDisplayChangesLocked() REQUIRES(mStateLock); void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState& state); void processDisplayRemoved(const wp<IBinder>& displayToken); void processDisplayChanged(const wp<IBinder>& displayToken, const DisplayDeviceState& currentState, - const DisplayDeviceState& drawingState); - void processDisplayHotplugEventsLocked(); + const DisplayDeviceState& drawingState) REQUIRES(mStateLock); + void processDisplayHotplugEventsLocked() REQUIRES(mStateLock); void dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected); @@ -1216,6 +1216,12 @@ private: * Misc */ + std::optional<ActiveConfigInfo> getDesiredActiveConfig() EXCLUDES(mActiveConfigLock) { + std::lock_guard<std::mutex> lock(mActiveConfigLock); + if (mDesiredActiveConfigChanged) return mDesiredActiveConfig; + return std::nullopt; + } + std::mutex mActiveConfigLock; // This bit is set once we start setting the config. We read from this bit during the // process. If at the end, this bit is different than mDesiredActiveConfig, we restart @@ -1258,7 +1264,8 @@ private: // This should only be accessed on the main thread. nsecs_t mFrameStartTime = 0; - std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay; + void enableRefreshRateOverlay(bool enable); + std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay GUARDED_BY(mStateLock); // Flag used to set override allowed display configs from backdoor bool mDebugDisplayConfigSetByBackdoor = false; diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp index 6fca673f6b..15207c94a3 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp @@ -512,5 +512,46 @@ TEST_F(LayerHistoryTestV2, inactiveLayers) { EXPECT_EQ(1, frequentLayerCount(time)); } +TEST_F(LayerHistoryTestV2, calculateRefreshRate30Hz) { + const auto layer = createLayer(); + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + + nsecs_t time = systemTime(); + const nsecs_t frameTime = 33'333'333; + + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + time += frameTime; + history().record(layer.get(), time, time); + } + ASSERT_EQ(1, history().summarize(time).size()); + EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote); + EXPECT_FLOAT_EQ(30.f, history().summarize(time)[0].desiredRefreshRate); +} + +TEST_F(LayerHistoryTestV2, calculateRefreshRate30HzSkipTimestamp) { + const auto layer = createLayer(); + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + + nsecs_t time = systemTime(); + const nsecs_t frameTime = 33'333'333; + + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + time += frameTime; + const auto timestamp = (i == PRESENT_TIME_HISTORY_SIZE / 2) ? 0 : time; + history().record(layer.get(), timestamp, time); + } + ASSERT_EQ(1, history().summarize(time).size()); + EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote); + EXPECT_FLOAT_EQ(30.f, history().summarize(time)[0].desiredRefreshRate); +} + } // namespace } // namespace android::scheduler |