diff options
140 files changed, 2439 insertions, 408 deletions
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc index fc0801cce8..3e6d2e01f8 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -93,6 +93,10 @@ on late-init chmod 0666 /sys/kernel/tracing/events/binder/binder_unlock/enable chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_set_priority/enable chmod 0666 /sys/kernel/tracing/events/binder/binder_set_priority/enable + chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_command/enable + chmod 0666 /sys/kernel/tracing/events/binder/binder_command/enable + chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_return/enable + chmod 0666 /sys/kernel/tracing/events/binder/binder_return/enable chmod 0666 /sys/kernel/debug/tracing/events/i2c/enable chmod 0666 /sys/kernel/tracing/events/i2c/enable chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp index a447cda492..822ab7fbb7 100644 --- a/cmds/installd/otapreopt.cpp +++ b/cmds/installd/otapreopt.cpp @@ -437,6 +437,9 @@ private: maybe_open_reference_profile(parameters_.pkgName, parameters_.apk_path, parameters_.profile_name, profile_guided, is_public, parameters_.uid, is_secondary_dex); + // `maybe_open_reference_profile` installs a hook that clears the profile on + // destruction. Disable it. + reference_profile.DisableCleanup(); struct stat sbuf; if (reference_profile.fd() == -1 || (fstat(reference_profile.fd(), &sbuf) != -1 && sbuf.st_size == 0)) { diff --git a/include/android/bitmap.h b/include/android/bitmap.h index 35f87f96ae..87a14c021d 100644 --- a/include/android/bitmap.h +++ b/include/android/bitmap.h @@ -196,7 +196,7 @@ enum AndroidBitmapCompressFormat { * * @param userContext Pointer to user-defined data passed to * {@link AndroidBitmap_compress}. - * @param data Compressed data of |size| bytes to write. + * @param data Compressed data of `size` bytes to write. * @param size Length in bytes of data to write. * @return Whether the operation succeeded. */ @@ -205,7 +205,7 @@ typedef bool (*AndroidBitmap_CompressWriteFunc)(void* userContext, size_t size) __INTRODUCED_IN(30); /** - * Compress |pixels| as described by |info|. + * Compress `pixels` as described by `info`. * * Available since API level 30. * diff --git a/include/android/sharedmem.h b/include/android/sharedmem.h index e0a8045d41..645fa8a5e7 100644 --- a/include/android/sharedmem.h +++ b/include/android/sharedmem.h @@ -53,7 +53,7 @@ extern "C" { /** * Create a shared memory region. * - * Create shared memory region and returns an file descriptor. The resulting file descriptor can be + * Create shared memory region and returns a file descriptor. The resulting file descriptor can be * mmap'ed to process memory space with PROT_READ | PROT_WRITE | PROT_EXEC. Access to shared memory * region can be restricted with {@link ASharedMemory_setProt}. * @@ -65,7 +65,7 @@ extern "C" { * cmsg(3) man pages for more information. * * If you intend to share this file descriptor with a child process after - * calling exec(3), note that you will need to use fcntl(2) with FD_SETFD + * calling exec(3), note that you will need to use fcntl(2) with F_SETFD * to clear the FD_CLOEXEC flag for this to work on all versions of Android. * * Available since API level 26. diff --git a/include/android/thermal.h b/include/android/thermal.h index 32580badc0..1f477f8233 100644 --- a/include/android/thermal.h +++ b/include/android/thermal.h @@ -188,13 +188,13 @@ int AThermal_unregisterThermalStatusListener(AThermalManager *manager, * Note that this only attempts to track the headroom of slow-moving sensors, such as * the skin temperature sensor. This means that there is no benefit to calling this function * more frequently than about once per second, and attempted to call significantly - * more frequently may result in the function returning {@code NaN}. + * more frequently may result in the function returning `NaN`. * * In addition, in order to be able to provide an accurate forecast, the system does * not attempt to forecast until it has multiple temperature samples from which to * extrapolate. This should only take a few seconds from the time of the first call, * but during this time, no forecasting will occur, and the current headroom will be - * returned regardless of the value of {@code forecastSeconds}. + * returned regardless of the value of `forecastSeconds`. * * The value returned is a non-negative float that represents how much of the thermal envelope * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is diff --git a/include/ftl/OWNERS b/include/ftl/OWNERS new file mode 100644 index 0000000000..3f6129226a --- /dev/null +++ b/include/ftl/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file diff --git a/include/ftl/enum.h b/include/ftl/enum.h index 075d12bd17..2c86e2e4c9 100644 --- a/include/ftl/enum.h +++ b/include/ftl/enum.h @@ -25,12 +25,12 @@ #include <ftl/string.h> -// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the -// compiler-generated string literal for the signature of this function. The function is defined in -// the global namespace with a short name and inferred return type to reduce bloat in the read-only -// data segment. -template <typename E, E V> -constexpr auto ftl_enum() { +// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as +// std::optional<std::string_view> by parsing the compiler-generated string literal for the +// signature of this function. The function is defined in the global namespace with a short name +// and inferred return type to reduce bloat in the read-only data segment. +template <bool S, typename E, E V> +constexpr auto ftl_enum_builder() { static_assert(std::is_enum_v<E>); using R = std::optional<std::string_view>; @@ -58,7 +58,9 @@ constexpr auto ftl_enum() { // V = android::test::Enum::kValue // view = view.substr(value_begin); - const auto name_begin = view.rfind("::"sv); + const auto pos = S ? view.rfind("::"sv) - 2 : view.npos; + + const auto name_begin = view.rfind("::"sv, pos); if (name_begin == view.npos) return R{}; // Chop off the leading "::". @@ -68,6 +70,18 @@ constexpr auto ftl_enum() { return name.find(')') == view.npos ? R{name} : R{}; } +// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> +template <typename E, E V> +constexpr auto ftl_enum() { + return ftl_enum_builder<false, E, V>(); +} + +// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view> +template <typename E, E V> +constexpr auto ftl_enum_full() { + return ftl_enum_builder<true, E, V>(); +} + namespace android::ftl { // Trait for determining whether a type is specifically a scoped enum or not. By definition, a @@ -191,6 +205,11 @@ struct EnumName { static constexpr auto value = ftl_enum<decltype(V), V>(); }; +template <auto V> +struct EnumNameFull { + static constexpr auto value = ftl_enum_full<decltype(V), V>(); +}; + template <auto I> struct FlagName { using E = decltype(I); @@ -230,6 +249,18 @@ constexpr std::string_view enum_name() { return *kName; } +// Returns a stringified enumerator with class at compile time. +// +// enum class E { A, B, C }; +// static_assert(ftl::enum_name<E::B>() == "E::B"); +// +template <auto V> +constexpr std::string_view enum_name_full() { + constexpr auto kName = ftl_enum_full<decltype(V), V>(); + static_assert(kName, "Unknown enumerator"); + return *kName; +} + // Returns a stringified enumerator, possibly at compile time. // // enum class E { A, B, C, F = 5, ftl_last = F }; @@ -249,6 +280,25 @@ constexpr std::optional<std::string_view> enum_name(E v) { return kRange.values[value - kBegin]; } +// Returns a stringified enumerator with class, possibly at compile time. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// static_assert(ftl::enum_name(E::C).value_or("?") == "E::C"); +// static_assert(ftl::enum_name(E{3}).value_or("?") == "?"); +// +template <typename E> +constexpr std::optional<std::string_view> enum_name_full(E v) { + const auto value = to_underlying(v); + + constexpr auto kBegin = to_underlying(enum_begin_v<E>); + constexpr auto kLast = to_underlying(enum_last_v<E>); + if (value < kBegin || value > kLast) return {}; + + constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{}; + return kRange.values[value - kBegin]; +} + // Returns a stringified flag enumerator, possibly at compile time. // // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; @@ -282,6 +332,21 @@ inline std::string enum_string(E v) { return to_string(to_underlying(v)); } +// Returns a stringified enumerator with class, or its integral value if not named. +// +// enum class E { A, B, C, F = 5, ftl_last = F }; +// +// assert(ftl::enum_string(E::C) == "E::C"); +// assert(ftl::enum_string(E{3}) == "3"); +// +template <typename E> +inline std::string enum_string_full(E v) { + if (const auto name = enum_name_full(v)) { + return std::string(*name); + } + return to_string(to_underlying(v)); +} + // Returns a stringified flag enumerator, or its integral value if not named. // // enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp index 589df9aceb..ef0ae4f654 100644 --- a/libs/binder/BpBinder.cpp +++ b/libs/binder/BpBinder.cpp @@ -54,6 +54,11 @@ uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500; // Another arbitrary value a binder count needs to drop below before another callback will be called uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000; +std::atomic<uint32_t> BpBinder::sBinderProxyCount(0); +std::atomic<uint32_t> BpBinder::sBinderProxyCountWarned(0); + +static constexpr uint32_t kBinderProxyCountWarnInterval = 5000; + // Log any transactions for which the data exceeds this size #define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024) @@ -193,6 +198,18 @@ sp<BpBinder> BpBinder::create(int32_t handle) { } sTrackingMap[trackedUid]++; } + uint32_t numProxies = sBinderProxyCount.fetch_add(1, std::memory_order_relaxed); + uint32_t numLastWarned = sBinderProxyCountWarned.load(std::memory_order_relaxed); + uint32_t numNextWarn = numLastWarned + kBinderProxyCountWarnInterval; + if (numProxies >= numNextWarn) { + // Multiple threads can get here, make sure only one of them gets to + // update the warn counter. + if (sBinderProxyCountWarned.compare_exchange_strong(numLastWarned, + numNextWarn, + std::memory_order_relaxed)) { + ALOGW("Unexpectedly many live BinderProxies: %d\n", numProxies); + } + } return sp<BpBinder>::make(BinderHandle{handle}, trackedUid); } @@ -604,6 +621,7 @@ BpBinder::~BpBinder() { } } } + --sBinderProxyCount; if (ipc) { ipc->expungeHandle(binderHandle(), this); @@ -688,6 +706,11 @@ uint32_t BpBinder::getBinderProxyCount(uint32_t uid) return 0; } +uint32_t BpBinder::getBinderProxyCount() +{ + return sBinderProxyCount.load(); +} + void BpBinder::getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts) { AutoMutex _l(sTrackingLock); diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h index 5496d61b70..a5c6094362 100644 --- a/libs/binder/include/binder/BpBinder.h +++ b/libs/binder/include/binder/BpBinder.h @@ -87,6 +87,7 @@ public: static void setCountByUidEnabled(bool enable); static void setLimitCallback(binder_proxy_limit_callback cb); static void setBinderProxyCountWatermarks(int high, int low); + static uint32_t getBinderProxyCount(); std::optional<int32_t> getDebugBinderHandle() const; @@ -208,6 +209,8 @@ private: static uint32_t sBinderProxyCountLowWatermark; static bool sBinderProxyThrottleCreate; static std::unordered_map<int32_t,uint32_t> sLastLimitCallbackMap; + static std::atomic<uint32_t> sBinderProxyCount; + static std::atomic<uint32_t> sBinderProxyCountWarned; }; } // namespace android diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h index 76c7aacb7c..4786c89c89 100644 --- a/libs/binder/ndk/include_ndk/android/binder_status.h +++ b/libs/binder/ndk/include_ndk/android/binder_status.h @@ -25,6 +25,7 @@ #pragma once +#include <assert.h> #include <errno.h> #include <stdbool.h> #include <stdint.h> diff --git a/libs/binder/ndk/tests/Android.bp b/libs/binder/ndk/tests/Android.bp index 8ee396e256..8fb755cdac 100644 --- a/libs/binder/ndk/tests/Android.bp +++ b/libs/binder/ndk/tests/Android.bp @@ -80,6 +80,28 @@ cc_test { require_root: true, } +cc_test_host { + name: "libbinder_ndk_unit_test_host", + defaults: ["test_libbinder_ndk_defaults"], + srcs: ["libbinder_ndk_unit_test_host.cpp"], + test_suites: [ + "general-tests", + ], + test_options: { + unit_test: true, + }, + static_libs: [ + "libbase", + "libbinder_ndk", + "libbinder", + "libcutils", + "libfakeservicemanager", + "libgmock", + "liblog", + "libutils", + ], +} + cc_test { name: "binderVendorDoubleLoadTest", vendor: true, diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp index 25b8e975b3..15708ca738 100644 --- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp +++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp @@ -39,7 +39,6 @@ #include <condition_variable> #include <iostream> #include <mutex> -#include <optional> #include <thread> #include "android/binder_ibinder.h" @@ -433,21 +432,6 @@ TEST(NdkBinder, GetLazyService) { EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get())); } -TEST(NdkBinder, IsUpdatable) { - bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.light.ILights/default"); - EXPECT_EQ(isUpdatable, false); -} - -TEST(NdkBinder, GetUpdatableViaApex) { - std::optional<std::string> updatableViaApex; - AServiceManager_getUpdatableApexName( - "android.hardware.light.ILights/default", &updatableViaApex, - [](const char* apexName, void* context) { - *static_cast<std::optional<std::string>*>(context) = apexName; - }); - EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex; -} - // This is too slow TEST(NdkBinder, CheckLazyServiceShutDown) { ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService)); diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp new file mode 100644 index 0000000000..0a3021d0b0 --- /dev/null +++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp @@ -0,0 +1,104 @@ +/* + * 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 <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <utils/String16.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> + +#include <optional> + +#include "fakeservicemanager/FakeServiceManager.h" + +using android::FakeServiceManager; +using android::setDefaultServiceManager; +using android::sp; +using android::String16; +using android::String8; +using testing::_; +using testing::Eq; +using testing::Mock; +using testing::NiceMock; +using testing::Optional; +using testing::Return; + +struct MockServiceManager : FakeServiceManager { + MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&)); +}; + +struct AServiceManager : testing::Test { + static sp<MockServiceManager> mockSM; + + static void InitMock() { + mockSM = new NiceMock<MockServiceManager>; + setDefaultServiceManager(mockSM); + } + + void TearDown() override { Mock::VerifyAndClear(mockSM.get()); } + + void ExpectUpdatableViaApexReturns(std::optional<String16> apexName) { + EXPECT_CALL(*mockSM, updatableViaApex(_)).WillRepeatedly(Return(apexName)); + } +}; + +sp<MockServiceManager> AServiceManager::mockSM; + +TEST_F(AServiceManager, isUpdatableViaApex) { + auto apexFoo = String16("com.android.hardware.foo"); + ExpectUpdatableViaApexReturns(apexFoo); + + bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default"); + EXPECT_EQ(isUpdatable, true); +} + +TEST_F(AServiceManager, isUpdatableViaApex_Not) { + ExpectUpdatableViaApexReturns(std::nullopt); + + bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default"); + EXPECT_EQ(isUpdatable, false); +} + +void getUpdatableApexNameCallback(const char* apexName, void* context) { + *(static_cast<std::optional<std::string>*>(context)) = apexName; +} + +TEST_F(AServiceManager, getUpdatableApexName) { + auto apexFoo = String16("com.android.hardware.foo"); + ExpectUpdatableViaApexReturns(apexFoo); + + std::optional<std::string> result; + AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result, + getUpdatableApexNameCallback); + EXPECT_THAT(result, Optional(std::string(String8(apexFoo)))); +} + +TEST_F(AServiceManager, getUpdatableApexName_Null) { + ExpectUpdatableViaApexReturns(std::nullopt); + + std::optional<std::string> result; + AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result, + getUpdatableApexNameCallback); + EXPECT_THAT(result, Eq(std::nullopt)); +} + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + AServiceManager::InitMock(); + return RUN_ALL_TESTS(); +} diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index e021af0264..0396869167 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -1441,6 +1441,36 @@ TEST_F(BinderLibTest, HangingServices) { EXPECT_GE(epochMsAfter, epochMsBefore + delay); } +TEST_F(BinderLibTest, BinderProxyCount) { + Parcel data, reply; + sp<IBinder> server = addServer(); + ASSERT_NE(server, nullptr); + + uint32_t initialCount = BpBinder::getBinderProxyCount(); + size_t iterations = 100; + { + uint32_t count = initialCount; + std::vector<sp<IBinder> > proxies; + sp<IBinder> proxy; + // Create binder proxies and verify the count. + for (size_t i = 0; i < iterations; i++) { + ASSERT_THAT(server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply), + StatusEq(NO_ERROR)); + proxies.push_back(reply.readStrongBinder()); + EXPECT_EQ(BpBinder::getBinderProxyCount(), ++count); + } + // Remove every other one and verify the count. + auto it = proxies.begin(); + for (size_t i = 0; it != proxies.end(); i++) { + if (i % 2 == 0) { + it = proxies.erase(it); + EXPECT_EQ(BpBinder::getBinderProxyCount(), --count); + } + } + } + EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount); +} + class BinderLibRpcTestBase : public BinderLibTest { public: void SetUp() override { diff --git a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp index 09cb2162f7..b80ac53ba2 100644 --- a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp +++ b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp @@ -16,6 +16,7 @@ #include <commonFuzzHelpers.h> #include <fuzzer/FuzzedDataProvider.h> +#include <functional> #include <string> #include <vector> #include "BufferedTextOutput.h" diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp new file mode 100644 index 0000000000..0ecf94c48d --- /dev/null +++ b/libs/bufferstreams/examples/app/Android.bp @@ -0,0 +1,27 @@ +// 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. + +android_app { + name: "BufferStreamsDemoApp", + srcs: ["java/**/*.java"], + sdk_version: "current", + + jni_uses_platform_apis: true, + jni_libs: ["libbufferstreamdemoapp"], + use_embedded_native_libs: true, + + static_libs: [ + "androidx.appcompat_appcompat", + ], +} diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml new file mode 100644 index 0000000000..872193c4e6 --- /dev/null +++ b/libs/bufferstreams/examples/app/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.graphics.bufferstreamsdemoapp" + xmlns:tools="http://schemas.android.com/tools"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.AppCompat.Light" + tools:targetApi="34"> + <activity + android:name=".MainActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java new file mode 100644 index 0000000000..67b95a5349 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java @@ -0,0 +1,39 @@ +// 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 com.android.graphics.bufferstreamsdemoapp; + +import android.os.Bundle; +import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + // Used to load the 'bufferstreamsdemoapp' library on application startup. + static { System.loadLibrary("bufferstreamdemoapp"); } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + RunBufferQueue(); + System.out.println("stringFromJNI: " + stringFromJNI()); + } + + /** + * A native method that is implemented by the 'bufferstreamsdemoapp' native + * library, which is packaged with this application. + */ + public native String stringFromJNI(); + public native void RunBufferQueue(); +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp new file mode 100644 index 0000000000..67910a1c4d --- /dev/null +++ b/libs/bufferstreams/examples/app/jni/Android.bp @@ -0,0 +1,28 @@ +// 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. + +cc_library_shared { + name: "libbufferstreamdemoapp", + cflags: [ + "-Werror", + "-Wno-error=unused-parameter", + ], + shared_libs: [ + "libgui", + "libbase", + "libutils", + ], + header_libs: ["jni_headers"], + srcs: ["*.cpp"], +} diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp new file mode 100644 index 0000000000..34e0eb451c --- /dev/null +++ b/libs/bufferstreams/examples/app/jni/main.cpp @@ -0,0 +1,37 @@ +// 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 <jni.h> + +#include <gui/BufferQueue.h> + +extern "C" +{ + JNIEXPORT jstring JNICALL + Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_stringFromJNI( + JNIEnv *env, + jobject /* this */) { + const char* hello = "Hello from C++"; + return env->NewStringUTF(hello); + } + + JNIEXPORT void JNICALL + Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_RunBufferQueue( + JNIEnv *env, + jobject /* this */) { + android::sp<android::IGraphicBufferProducer> producer; + android::sp<android::IGraphicBufferConsumer> consumer; + android::BufferQueue::createBufferQueue(&producer, &consumer); + } +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..07d5da9cbf --- /dev/null +++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> +</vector> diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..2b068d1146 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/layout/activity_main.xml b/libs/bufferstreams/examples/app/res/layout/activity_main.xml new file mode 100644 index 0000000000..79fb331f09 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/layout/activity_main.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity"> + + <TextView + android:id="@+id/sample_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello World!" + tools:layout_editor_absoluteX="100dp" + tools:layout_editor_absoluteY="100dp" /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000000..6f3b755bf5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000000..6f3b755bf5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..c209e78ecd --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..b2dfe3d1ba --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..4f0f1d64e5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..62b611da08 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..948a3070fe --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..1b9a6956b3 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..28d4b77f9f --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..9287f50836 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..aa7d6427e6 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..9126ae37cb --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml new file mode 100644 index 0000000000..f8c6127d32 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="purple_200">#FFBB86FC</color> + <color name="purple_500">#FF6200EE</color> + <color name="purple_700">#FF3700B3</color> + <color name="teal_200">#FF03DAC5</color> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> +</resources>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml new file mode 100644 index 0000000000..e652102cb3 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Buffer Demos</string> +</resources>
\ No newline at end of file diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp index ff951487bc..7fcb222085 100644 --- a/libs/bufferstreams/rust/Android.bp +++ b/libs/bufferstreams/rust/Android.bp @@ -12,13 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -rust_library { - name: "libbufferstreams", - crate_name: "bufferstreams", +rust_defaults { + name: "libbufferstreams_defaults", srcs: ["src/lib.rs"], - edition: "2021", - rlibs: [ + rustlibs: [ + "libanyhow", "libnativewindow_rs", ], + edition: "2021", +} + +rust_library { + name: "libbufferstreams", + crate_name: "bufferstreams", + defaults: ["libbufferstreams_defaults"], min_sdk_version: "30", } + +rust_test { + name: "libbufferstreams-internal_test", + crate_name: "bufferstreams", + defaults: ["libbufferstreams_defaults"], + test_suites: ["general-tests"], +} diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs index 1d321c833d..87f3104915 100644 --- a/libs/bufferstreams/rust/src/lib.rs +++ b/libs/bufferstreams/rust/src/lib.rs @@ -14,8 +14,14 @@ //! libbufferstreams: Reactive Streams for Graphics Buffers +pub mod publishers; +mod stream_config; +pub mod subscribers; +pub mod subscriptions; + +pub use stream_config::*; + use nativewindow::*; -use std::sync::{Arc, Weak}; use std::time::Instant; /// This function will print Hello World. @@ -31,28 +37,30 @@ pub extern "C" fn hello() -> bool { /// /// BufferPublishers are required to adhere to the following, based on the /// reactive streams specification: -/// * The total number of on_next´s signalled by a Publisher to a Subscriber +/// * The total number of on_next´s signalled by a Publisher to a Subscriber /// MUST be less than or equal to the total number of elements requested by that /// Subscriber´s Subscription at all times. -/// * A Publisher MAY signal fewer on_next than requested and terminate the +/// * A Publisher MAY signal fewer on_next than requested and terminate the /// Subscription by calling on_complete or on_error. -/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber +/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber /// MUST be signaled serially. -/// * If a Publisher fails it MUST signal an on_error. -/// * If a Publisher terminates successfully (finite stream) it MUST signal an +/// * If a Publisher fails it MUST signal an on_error. +/// * If a Publisher terminates successfully (finite stream) it MUST signal an /// on_complete. -/// * If a Publisher signals either on_error or on_complete on a Subscriber, +/// * If a Publisher signals either on_error or on_complete on a Subscriber, /// that Subscriber’s Subscription MUST be considered cancelled. -/// * Once a terminal state has been signaled (on_error, on_complete) it is +/// * Once a terminal state has been signaled (on_error, on_complete) it is /// REQUIRED that no further signals occur. -/// * If a Subscription is cancelled its Subscriber MUST eventually stop being +/// * If a Subscription is cancelled its Subscriber MUST eventually stop being /// signaled. -/// * A Publisher MAY support multiple Subscribers and decides whether each +/// * A Publisher MAY support multiple Subscribers and decides whether each /// Subscription is unicast or multicast. pub trait BufferPublisher { + /// Returns the StreamConfig of buffers that publisher creates. + fn get_publisher_stream_config(&self) -> StreamConfig; /// This function will create the subscription between the publisher and /// the subscriber. - fn subscribe(&self, subscriber: Weak<dyn BufferSubscriber>); + fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static); } /// BufferSubscribers can subscribe to BufferPublishers. They can request Frames @@ -61,35 +69,37 @@ pub trait BufferPublisher { /// /// BufferSubcribers are required to adhere to the following, based on the /// reactive streams specification: -/// * The total number of on_next´s signalled by a Publisher to a Subscriber +/// * The total number of on_next´s signalled by a Publisher to a Subscriber /// MUST be less than or equal to the total number of elements requested by that /// Subscriber´s Subscription at all times. -/// * A Publisher MAY signal fewer on_next than requested and terminate the +/// * A Publisher MAY signal fewer on_next than requested and terminate the /// Subscription by calling on_complete or on_error. -/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber +/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber /// MUST be signaled serially. -/// * If a Publisher fails it MUST signal an on_error. -/// * If a Publisher terminates successfully (finite stream) it MUST signal an +/// * If a Publisher fails it MUST signal an on_error. +/// * If a Publisher terminates successfully (finite stream) it MUST signal an /// on_complete. -/// * If a Publisher signals either on_error or on_complete on a Subscriber, +/// * If a Publisher signals either on_error or on_complete on a Subscriber, /// that Subscriber’s Subscription MUST be considered cancelled. -/// * Once a terminal state has been signaled (on_error, on_complete) it is +/// * Once a terminal state has been signaled (on_error, on_complete) it is /// REQUIRED that no further signals occur. -/// * If a Subscription is cancelled its Subscriber MUST eventually stop being +/// * If a Subscription is cancelled its Subscriber MUST eventually stop being /// signaled. -/// * Publisher.subscribe MAY be called as many times as wanted but MUST be +/// * Publisher.subscribe MAY be called as many times as wanted but MUST be /// with a different Subscriber each time. -/// * A Publisher MAY support multiple Subscribers and decides whether each +/// * A Publisher MAY support multiple Subscribers and decides whether each /// Subscription is unicast or multicast. pub trait BufferSubscriber { + /// The StreamConfig of buffers that this subscriber expects. + fn get_subscriber_stream_config(&self) -> StreamConfig; /// This function will be called at the beginning of the subscription. - fn on_subscribe(&self, subscription: Arc<dyn BufferSubscription>); + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>); /// This function will be called for buffer that comes in. - fn on_next(&self, frame: Frame); + fn on_next(&mut self, frame: Frame); /// This function will be called in case of an error. - fn on_error(&self, error: BufferError); + fn on_error(&mut self, error: BufferError); /// This function will be called on finite streams when done. - fn on_complete(&self); + fn on_complete(&mut self); } /// BufferSubscriptions serve as the bridge between BufferPublishers and @@ -100,50 +110,51 @@ pub trait BufferSubscriber { /// /// BufferSubcriptions are required to adhere to the following, based on the /// reactive streams specification: -/// * Subscription.request and Subscription.cancel MUST only be called inside +/// * Subscription.request and Subscription.cancel MUST only be called inside /// of its Subscriber context. -/// * The Subscription MUST allow the Subscriber to call Subscription.request +/// * The Subscription MUST allow the Subscriber to call Subscription.request /// synchronously from within on_next or on_subscribe. -/// * Subscription.request MUST place an upper bound on possible synchronous +/// * Subscription.request MUST place an upper bound on possible synchronous /// recursion between Publisher and Subscriber. -/// * Subscription.request SHOULD respect the responsivity of its caller by +/// * Subscription.request SHOULD respect the responsivity of its caller by /// returning in a timely manner. -/// * Subscription.cancel MUST respect the responsivity of its caller by +/// * Subscription.cancel MUST respect the responsivity of its caller by /// returning in a timely manner, MUST be idempotent and MUST be thread-safe. -/// * After the Subscription is cancelled, additional +/// * After the Subscription is cancelled, additional /// Subscription.request(n: u64) MUST be NOPs. -/// * After the Subscription is cancelled, additional Subscription.cancel() +/// * After the Subscription is cancelled, additional Subscription.cancel() /// MUST be NOPs. -/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// * While the Subscription is not cancelled, Subscription.request(n: u64) /// MUST register the given number of additional elements to be produced to the /// respective subscriber. -/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// * While the Subscription is not cancelled, Subscription.request(n: u64) /// MUST signal on_error if the argument is <= 0. The cause message SHOULD /// explain that non-positive request signals are illegal. -/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// * While the Subscription is not cancelled, Subscription.request(n: u64) /// MAY synchronously call on_next on this (or other) subscriber(s). -/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// * While the Subscription is not cancelled, Subscription.request(n: u64) /// MAY synchronously call on_complete or on_error on this (or other) /// subscriber(s). -/// * While the Subscription is not cancelled, Subscription.cancel() MUST +/// * While the Subscription is not cancelled, Subscription.cancel() MUST /// request the Publisher to eventually stop signaling its Subscriber. The /// operation is NOT REQUIRED to affect the Subscription immediately. -/// * While the Subscription is not cancelled, Subscription.cancel() MUST +/// * While the Subscription is not cancelled, Subscription.cancel() MUST /// request the Publisher to eventually drop any references to the corresponding /// subscriber. -/// * While the Subscription is not cancelled, calling Subscription.cancel MAY +/// * While the Subscription is not cancelled, calling Subscription.cancel MAY /// cause the Publisher, if stateful, to transition into the shut-down state if /// no other Subscription exists at this point. -/// * Calling Subscription.cancel MUST return normally. -/// * Calling Subscription.request MUST return normally. +/// * Calling Subscription.cancel MUST return normally. +/// * Calling Subscription.request MUST return normally. pub trait BufferSubscription { /// request fn request(&self, n: u64); /// cancel fn cancel(&self); } + /// Type used to describe errors produced by subscriptions. -type BufferError = Box<dyn std::error::Error + Send + Sync + 'static>; +pub type BufferError = anyhow::Error; /// Struct used to contain the buffer. pub struct Frame { @@ -154,3 +165,88 @@ pub struct Frame { /// A fence used for reading/writing safely. pub fence: i32, } + +#[cfg(test)] +mod test { + #![allow(warnings, unused)] + use super::*; + + use anyhow::anyhow; + use std::borrow::BorrowMut; + use std::error::Error; + use std::ops::Add; + use std::sync::Arc; + use std::time::Duration; + + use crate::publishers::testing::*; + use crate::subscribers::{testing::*, SharedSubscriber}; + + const STREAM_CONFIG: StreamConfig = StreamConfig { + width: 1, + height: 1, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, + stride: 0, + }; + + fn make_frame() -> Frame { + Frame { + buffer: STREAM_CONFIG + .create_hardware_buffer() + .expect("Unable to create hardware buffer for test"), + present_time: Instant::now() + Duration::from_secs(1), + fence: 0, + } + } + + #[test] + fn test_test_implementations_next() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_frame(make_frame()); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_))); + + subscriber.map_inner(|s| s.request(1)); + assert_eq!(publisher.pending_requests(), 1); + + publisher.send_frame(make_frame()); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_))); + assert_eq!(publisher.pending_requests(), 0); + } + + #[test] + fn test_test_implementations_complete() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_complete(); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete)); + } + + #[test] + fn test_test_implementations_error() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_error(anyhow!("error")); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_))); + } +} diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs new file mode 100644 index 0000000000..2fd518efee --- /dev/null +++ b/libs/bufferstreams/rust/src/publishers/mod.rs @@ -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 module provides [BufferSubscriber] implementations and helpers. + +pub mod testing; diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs new file mode 100644 index 0000000000..1593b18d7f --- /dev/null +++ b/libs/bufferstreams/rust/src/publishers/testing.rs @@ -0,0 +1,103 @@ +// 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. + +//! Provides useful publishers for testing specifically. These should not be used in normal code. + +use crate::{subscriptions::SharedBufferSubscription, *}; + +/// A [BufferPublisher] specifically for testing. +/// +/// Provides users the ability to send events and read the state of the subscription. +pub struct TestPublisher { + config: StreamConfig, + subscriber: Option<Box<dyn BufferSubscriber>>, + subscription: SharedBufferSubscription, +} + +impl TestPublisher { + /// Create a new [TestPublisher]. + pub fn new(config: StreamConfig) -> Self { + Self { config, subscriber: None, subscription: SharedBufferSubscription::new() } + } + + /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any + /// requested and returns true. Drops the frame and returns false otherwise. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_frame(&mut self, frame: Frame) -> bool { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber"); + + if self.subscription.take_request() { + subscriber.on_next(frame); + true + } else { + false + } + } + + /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber]. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_complete(&mut self) { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber"); + subscriber.on_complete(); + } + + /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber]. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_error(&mut self, error: BufferError) { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber"); + subscriber.on_error(error); + } + + /// Returns whether this [BufferPublisher] owns a subscriber. + pub fn has_subscriber(&self) -> bool { + self.subscriber.is_some() + } + + /// Returns the nummber of frames requested by the [BufferSubscriber]. + pub fn pending_requests(&self) -> u64 { + self.subscription.pending_requests() + } + + /// Returns whether the [BufferSubscriber] has cancelled the subscription. + pub fn is_cancelled(&self) -> bool { + self.subscription.is_cancelled() + } +} + +impl BufferPublisher for TestPublisher { + fn get_publisher_stream_config(&self) -> crate::StreamConfig { + self.config + } + + fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) { + assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber"); + self.subscriber = Some(Box::new(subscriber)); + + if let Some(ref mut subscriber) = self.subscriber { + subscriber.on_subscribe(self.subscription.clone_for_subscriber()); + } + } +} diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs new file mode 100644 index 0000000000..d0c621b0c4 --- /dev/null +++ b/libs/bufferstreams/rust/src/stream_config.rs @@ -0,0 +1,67 @@ +// 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. + +use nativewindow::*; + +/// The configuration of the buffers published by a [BufferPublisher] or +/// expected by a [BufferSubscriber]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StreamConfig { + /// Width in pixels of streaming buffers. + pub width: u32, + /// Height in pixels of streaming buffers. + pub height: u32, + /// Number of layers of streaming buffers. + pub layers: u32, + /// Format of streaming buffers. + pub format: AHardwareBuffer_Format::Type, + /// Usage of streaming buffers. + pub usage: AHardwareBuffer_UsageFlags, + /// Stride of streaming buffers. + pub stride: u32, +} + +impl StreamConfig { + /// Tries to create a new AHardwareBuffer from settings in a [StreamConfig]. + pub fn create_hardware_buffer(&self) -> Option<AHardwareBuffer> { + AHardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_create_hardware_buffer() { + let config = StreamConfig { + width: 123, + height: 456, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN + | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + stride: 0, + }; + + let maybe_buffer = config.create_hardware_buffer(); + assert!(maybe_buffer.is_some()); + + let buffer = maybe_buffer.unwrap(); + assert_eq!(config.width, buffer.width()); + assert_eq!(config.height, buffer.height()); + assert_eq!(config.format, buffer.format()); + assert_eq!(config.usage, buffer.usage()); + } +} diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs new file mode 100644 index 0000000000..dd038c6c32 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/mod.rs @@ -0,0 +1,20 @@ +// 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 module provides [BufferSubscriber] implementations and helpers. + +mod shared; +pub mod testing; + +pub use shared::*; diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs new file mode 100644 index 0000000000..46c58dc04a --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/shared.rs @@ -0,0 +1,94 @@ +// 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 module provides [BufferSubscriber] implementations and helpers. + +use std::sync::{Arc, Mutex}; + +use crate::*; + +/// A [BufferSubscriber] wrapper that provides shared access. +/// +/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With +/// [SharedSubscriber], a +/// +/// # Panics +/// +/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it +/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to +/// reflect the usual behavior where a publisher takes full ownership of a subscriber. +pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>); + +struct SharedSubscriberInner<S: BufferSubscriber> { + subscriber: S, + is_subscribed: bool, +} + +impl<S: BufferSubscriber> SharedSubscriber<S> { + /// Create a new wrapper around a [BufferSubscriber]. + pub fn new(subscriber: S) -> Self { + Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false }))) + } + + /// Provides access to an immutable reference to the wrapped [BufferSubscriber]. + pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R { + let inner = self.0.lock().unwrap(); + f(&inner.subscriber) + } + + /// Provides access to a mutable reference to the wrapped [BufferSubscriber]. + pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R { + let mut inner = self.0.lock().unwrap(); + f(&mut inner.subscriber) + } +} + +impl<S: BufferSubscriber> Clone for SharedSubscriber<S> { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} + +impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> { + fn get_subscriber_stream_config(&self) -> StreamConfig { + let inner = self.0.lock().unwrap(); + inner.subscriber.get_subscriber_stream_config() + } + + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) { + let mut inner = self.0.lock().unwrap(); + assert!( + !inner.is_subscribed, + "A SharedSubscriber can not be shared between two BufferPublishers" + ); + inner.is_subscribed = true; + + inner.subscriber.on_subscribe(subscription); + } + + fn on_next(&mut self, frame: Frame) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_next(frame); + } + + fn on_error(&mut self, error: BufferError) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_error(error); + } + + fn on_complete(&mut self) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_complete(); + } +} diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs new file mode 100644 index 0000000000..b7e970579e --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/testing.rs @@ -0,0 +1,106 @@ +// 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. + +//! Provides useful subscribers for testing specifically. These should not be used in normal code. + +use crate::*; + +/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber]. +pub enum TestingSubscriberEvent { + /// Represents a call to [BufferSubscriber::on_subscribe]. + Subscribe, + /// Represents a call to [BufferSubscriber::on_next]. + Next(Frame), + /// Represents a call to [BufferSubscriber::on_error]. + Error(BufferError), + /// Represents a call to [BufferSubscriber::on_complete]. + Complete, +} + +/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved +/// by the test to ensure appropriate behavior. +pub struct TestSubscriber { + config: StreamConfig, + subscription: Option<Box<dyn BufferSubscription>>, + events: Vec<TestingSubscriberEvent>, +} + +impl TestSubscriber { + /// Create a new [TestSubscriber]. + pub fn new(config: StreamConfig) -> Self { + Self { config, subscription: None, events: Vec::new() } + } + + /// Returns true if this [BufferSubscriber] has an active subscription. + pub fn has_subscription(&self) -> bool { + self.subscription.is_some() + } + + /// Make a request on behalf of this test subscriber. + /// + /// This will panic if there is no owned subscription. + pub fn request(&self, n: u64) { + let subscription = self + .subscription + .as_deref() + .expect("Tried to request on a TestSubscriber with no subscription"); + subscription.request(n); + } + + /// Cancel on behalf of this test subscriber. + /// + /// # Panics + /// + /// This will panic if there is no owned subscription. + pub fn cancel(&self) { + let subscription = self + .subscription + .as_deref() + .expect("Tried to cancel a TestSubscriber with no subscription"); + subscription.cancel(); + } + + /// Gets all of the events that have happened to this [BufferSubscriber] since the last call + /// to this function or it was created. + pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> { + let mut out = Vec::new(); + out.append(&mut self.events); + out + } +} + +impl BufferSubscriber for TestSubscriber { + fn get_subscriber_stream_config(&self) -> StreamConfig { + self.config + } + + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) { + assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once"); + self.subscription = Some(subscription); + + self.events.push(TestingSubscriberEvent::Subscribe); + } + + fn on_next(&mut self, frame: Frame) { + self.events.push(TestingSubscriberEvent::Next(frame)); + } + + fn on_error(&mut self, error: BufferError) { + self.events.push(TestingSubscriberEvent::Error(error)); + } + + fn on_complete(&mut self) { + self.events.push(TestingSubscriberEvent::Complete); + } +} diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs new file mode 100644 index 0000000000..e046dbbda3 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs @@ -0,0 +1,19 @@ +// 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 module provides [BufferSubscription] implementations and helpers. + +mod shared_buffer_subscription; + +pub use shared_buffer_subscription::*; diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs new file mode 100644 index 0000000000..90275c7320 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs @@ -0,0 +1,84 @@ +// 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. + +use std::sync::{Arc, Mutex}; + +use crate::*; + +/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and +/// as a state tracker by a [BufferPublisher]. +#[derive(Clone, Debug)] +pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>); + +#[derive(Debug, Default)] +struct BufferSubscriptionData { + requests: u64, + is_cancelled: bool, +} + +impl SharedBufferSubscription { + /// Create a new [SharedBufferSubscription]. + pub fn new() -> Self { + SharedBufferSubscription::default() + } + + /// Clone this [SharedBufferSubscription] so it can be passed into + /// [BufferSubscriber::on_subscribe]. + pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> { + Box::new(self.clone()) as Box<dyn BufferSubscription> + } + + /// If possible (not cancelled and with requests pending), take + pub fn take_request(&self) -> bool { + let mut data = self.0.lock().unwrap(); + + if data.is_cancelled || data.requests == 0 { + false + } else { + data.requests -= 1; + true + } + } + + /// Get the number of pending requests made by the [BufferSubscriber] via + /// [BufferSubscription::request]. + pub fn pending_requests(&self) -> u64 { + self.0.lock().unwrap().requests + } + + /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel]. + pub fn is_cancelled(&self) -> bool { + self.0.lock().unwrap().is_cancelled + } +} + +impl Default for SharedBufferSubscription { + fn default() -> Self { + Self(Arc::new(Mutex::new(BufferSubscriptionData::default()))) + } +} + +impl BufferSubscription for SharedBufferSubscription { + fn request(&self, n: u64) { + let mut data = self.0.lock().unwrap(); + if !data.is_cancelled { + data.requests = data.requests.saturating_add(n); + } + } + + fn cancel(&self) { + let mut data = self.0.lock().unwrap(); + data.is_cancelled = true; + } +} diff --git a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp index f835997187..9df5632e97 100644 --- a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp +++ b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp @@ -20,6 +20,7 @@ #include <fuzzer/FuzzedDataProvider.h> #include <android-base/unique_fd.h> #include <cputimeinstate.h> +#include <functional> using namespace android::bpf; diff --git a/libs/ftl/OWNERS b/libs/ftl/OWNERS new file mode 100644 index 0000000000..3f6129226a --- /dev/null +++ b/libs/ftl/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp index 5592a01fde..b68c2c3d02 100644 --- a/libs/ftl/enum_test.cpp +++ b/libs/ftl/enum_test.cpp @@ -33,6 +33,11 @@ static_assert(ftl::enum_name<E::ftl_last>() == "F"); static_assert(ftl::enum_name(E::C).value_or("?") == "C"); static_assert(ftl::enum_name(E{3}).value_or("?") == "?"); +static_assert(ftl::enum_name_full<E::B>() == "E::B"); +static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F"); +static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C"); +static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?"); + enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 }; static_assert(ftl::enum_begin_v<F> == F{0}); @@ -60,6 +65,10 @@ static_assert(ftl::enum_name<Flags::kNone>() == "kNone"); static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4"); static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7"); +static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone"); +static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4"); +static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7"); + // Though not flags, the enumerators are within the implicit range of bit indices. enum class Planet : std::uint8_t { kMercury, @@ -81,6 +90,9 @@ static_assert(ftl::enum_size_v<Planet> == 8); static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury"); static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn"); +static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury"); +static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn"); + // Unscoped enum must define explicit range, even if the underlying type is fixed. enum Temperature : int { kRoom = 20, @@ -122,16 +134,28 @@ TEST(Enum, Name) { EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth"); EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune"); + EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth"); + EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune"); + EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt); + EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt); } { EXPECT_EQ(ftl::enum_name(kRoom), "kRoom"); EXPECT_EQ(ftl::enum_name(kFridge), "kFridge"); EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer"); + EXPECT_EQ(ftl::enum_name(kRoom), "kRoom"); + EXPECT_EQ(ftl::enum_name(kFridge), "kFridge"); + EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer"); + EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt); EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt); EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt); + + EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt); + EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt); + EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt); } } @@ -158,16 +182,30 @@ TEST(Enum, String) { EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth"); EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune"); + EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth"); + EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune"); + EXPECT_EQ(ftl::enum_string(kPluto), "8"); + + EXPECT_EQ(ftl::enum_string_full(kPluto), "8"); + } { EXPECT_EQ(ftl::enum_string(kRoom), "kRoom"); EXPECT_EQ(ftl::enum_string(kFridge), "kFridge"); EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer"); + EXPECT_EQ(ftl::enum_string_full(kRoom), "20"); + EXPECT_EQ(ftl::enum_string_full(kFridge), "4"); + EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18"); + EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30"); EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0"); EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100"); + + EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30"); + EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0"); + EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100"); } } diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 2ea4d16260..9a27d2321b 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -204,27 +204,8 @@ cc_aconfig_library { aconfig_declarations: "libgui_flags", } -cc_library_shared { - name: "libgui", - vendor_available: true, - vndk: { - enabled: true, - private: true, - }, - double_loadable: true, - - defaults: ["libgui_bufferqueue-defaults"], - - static_libs: [ - "libgui_aidl_static", - "libgui_window_info_static", - "libguiflags", - ], - export_static_lib_headers: [ - "libgui_aidl_static", - "libgui_window_info_static", - ], - +filegroup { + name: "libgui-sources", srcs: [ ":framework_native_aidl_binder", ":framework_native_aidl_gui", @@ -268,11 +249,40 @@ cc_library_shared { "bufferqueue/2.0/B2HProducerListener.cpp", "bufferqueue/2.0/H2BGraphicBufferProducer.cpp", ], +} +cc_defaults { + name: "libgui-defaults", + defaults: ["libgui_bufferqueue-defaults"], + srcs: [":libgui-sources"], + static_libs: [ + "libgui_aidl_static", + "libgui_window_info_static", + "libguiflags", + ], shared_libs: [ "libbinder", "libGLESv2", ], +} + +cc_library_shared { + name: "libgui", + vendor_available: true, + vndk: { + enabled: true, + private: true, + }, + double_loadable: true, + + defaults: [ + "libgui-defaults", + ], + + export_static_lib_headers: [ + "libgui_aidl_static", + "libgui_window_info_static", + ], export_shared_lib_headers: [ "libbinder", @@ -346,6 +356,7 @@ filegroup { "BufferQueueProducer.cpp", "BufferQueueThreadState.cpp", "BufferSlot.cpp", + "FrameRateUtils.cpp", "FrameTimestamps.cpp", "GLConsumerUtils.cpp", "HdrMetadata.cpp", diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 207fa4fd31..dd0a028865 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -26,6 +26,8 @@ #include <gui/BufferQueueConsumer.h> #include <gui/BufferQueueCore.h> #include <gui/BufferQueueProducer.h> +#include <gui/Flags.h> +#include <gui/FrameRateUtils.h> #include <gui/GLConsumer.h> #include <gui/IProducerListener.h> #include <gui/Surface.h> @@ -39,6 +41,9 @@ #include <android-base/thread_annotations.h> #include <chrono> +#include <com_android_graphics_libgui_flags.h> + +using namespace com::android::graphics::libgui; using namespace std::chrono_literals; namespace { @@ -139,6 +144,16 @@ void BLASTBufferItemConsumer::onSidebandStreamChanged() { } } +#if FLAG_BQ_SET_FRAME_RATE +void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) { + sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote(); + if (bbq != nullptr) { + bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy); + } +} +#endif + void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) { Mutex::Autolock lock(mMutex); mFrameEventHistory.resize(newSize); @@ -890,6 +905,10 @@ public: status_t setFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy) override { + if (flags::bq_setframerate()) { + return Surface::setFrameRate(frameRate, compatibility, changeFrameRateStrategy); + } + std::lock_guard _lock{mMutex}; if (mDestroyed) { return DEAD_OBJECT; diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp index 66cad03fec..ab0f6d213f 100644 --- a/libs/gui/BufferQueue.cpp +++ b/libs/gui/BufferQueue.cpp @@ -22,6 +22,7 @@ #include <gui/BufferQueueConsumer.h> #include <gui/BufferQueueCore.h> #include <gui/BufferQueueProducer.h> +#include <gui/Flags.h> namespace android { @@ -98,6 +99,16 @@ void BufferQueue::ProxyConsumerListener::addAndGetFrameTimestamps( } } +#if FLAG_BQ_SET_FRAME_RATE +void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) { + sp<ConsumerListener> listener(mConsumerListener.promote()); + if (listener != nullptr) { + listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy); + } +} +#endif + void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer, sp<IGraphicBufferConsumer>* outConsumer, bool consumerIsSurfaceFlinger) { diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 920b83dba9..67dff6dec6 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -32,6 +32,8 @@ #include <gui/BufferItem.h> #include <gui/BufferQueueCore.h> #include <gui/BufferQueueProducer.h> +#include <gui/Flags.h> +#include <gui/FrameRateUtils.h> #include <gui/GLConsumer.h> #include <gui/IConsumerListener.h> #include <gui/IProducerListener.h> @@ -1751,4 +1753,27 @@ status_t BufferQueueProducer::setAutoPrerotation(bool autoPrerotation) { return NO_ERROR; } +#if FLAG_BQ_SET_FRAME_RATE +status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) { + ATRACE_CALL(); + BQ_LOGV("setFrameRate: %.2f", frameRate); + + if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy, + "BufferQueueProducer::setFrameRate")) { + return BAD_VALUE; + } + + sp<IConsumerListener> listener; + { + std::lock_guard<std::mutex> lock(mCore->mMutex); + listener = mCore->mConsumerListener; + } + if (listener != nullptr) { + listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy); + } + return NO_ERROR; +} +#endif + } // namespace android diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 46fb068dee..93df12471d 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -324,6 +324,12 @@ void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool c to_string(displayId).c_str(), toString(connected)); } +void Choreographer::dispatchHotplugConnectionError(nsecs_t, int32_t connectionError) { + ALOGV("choreographer %p ~ received hotplug connection error event (connectionError=%d), " + "ignoring.", + this, connectionError); +} + void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) { LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered"); } @@ -394,4 +400,4 @@ int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) { return iter->second; } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index 8a883770d8..5dd058cf9f 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -173,7 +173,13 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp, *outVsyncEventData = ev.vsync.vsyncData; break; case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: - dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected); + if (ev.hotplug.connectionError == 0) { + dispatchHotplug(ev.header.timestamp, ev.header.displayId, + ev.hotplug.connected); + } else { + dispatchHotplugConnectionError(ev.header.timestamp, + ev.hotplug.connectionError); + } break; case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: dispatchModeChanged(ev.header.timestamp, ev.header.displayId, diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp new file mode 100644 index 0000000000..6993bfab45 --- /dev/null +++ b/libs/gui/FrameRateUtils.cpp @@ -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. + */ + +#include <gui/Flags.h> +#include <gui/FrameRateUtils.h> +#include <system/window.h> +#include <utils/Log.h> + +#include <cmath> + +namespace android { +// Returns true if the frameRate is valid. +// +// @param frameRate the frame rate in Hz +// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* +// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_* +// @param functionName calling function or nullptr. Used for logging +// @param privileged whether caller has unscoped surfaceflinger access +bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy, + const char* inFunctionName, bool privileged) { + const char* functionName = inFunctionName != nullptr ? inFunctionName : "call"; + int floatClassification = std::fpclassify(frameRate); + if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) { + ALOGE("%s failed - invalid frame rate %f", functionName, frameRate); + return false; + } + + if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT && + compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE && + (!privileged || + (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT && + compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) { + ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName, + compatibility, privileged ? "yes" : "no"); + return false; + } + + if (__builtin_available(android 31, *)) { + if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS && + changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) { + ALOGE("%s failed - invalid change frame rate strategy value %d", functionName, + changeFrameRateStrategy); + if (FLAG_BQ_SET_FRAME_RATE) { + return false; + } + } + } + + return true; +} + +} // namespace android diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp index 918ff2dd25..d0c09e481d 100644 --- a/libs/gui/IGraphicBufferProducer.cpp +++ b/libs/gui/IGraphicBufferProducer.cpp @@ -27,11 +27,12 @@ #include <binder/Parcel.h> #include <binder/IInterface.h> -#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h> -#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h> #include <gui/BufferQueueDefs.h> +#include <gui/Flags.h> #include <gui/IGraphicBufferProducer.h> #include <gui/IProducerListener.h> +#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h> +#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h> namespace android { // ---------------------------------------------------------------------------- @@ -78,6 +79,7 @@ enum { CANCEL_BUFFERS, QUERY_MULTIPLE, GET_LAST_QUEUED_BUFFER2, + SET_FRAME_RATE, }; class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer> @@ -761,6 +763,21 @@ public: } return result; } +#if FLAG_BQ_SET_FRAME_RATE + virtual status_t setFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) override { + Parcel data, reply; + data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor()); + data.writeFloat(frameRate); + data.writeInt32(compatibility); + data.writeInt32(changeFrameRateStrategy); + status_t result = remote()->transact(SET_FRAME_RATE, data, &reply); + if (result == NO_ERROR) { + result = reply.readInt32(); + } + return result; + } +#endif }; // Out-of-line virtual method definition to trigger vtable emission in this @@ -956,6 +973,14 @@ status_t IGraphicBufferProducer::setAutoPrerotation(bool autoPrerotation) { return INVALID_OPERATION; } +#if FLAG_BQ_SET_FRAME_RATE +status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, + int8_t /*changeFrameRateStrategy*/) { + // No-op for IGBP other than BufferQueue. + return INVALID_OPERATION; +} +#endif + status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) { status_t res = OK; res = parcel->writeUint32(USE_BUFFER_QUEUE); @@ -1497,6 +1522,17 @@ status_t BnGraphicBufferProducer::onTransact( reply->writeInt32(result); return NO_ERROR; } +#if FLAG_BQ_SET_FRAME_RATE + case SET_FRAME_RATE: { + CHECK_INTERFACE(IGraphicBuffer, data, reply); + float frameRate = data.readFloat(); + int8_t compatibility = data.readInt32(); + int8_t changeFrameRateStrategy = data.readInt32(); + status_t result = setFrameRate(frameRate, compatibility, changeFrameRateStrategy); + reply->writeInt32(result); + return NO_ERROR; + } +#endif } return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 27b1d8b09b..613721e103 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -22,6 +22,7 @@ #include <android/gui/ISurfaceComposerClient.h> #include <android/native_window.h> #include <binder/Parcel.h> +#include <gui/FrameRateUtils.h> #include <gui/IGraphicBufferProducer.h> #include <gui/LayerState.h> #include <gui/SurfaceControl.h> @@ -871,34 +872,6 @@ status_t InputWindowCommands::read(const Parcel& input) { return NO_ERROR; } -bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy, - const char* inFunctionName, bool privileged) { - const char* functionName = inFunctionName != nullptr ? inFunctionName : "call"; - int floatClassification = std::fpclassify(frameRate); - if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) { - ALOGE("%s failed - invalid frame rate %f", functionName, frameRate); - return false; - } - - if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT && - compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE && - (!privileged || - (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT && - compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) { - ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName, - compatibility, privileged ? "yes" : "no"); - return false; - } - - if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS && - changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) { - ALOGE("%s failed - invalid change frame rate strategy value %d", functionName, - changeFrameRateStrategy); - } - - return true; -} - // ---------------------------------------------------------------------------- namespace gui { diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp index 53a2f64d11..a87f05357f 100644 --- a/libs/gui/Surface.cpp +++ b/libs/gui/Surface.cpp @@ -43,6 +43,7 @@ #include <gui/AidlStatusUtil.h> #include <gui/BufferItem.h> +#include <gui/Flags.h> #include <gui/IProducerListener.h> #include <gui/ISurfaceComposer.h> @@ -50,8 +51,11 @@ #include <private/gui/ComposerService.h> #include <private/gui/ComposerServiceAIDL.h> +#include <com_android_graphics_libgui_flags.h> + namespace android { +using namespace com::android::graphics::libgui; using gui::aidl_utils::statusTFromBinderStatus; using ui::Dataspace; @@ -2565,8 +2569,22 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vector<int32_ mSurfaceListener->onBuffersDiscarded(discardedBufs); } -[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/, - int8_t /*changeFrameRateStrategy*/) { +status_t Surface::setFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) { +#if FLAG_BQ_SET_FRAME_RATE + if (flags::bq_setframerate()) { + status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility, + changeFrameRateStrategy); + ALOGE_IF(err, "IGraphicBufferProducer::setFrameRate(%.2f) returned %s", frameRate, + strerror(-err)); + return err; + } +#else + static_cast<void>(frameRate); + static_cast<void>(compatibility); + static_cast<void>(changeFrameRateStrategy); +#endif + 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 diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index c2543d182b..038764b800 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -26,6 +26,7 @@ #include <android/gui/IWindowInfosListener.h> #include <android/gui/TrustedPresentationThresholds.h> #include <android/os/IInputConstants.h> +#include <gui/FrameRateUtils.h> #include <gui/TraceUtils.h> #include <utils/Errors.h> #include <utils/Log.h> diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp index 6e4f074825..0d2a52b576 100644 --- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp +++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp @@ -62,7 +62,10 @@ DisplayEventReceiver::Event buildDisplayEvent(FuzzedDataProvider* fdp, uint32_t } case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: { - event.hotplug = DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/}; + event.hotplug = + DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/, + fdp->ConsumeIntegral< + int32_t>() /*connectionError*/}; break; } case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: { diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h index 177d5f89c9..bdf8856a75 100644 --- a/libs/gui/fuzzer/libgui_fuzzer_utils.h +++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h @@ -197,6 +197,7 @@ public: MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData)); MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool)); + MOCK_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t)); MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t)); MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId)); MOCK_METHOD3(dispatchFrameRateOverrides, diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h index a49a85984f..02d7c4d2ac 100644 --- a/libs/gui/include/gui/BLASTBufferQueue.h +++ b/libs/gui/include/gui/BLASTBufferQueue.h @@ -17,9 +17,10 @@ #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H -#include <gui/IGraphicBufferProducer.h> -#include <gui/BufferItemConsumer.h> #include <gui/BufferItem.h> +#include <gui/BufferItemConsumer.h> +#include <gui/Flags.h> +#include <gui/IGraphicBufferProducer.h> #include <gui/SurfaceComposerClient.h> #include <utils/Condition.h> @@ -58,6 +59,10 @@ public: protected: void onSidebandStreamChanged() override EXCLUDES(mMutex); +#if FLAG_BQ_SET_FRAME_RATE + void onSetFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) override; +#endif private: const wp<BLASTBufferQueue> mBLASTBufferQueue; diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h index 690587f0e6..2756277f2c 100644 --- a/libs/gui/include/gui/BufferQueue.h +++ b/libs/gui/include/gui/BufferQueue.h @@ -19,9 +19,10 @@ #include <gui/BufferItem.h> #include <gui/BufferQueueDefs.h> +#include <gui/Flags.h> +#include <gui/IConsumerListener.h> #include <gui/IGraphicBufferConsumer.h> #include <gui/IGraphicBufferProducer.h> -#include <gui/IConsumerListener.h> namespace android { @@ -69,6 +70,10 @@ public: void addAndGetFrameTimestamps( const NewFrameEventsEntry* newTimestamps, FrameEventHistoryDelta* outDelta) override; +#if FLAG_BQ_SET_FRAME_RATE + void onSetFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) override; +#endif private: // mConsumerListener is a weak reference to the IConsumerListener. This is // the raison d'etre of ProxyConsumerListener. diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h index 1d13dab623..38805d0221 100644 --- a/libs/gui/include/gui/BufferQueueProducer.h +++ b/libs/gui/include/gui/BufferQueueProducer.h @@ -18,6 +18,7 @@ #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H #include <gui/BufferQueueDefs.h> +#include <gui/Flags.h> #include <gui/IGraphicBufferProducer.h> namespace android { @@ -201,6 +202,11 @@ public: // See IGraphicBufferProducer::setAutoPrerotation virtual status_t setAutoPrerotation(bool autoPrerotation); +#if FLAG_BQ_SET_FRAME_RATE + // See IGraphicBufferProducer::setFrameRate + status_t setFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) override; +#endif protected: // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index 1df9b11432..9fef512b64 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -110,6 +110,7 @@ private: void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, VsyncEventData vsyncEventData) override; void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; + void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override; void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, nsecs_t vsyncPeriod) override; void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override; @@ -137,4 +138,4 @@ private: static constexpr size_t kMaxStartTimes = 250; }; -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h index 140efa6d97..fe2dd206ed 100644 --- a/libs/gui/include/gui/DisplayEventDispatcher.h +++ b/libs/gui/include/gui/DisplayEventDispatcher.h @@ -53,6 +53,9 @@ private: VsyncEventData vsyncEventData) = 0; virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) = 0; + + virtual void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) = 0; + virtual void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, nsecs_t vsyncPeriod) = 0; // AChoreographer-specific hook for processing null-events so that looper diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h index 7fd6c35c5e..79582ce685 100644 --- a/libs/gui/include/gui/DisplayEventReceiver.h +++ b/libs/gui/include/gui/DisplayEventReceiver.h @@ -88,6 +88,7 @@ public: struct Hotplug { bool connected; + int32_t connectionError __attribute__((aligned(4))); }; struct ModeChange { diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h new file mode 100644 index 0000000000..a2cff56e97 --- /dev/null +++ b/libs/gui/include/gui/Flags.h @@ -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. + */ + +#pragma once + +// TODO(281695725): replace this with build time flags, whenever they are available +#ifndef FLAG_BQ_SET_FRAME_RATE +#define FLAG_BQ_SET_FRAME_RATE false +#endif
\ No newline at end of file diff --git a/libs/gui/include/gui/FrameRateUtils.h b/libs/gui/include/gui/FrameRateUtils.h new file mode 100644 index 0000000000..16896efe1f --- /dev/null +++ b/libs/gui/include/gui/FrameRateUtils.h @@ -0,0 +1,26 @@ +/* + * 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 <stdint.h> + +namespace android { + +bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy, + const char* inFunctionName, bool privileged = false); + +} // namespace android
\ No newline at end of file diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h index 0ab2399eb2..e183bf2668 100644 --- a/libs/gui/include/gui/IConsumerListener.h +++ b/libs/gui/include/gui/IConsumerListener.h @@ -19,6 +19,8 @@ #include <binder/IInterface.h> #include <binder/SafeInterface.h> +#include <gui/Flags.h> + #include <utils/Errors.h> #include <utils/RefBase.h> @@ -90,6 +92,12 @@ public: // WARNING: This method can only be called when the BufferQueue is in the consumer's process. virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/, FrameEventHistoryDelta* /*outDelta*/) {} + +#if FLAG_BQ_SET_FRAME_RATE + // Notifies the consumer of a setFrameRate call from the producer side. + virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/, + int8_t /*changeFrameRateStrategy*/) {} +#endif }; #ifndef NO_BINDER diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h index 98df83453d..3562906870 100644 --- a/libs/gui/include/gui/IGraphicBufferProducer.h +++ b/libs/gui/include/gui/IGraphicBufferProducer.h @@ -31,6 +31,7 @@ #include <ui/Rect.h> #include <ui/Region.h> +#include <gui/Flags.h> #include <gui/FrameTimestamps.h> #include <gui/HdrMetadata.h> @@ -676,6 +677,12 @@ public: // the width and height used for dequeueBuffer will be additionally swapped. virtual status_t setAutoPrerotation(bool autoPrerotation); +#if FLAG_BQ_SET_FRAME_RATE + // Sets the apps intended frame rate. + virtual status_t setFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy); +#endif + struct RequestBufferOutput : public Flattenable<RequestBufferOutput> { RequestBufferOutput() = default; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 35fcccdf78..4371007778 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -480,16 +480,6 @@ static inline int compare_type(const DisplayState& lhs, const DisplayState& rhs) return compare_type(lhs.token, rhs.token); } -// Returns true if the frameRate is valid. -// -// @param frameRate the frame rate in Hz -// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* -// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_* -// @param functionName calling function or nullptr. Used for logging -// @param privileged whether caller has unscoped surfaceflinger access -bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy, - const char* functionName, bool privileged = false); - }; // namespace android #endif // ANDROID_SF_LAYER_STATE_H diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp index 462ce6e14f..38c0eed474 100644 --- a/libs/gui/tests/Android.bp +++ b/libs/gui/tests/Android.bp @@ -15,9 +15,13 @@ cc_test { name: "libgui_test", test_suites: ["device-tests"], - cflags: [ + defaults: ["libgui-defaults"], + + cppflags: [ "-Wall", "-Werror", + "-Wno-extra", + "-DFLAG_BQ_SET_FRAME_RATE=true", ], srcs: [ @@ -28,6 +32,7 @@ cc_test { "CompositorTiming_test.cpp", "CpuConsumer_test.cpp", "EndToEndNativeInputTest.cpp", + "FrameRateUtilsTest.cpp", "DisplayInfo_test.cpp", "DisplayedContentSampling_test.cpp", "FillBuffer.cpp", @@ -53,19 +58,12 @@ cc_test { "android.hardware.configstore@1.0", "android.hardware.configstore-utils", "libSurfaceFlingerProp", - "libbase", - "liblog", - "libEGL", "libGLESv1_CM", - "libGLESv2", - "libbinder", - "libcutils", - "libgui", - "libhidlbase", "libinput", - "libui", - "libutils", - "libnativewindow", + ], + + static_libs: [ + "libgmock", ], header_libs: ["libsurfaceflinger_headers"], diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index 0168877478..17aa5f1350 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -37,14 +37,18 @@ #include <system/window.h> +#include <gmock/gmock.h> #include <gtest/gtest.h> #include <future> #include <thread> +#include <com_android_graphics_libgui_flags.h> + using namespace std::chrono_literals; namespace android { +using namespace com::android::graphics::libgui; class BufferQueueTest : public ::testing::Test { @@ -1261,6 +1265,31 @@ TEST_F(BufferQueueTest, TestProducerConnectDisconnect) { ASSERT_EQ(NO_INIT, mProducer->disconnect(NATIVE_WINDOW_API_CPU)); } +TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) { + if (flags::bq_setframerate()) { + ASSERT_EQ(true, FLAG_BQ_SET_FRAME_RATE); + } +} + +struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer { + BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer) + : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {} + + MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override)); +}; + +TEST_F(BufferQueueTest, TestSetFrameRate) { + sp<IGraphicBufferProducer> producer; + sp<IGraphicBufferConsumer> consumer; + BufferQueue::createBufferQueue(&producer, &consumer); + + sp<BufferItemConsumerSetFrameRateListener> bufferConsumer = + sp<BufferItemConsumerSetFrameRateListener>::make(consumer); + + EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1); + producer->setFrameRate(12.34f, 1, 0); +} + class Latch { public: explicit Latch(int expected) : mExpected(expected) {} diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp index 3949d70aac..29eeaa806f 100644 --- a/libs/gui/tests/DisplayEventStructLayout_test.cpp +++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp @@ -59,6 +59,7 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) { lastFrameTimelineOffset + 16); CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0); + CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connectionError, 4); CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, modeId, 0); CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, vsyncPeriod, 8); diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp new file mode 100644 index 0000000000..5fe22b05f9 --- /dev/null +++ b/libs/gui/tests/FrameRateUtilsTest.cpp @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> + +#include <gui/FrameRateUtils.h> +#include <inttypes.h> +#include <system/window.h> + +#include <com_android_graphics_libgui_flags.h> + +namespace android { +using namespace com::android::graphics::libgui; + +TEST(FrameRateUtilsTest, ValidateFrameRate) { + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, "")); + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + + // Privileged APIs. + EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + + constexpr bool kPrivileged = true; + EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "", + kPrivileged)); + EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "", + kPrivileged)); + + // Invalid frame rate. + EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, + ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + + // Invalid compatibility. + EXPECT_FALSE( + ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); + + // Invalid change frame rate strategy. + if (flags::bq_setframerate()) { + EXPECT_FALSE( + ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, -1, "")); + EXPECT_FALSE( + ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, 2, "")); + } +} + +} // namespace android diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 1f1439608b..16000139f7 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -476,8 +476,6 @@ status_t InputChannel::sendMessage(const InputMessage* msg) { } status_t InputChannel::receiveMessage(InputMessage* msg) { - ATRACE_NAME_IF(ATRACE_ENABLED(), - StringPrintf("receiveMessage(inputChannel=%s)", mName.c_str())); ssize_t nRead; do { nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT); diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index c8d1da7b1f..412931bc41 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -205,6 +205,13 @@ std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) { coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x); coords.setAxisValue(AMOTION_EVENT_AXIS_Y, predictedPoint.y); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]); + // Copy forward tilt and orientation from the last event until they are predicted + // (b/291789258). + coords.setAxisValue(AMOTION_EVENT_AXIS_TILT, + event.getAxisValue(AMOTION_EVENT_AXIS_TILT, 0)); + coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, + event.getRawPointerCoords(0)->getAxisValue( + AMOTION_EVENT_AXIS_ORIENTATION)); predictionTime += mModel->config().predictionInterval; if (i == 0) { diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp index ee961f0ffc..a0ec6adc65 100644 --- a/libs/input/tests/InputDevice_test.cpp +++ b/libs/input/tests/InputDevice_test.cpp @@ -20,6 +20,7 @@ #include <input/InputDevice.h> #include <input/KeyLayoutMap.h> #include <input/Keyboard.h> +#include <linux/uinput.h> #include "android-base/file.h" namespace android { @@ -97,7 +98,7 @@ TEST_F(InputDeviceKeyMapTest, keyCharacterMapWithOverlayParcelingTest) { ASSERT_EQ(*map, *mKeyMap.keyCharacterMap); } -TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) { +TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) { std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm"; std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm"; std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm"; @@ -133,14 +134,33 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) { ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap); } -TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) { +TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) { + std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm"; + base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay = + KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY); + ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath; + + // Apply the French overlay + mKeyMap.keyCharacterMap->combine(*frenchOverlay->get()); + + // Check if mapping for key_Q is correct + int32_t outKeyCode; + status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode); + ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath; + ASSERT_EQ(outKeyCode, AKEYCODE_A); + + mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode); + ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath; +} + +TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) { std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl"; base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath); ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath; } -TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) { +TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) { std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl"; base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath); diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index e253ad596e..cc1d12bc5c 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -24,6 +24,7 @@ #include <EGL/egl.h> #include <EGL/eglext.h> #include <GrContextOptions.h> +#include <GrTypes.h> #include <android-base/stringprintf.h> #include <gl/GrGLInterface.h> #include <gui/TraceUtils.h> @@ -338,7 +339,8 @@ base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) { } else { ATRACE_BEGIN("Submit(sync=false)"); } - bool success = grContext->submit(requireSync); + bool success = grContext->submit(requireSync ? GrSyncCpu::kYes : + GrSyncCpu::kNo); ATRACE_END(); if (!success) { ALOGE("Failed to flush RenderEngine commands"); diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index 6ecc6ab362..17f263d2ce 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -681,7 +681,7 @@ base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { flushInfo.fFinishedContext = destroySemaphoreInfo; } GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); - grContext->submit(false /* no cpu sync */); + grContext->submit(GrSyncCpu::kNo); int drawFenceFd = -1; if (semaphore != VK_NULL_HANDLE) { if (GrSemaphoresSubmitted::kYes == submitted) { diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h index 30149c11f0..b86ce5f450 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -136,7 +136,7 @@ private: size_t mHeight; // Position of EXIF package, default value is -1 which means no EXIF package appears. - size_t mExifPos; + ssize_t mExifPos = -1; }; } /* namespace android::ultrahdr */ diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp index 74760d9b32..3d70fcea71 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -85,13 +85,11 @@ static void copyJpegWithoutExif(jr_compressed_ptr pDest, jr_compressed_ptr pSource, size_t exif_pos, size_t exif_size) { - memcpy(pDest, pSource, sizeof(jpegr_compressed_struct)); - const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign pDest->length = pSource->length - exif_size - exif_offset; pDest->data = new uint8_t[pDest->length]; - std::unique_ptr<uint8_t[]> dest_data; - dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data)); + pDest->maxLength = pDest->length; + pDest->colorGamut = pSource->colorGamut; memcpy(pDest->data, pSource->data, exif_pos - exif_offset); memcpy((uint8_t*)pDest->data + exif_pos - exif_offset, (uint8_t*)pSource->data + exif_pos + exif_size, @@ -1262,13 +1260,13 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) { return ERROR_JPEGR_DECODE_ERROR; } - jpegr_exif_struct exif_from_jpg; - exif_from_jpg.data = nullptr; - exif_from_jpg.length = 0; - jpegr_compressed_struct new_jpg_image; - new_jpg_image.data = nullptr; - new_jpg_image.length = 0; - if (decoder.getEXIFPos() != 0) { + jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0}; + jpegr_compressed_struct new_jpg_image = {.data = nullptr, + .length = 0, + .maxLength = 0, + .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + std::unique_ptr<uint8_t[]> dest_data; + if (decoder.getEXIFPos() >= 0) { if (pExif != nullptr) { ALOGE("received EXIF from outside while the primary image already contains EXIF"); return ERROR_JPEGR_INVALID_INPUT_TYPE; @@ -1277,6 +1275,7 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, primary_jpg_image_ptr, decoder.getEXIFPos(), decoder.getEXIFSize()); + dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data)); exif_from_jpg.data = decoder.getEXIFPtr(); exif_from_jpg.length = decoder.getEXIFSize(); pExif = &exif_from_jpg; diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp index 052efb6bbb..e5b1e44b44 100644 --- a/services/gpuservice/Android.bp +++ b/services/gpuservice/Android.bp @@ -21,7 +21,13 @@ cc_defaults { cc_defaults { name: "libgpuservice_defaults", - defaults: ["gpuservice_defaults"], + defaults: [ + "gpuservice_defaults", + "libvkjson_deps", + "libgfxstats_deps", + "libgpumem_deps", + "libgpumemtracer_deps", + ], cflags: [ "-DLOG_TAG=\"GpuService\"", ], @@ -29,17 +35,17 @@ cc_defaults { "libbase", "libbinder", "libcutils", - "libgfxstats", - "libgpumem", "libgpuwork", - "libgpumemtracer", "libgraphicsenv", "liblog", "libutils", - "libvkjson", ], static_libs: [ + "libgfxstats", + "libgpumem", + "libgpumemtracer", "libserviceutils", + "libvkjson", ], export_static_lib_headers: [ "libserviceutils", @@ -68,7 +74,7 @@ filegroup { ], } -cc_library_shared { +cc_library_static { name: "libgpuservice", defaults: ["libgpuservice_production_defaults"], export_include_dirs: ["include"], @@ -96,14 +102,17 @@ filegroup { cc_binary { name: "gpuservice", - defaults: ["libgpuservice_binary"], + defaults: [ + "libgpuservice_binary", + "libgpuservice_production_defaults", + ], init_rc: ["gpuservice.rc"], required: [ "bpfloader", "gpuMem.o", ], srcs: [":gpuservice_binary_sources"], - shared_libs: [ + static_libs: [ "libgpuservice", ], } diff --git a/services/gpuservice/gpumem/Android.bp b/services/gpuservice/gpumem/Android.bp index d0ea856fb5..66a30597a7 100644 --- a/services/gpuservice/gpumem/Android.bp +++ b/services/gpuservice/gpumem/Android.bp @@ -21,12 +21,8 @@ package { default_applicable_licenses: ["frameworks_native_license"], } -cc_library_shared { - name: "libgpumem", - srcs: [ - "GpuMem.cpp", - ], - header_libs: ["bpf_headers"], +cc_defaults { + name: "libgpumem_deps", shared_libs: [ "libbase", "libbpf_bcc", @@ -34,6 +30,17 @@ cc_library_shared { "liblog", "libutils", ], +} + +cc_library_static { + name: "libgpumem", + defaults: [ + "libgpumem_deps", + ], + srcs: [ + "GpuMem.cpp", + ], + header_libs: ["bpf_headers"], export_include_dirs: ["include"], export_header_lib_headers: ["bpf_headers"], export_shared_lib_headers: ["libbase"], diff --git a/services/gpuservice/gpustats/Android.bp b/services/gpuservice/gpustats/Android.bp index 54291ad6c6..0e64716e6d 100644 --- a/services/gpuservice/gpustats/Android.bp +++ b/services/gpuservice/gpustats/Android.bp @@ -7,11 +7,8 @@ package { default_applicable_licenses: ["frameworks_native_license"], } -cc_library_shared { - name: "libgfxstats", - srcs: [ - "GpuStats.cpp", - ], +cc_defaults { + name: "libgfxstats_deps", shared_libs: [ "libcutils", "libgraphicsenv", @@ -22,6 +19,16 @@ cc_library_shared { "libstatssocket", "libutils", ], +} + +cc_library_static { + name: "libgfxstats", + defaults: [ + "libgfxstats_deps", + ], + srcs: [ + "GpuStats.cpp", + ], export_include_dirs: ["include"], export_shared_lib_headers: [ "libstatspull", diff --git a/services/gpuservice/tests/fuzzers/Android.bp b/services/gpuservice/tests/fuzzers/Android.bp index 6bcc5e8601..d4d48c48ea 100644 --- a/services/gpuservice/tests/fuzzers/Android.bp +++ b/services/gpuservice/tests/fuzzers/Android.bp @@ -5,10 +5,12 @@ package { cc_fuzz { name: "gpu_service_fuzzer", defaults: [ + "libgpuservice_defaults", "service_fuzzer_defaults", "fuzzer_disable_leaks", ], static_libs: [ + "libgpuservice", "liblog", ], fuzz_config: { @@ -20,7 +22,4 @@ cc_fuzz { }, include_dirs: ["frameworks/native/services/gpuservice/"], srcs: ["GpuServiceFuzzer.cpp"], - shared_libs: [ - "libgpuservice", - ], } diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp index c870b17b79..8056a2c601 100644 --- a/services/gpuservice/tests/unittests/Android.bp +++ b/services/gpuservice/tests/unittests/Android.bp @@ -24,6 +24,9 @@ package { cc_test { name: "gpuservice_unittest", test_suites: ["device-tests"], + defaults: [ + "libgpuservice_defaults", + ], srcs: [ "GpuMemTest.cpp", "GpuMemTracerTest.cpp", @@ -36,9 +39,6 @@ cc_test { "libbinder", "libbpf_bcc", "libcutils", - "libgfxstats", - "libgpumem", - "libgpumemtracer", "libgraphicsenv", "liblog", "libprotobuf-cpp-lite", @@ -46,10 +46,10 @@ cc_test { "libstatslog", "libstatspull", "libutils", - "libgpuservice", ], static_libs: [ "libgmock", + "libgpuservice", "libperfetto_client_experimental", "perfetto_trace_protos", ], diff --git a/services/gpuservice/tracing/Android.bp b/services/gpuservice/tracing/Android.bp index a1bc1edad8..d636b7d195 100644 --- a/services/gpuservice/tracing/Android.bp +++ b/services/gpuservice/tracing/Android.bp @@ -21,20 +21,28 @@ package { default_applicable_licenses: ["frameworks_native_license"], } -cc_library_shared { - name: "libgpumemtracer", - srcs: [ - "GpuMemTracer.cpp", - ], +cc_defaults { + name: "libgpumemtracer_deps", shared_libs: [ - "libgpumem", "libbase", "liblog", "libutils", ], static_libs: [ + "libgpumem", "libperfetto_client_experimental", ], +} + +cc_library_static { + name: "libgpumemtracer", + defaults: [ + "libgpumemtracer_deps", + "libgpumem_deps", + ], + srcs: [ + "GpuMemTracer.cpp", + ], export_include_dirs: ["include"], export_static_lib_headers: [ "libperfetto_client_experimental", diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index cb369a836e..fa8f5485c0 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -269,6 +269,11 @@ std::string MotionEntry::getDescription() const { return msg; } +std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry) { + out << motionEntry.getDescription(); + return out; +} + // --- SensorEntry --- SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source, diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 8dc2a2a221..dd4aab85f5 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -24,6 +24,7 @@ #include <stdint.h> #include <utils/Timers.h> #include <functional> +#include <ostream> #include <string> namespace android::inputdispatcher { @@ -189,6 +190,8 @@ struct MotionEntry : EventEntry { ~MotionEntry() override; }; +std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry); + struct SensorEntry : EventEntry { int32_t deviceId; uint32_t source; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index d98641ec1d..b3be89ed06 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -668,7 +668,15 @@ std::vector<TouchedWindow> 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/299977100): 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; } @@ -706,6 +714,40 @@ void filterUntrustedTargets(TouchState& touchState, std::vector<InputTarget>& ta }); } +/** + * In general, touch should be always split between windows. Some exceptions: + * 1. Don't split touch if all of the below is true: + * (a) we have an active pointer down *and* + * (b) a new pointer is going down that's from the same device *and* + * (c) the window that's receiving the current pointer does not support split touch. + * 2. Don't split mouse events + */ +bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) { + 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; + } + if (touchedWindow.windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::IS_WALLPAPER)) { + // Wallpaper window should not affect whether or not touch is split + continue; + } + + if (touchedWindow.hasTouchingPointers(entry.deviceId)) { + return false; + } + } + return true; +} + } // namespace // --- InputDispatcher --- @@ -2210,40 +2252,6 @@ std::vector<Monitor> 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; - } - if (touchedWindow.windowHandle->getInfo()->inputConfig.test( - gui::WindowInfo::InputConfig::IS_WALLPAPER)) { - // Wallpaper window should not affect whether or not touch is split - continue; - } - - if (touchedWindow.hasTouchingPointers(entry.deviceId)) { - return false; - } - } - return true; -} - std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions, InputEventInjectionResult& outInjectionResult) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 512bc4f986..4d145c68f6 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -522,7 +522,6 @@ 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<android::gui::WindowInfoHandle> 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 4221e42ae5..9dcf615479 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -288,4 +288,9 @@ std::string TouchState::dump() const { return out; } +std::ostream& operator<<(std::ostream& out, const TouchState& state) { + out << state.dump(); + return out; +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 39e63e5894..f01693662c 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -17,6 +17,7 @@ #pragma once #include <bitset> +#include <ostream> #include <set> #include "TouchedWindow.h" @@ -79,5 +80,7 @@ struct TouchState { std::string dump() const; }; +std::ostream& operator<<(std::ostream& out, const TouchState& state); + } // namespace inputdispatcher } // namespace android diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index 9807a6da9b..ff4b425da3 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -256,5 +256,10 @@ std::string TouchedWindow::dump() const { return out; } +std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) { + out << window.dump(); + return out; +} + } // namespace inputdispatcher } // namespace android diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index 0a38f9f5cd..3f760c0fac 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -20,6 +20,7 @@ #include <input/Input.h> #include <utils/BitSet.h> #include <bitset> +#include <ostream> #include <set> #include "InputTarget.h" @@ -92,5 +93,7 @@ private: static std::string deviceStateToString(const TouchedWindow::DeviceState& state); }; +std::ostream& operator<<(std::ostream& out, const TouchedWindow& window); + } // namespace inputdispatcher } // namespace android diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index b565454805..90bd7c9c74 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -130,7 +130,10 @@ TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext, TouchInputMapper::~TouchInputMapper() {} uint32_t TouchInputMapper::getSources() const { - return mSource; + // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified + // by the external stylus state. That's why we don't add it directly to mSource during + // configuration. + return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0); } void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) { @@ -932,9 +935,6 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (hasStylus()) { mSource |= AINPUT_SOURCE_STYLUS; } - if (hasExternalStylus()) { - mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS; - } } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) { mSource = AINPUT_SOURCE_TOUCH_NAVIGATION; mDeviceMode = DeviceMode::NAVIGATION; @@ -1664,6 +1664,10 @@ std::list<NotifyArgs> TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re mSource, mViewport.displayId, policyFlags, mLastCookedState.buttonState, mCurrentCookedState.buttonState); + if (mCurrentCookedState.cookedPointerData.pointerCount == 0) { + mCurrentStreamModifiedByExternalStylus = false; + } + // Clear some transient state. mCurrentRawState.rawVScroll = 0; mCurrentRawState.rawHScroll = 0; @@ -1715,6 +1719,10 @@ void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) { mExternalStylusButtonsApplied |= pressedButtons; mExternalStylusButtonsApplied &= ~releasedButtons; + + if (mExternalStylusButtonsApplied != 0 || releasedButtons != 0) { + mCurrentStreamModifiedByExternalStylus = true; + } } } @@ -1725,6 +1733,8 @@ void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) { return; } + mCurrentStreamModifiedByExternalStylus = true; + float pressure = lastPointerData.isTouching(*mFusedStylusPointerId) ? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId) .getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) @@ -3821,6 +3831,9 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( ALOG_ASSERT(false); } } + if (mCurrentStreamModifiedByExternalStylus) { + source |= AINPUT_SOURCE_BLUETOOTH_STYLUS; + } const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled && diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index c5dfb00adb..bd9371d263 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -357,6 +357,8 @@ protected: bool mExternalStylusDataPending; // A subset of the buttons in mCurrentRawState that came from an external stylus. int32_t mExternalStylusButtonsApplied{0}; + // True if the current cooked pointer data was modified due to the state of an external stylus. + bool mCurrentStreamModifiedByExternalStylus{false}; // True if we sent a HOVER_ENTER event. bool mSentHoverEnter{false}; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index dc281a3d5f..dd003a6921 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -3349,6 +3349,31 @@ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { } /** + * Test that invalid HOVER events sent by accessibility do not cause a fatal crash. + */ +TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 1200, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + 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. */ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bce0937890..6539593217 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -91,6 +91,9 @@ 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); +static constexpr uint32_t STYLUS_FUSION_SOURCE = + AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS; + // 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. @@ -2308,6 +2311,22 @@ TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) { // ongoing stylus gesture that is being emitted by the touchscreen. using ExternalStylusIntegrationTest = BaseTouchIntegrationTest; +TEST_F(ExternalStylusIntegrationTest, ExternalStylusConnectionChangesTouchscreenSource) { + // Create an external stylus capable of reporting pressure data that + // should be fused with a touch pointer. + std::unique_ptr<UinputExternalStylusWithPressure> stylus = + createUinputDevice<UinputExternalStylusWithPressure>(); + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto stylusInfo = findDeviceByName(stylus->getName()); + ASSERT_TRUE(stylusInfo); + + // Connecting an external stylus changes the source of the touchscreen. + const auto deviceInfo = findDeviceByName(mDevice->getName()); + ASSERT_TRUE(deviceInfo); + ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE)); +} + TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) { const Point centerPoint = mDevice->getCenterPoint(); @@ -2337,17 +2356,17 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) { mDevice->sendDown(centerPoint); mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(ToolType::STYLUS), WithButtonState(0), - WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX)))); + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::STYLUS), + WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), 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(ToolType::STYLUS), WithButtonState(0), - WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithToolType(ToolType::STYLUS), + WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId), + WithPressure(200.f / RAW_PRESSURE_MAX)))); // The external stylus did not generate any events. ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); @@ -2392,8 +2411,8 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { // it shows up as a finger pointer. ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), - WithPressure(1.f)))); + WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), + WithToolType(ToolType::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. @@ -2405,6 +2424,7 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), + WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS), WithToolType(ToolType::FINGER)))); // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus. @@ -2413,9 +2433,9 @@ TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) { mDevice->sendDown(centerPoint); mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType(ToolType::STYLUS), WithButtonState(0), - WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX)))); + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(STYLUS_FUSION_SOURCE), + WithToolType(ToolType::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()); @@ -2447,14 +2467,15 @@ TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) { std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT)); mDevice->sendSync(); ASSERT_NO_FATAL_FAILURE( - mTestListener - ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithToolType( - ToolType::FINGER), - WithButtonState(0), - WithDeviceId(touchscreenId), - WithPressure(1.f)), - waitUntil)); + mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction( + AMOTION_EVENT_ACTION_DOWN), + WithToolType(ToolType::FINGER), + WithSource(AINPUT_SOURCE_TOUCHSCREEN | + AINPUT_SOURCE_STYLUS), + WithButtonState(0), + WithDeviceId(touchscreenId), + WithPressure(1.f)), + waitUntil)); // The external stylus did not generate any events. ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled()); @@ -7567,12 +7588,10 @@ public: 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(ToolType::STYLUS)); + AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)); // The first pointer is withheld. processDown(mapper, 100, 200); @@ -7606,7 +7625,7 @@ protected: processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)))); mStylusState.pressure = 0.f; @@ -7616,8 +7635,10 @@ protected: } void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) { + // When stylus fusion is not successful, events should be reported with the original source. + // In this case, it is from a touchscreen. auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER)); + AllOf(WithSource(AINPUT_SOURCE_TOUCHSCREEN), WithToolType(ToolType::FINGER)); // The first pointer is withheld when an external stylus is connected, // and a timeout is requested. @@ -7657,7 +7678,7 @@ private: TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources()); + ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources()); } TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) { @@ -7674,8 +7695,7 @@ TEST_F(ExternalStylusFusionTest, SuccessfulFusion_TouchFirst) { // before the touch is reported by the touchscreen. TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); + auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)); // The external stylus reports pressure first. It is ignored for now. mStylusState.pressure = 1.f; @@ -7717,8 +7737,7 @@ TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) { TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); + auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)); mStylusState.pressure = 0.8f; processExternalStylusState(mapper); @@ -7779,7 +7798,7 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE), + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)))); ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested()); @@ -7788,7 +7807,7 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) { TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - auto source = WithSource(EXPECTED_SOURCE); + auto source = WithSource(STYLUS_FUSION_SOURCE); mStylusState.pressure = 1.f; mStylusState.toolType = ToolType::ERASER; @@ -7841,8 +7860,7 @@ TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) { TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) { SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus(); - auto toolTypeSource = - AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS)); + auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS)); ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper)); diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp index ed4829abe3..6870d4e539 100644 --- a/services/sensorservice/aidl/fuzzer/Android.bp +++ b/services/sensorservice/aidl/fuzzer/Android.bp @@ -46,7 +46,6 @@ cc_fuzz { "unsigned-integer-overflow", ], }, - address: true, integer_overflow: true, }, diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index 22db247cc9..fe56969884 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -844,10 +844,16 @@ void OutputLayer::applyDeviceLayerRequest(hal::LayerRequest request) { bool OutputLayer::needsFiltering() const { const auto& state = getState(); - const auto& displayFrame = state.displayFrame; const auto& sourceCrop = state.sourceCrop; - return sourceCrop.getHeight() != displayFrame.getHeight() || - sourceCrop.getWidth() != displayFrame.getWidth(); + auto displayFrameWidth = static_cast<float>(state.displayFrame.getWidth()); + auto displayFrameHeight = static_cast<float>(state.displayFrame.getHeight()); + + if (state.bufferTransform & HAL_TRANSFORM_ROT_90) { + std::swap(displayFrameWidth, displayFrameHeight); + } + + return sourceCrop.getHeight() != displayFrameHeight || + sourceCrop.getWidth() != displayFrameWidth; } std::optional<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionSettings() const { diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index 9039d16aeb..630906a5b7 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -1614,5 +1614,20 @@ TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfDisplaySizeDifferentFromSourc EXPECT_TRUE(mOutputLayer.needsFiltering()); } +TEST_F(OutputLayerTest, needsFilteringReturnsFalseIfRotatedDisplaySizeSameAsSourceSize) { + mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200); + mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f}; + mOutputLayer.editState().bufferTransform = Hwc2::Transform::ROT_90; + + EXPECT_FALSE(mOutputLayer.needsFiltering()); +} + +TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfRotatedDisplaySizeDiffersFromSourceSize) { + mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200); + mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f}; + + EXPECT_TRUE(mOutputLayer.needsFiltering()); +} + } // namespace } // namespace android::compositionengine diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 252ba8e753..1faf6a1bcb 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -477,8 +477,12 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool sh void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) { ATRACE_CALL(); - if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) { - mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps); + if (mRefreshRateOverlay) { + if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) { + mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps); + } else { + mRefreshRateOverlay->changeRenderRate(renderFps); + } } } diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h index 422513be9b..1775a7a4a6 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayMode.h +++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h @@ -31,8 +31,7 @@ #include "DisplayHardware/Hal.h" #include "Scheduler/StrongTyping.h" - -#include <com_android_graphics_surfaceflinger_flags.h> +#include "Utils/FlagUtils.h" namespace android { @@ -141,7 +140,7 @@ public: // Peak refresh rate represents the highest refresh rate that can be used // for the presentation. Fps getPeakFps() const { - return flags::vrr_config() && mVrrConfig + return flagutils::vrrConfigEnabled() && mVrrConfig ? Fps::fromPeriodNsecs(mVrrConfig->minFrameIntervalNs) : mVsyncRate; } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index acec630167..dc8694c930 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -405,10 +405,19 @@ std::string RequestedLayerState::getDebugString() const { return debug.str(); } +std::ostream& operator<<(std::ostream& out, const scheduler::LayerInfo::FrameRate& obj) { + out << obj.vote.rate; + out << " " << ftl::enum_string_full(obj.vote.type); + out << " " << ftl::enum_string_full(obj.category); + return out; +} + std::ostream& operator<<(std::ostream& out, const RequestedLayerState& obj) { out << obj.debugName; if (obj.relativeParentId != UNASSIGNED_LAYER_ID) out << " parent=" << obj.parentId; if (!obj.handleAlive) out << " handleNotAlive"; + if (obj.requestedFrameRate.isValid()) + out << " requestedFrameRate: {" << obj.requestedFrameRate << "}"; return out; } @@ -468,12 +477,18 @@ Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const { Rect RequestedLayerState::getBufferCrop() const { // this is the crop rectangle that applies to the buffer // itself (as opposed to the window) - if (!bufferCrop.isEmpty()) { - // if the buffer crop is defined, we use that - return bufferCrop; + if (!bufferCrop.isEmpty() && externalTexture != nullptr) { + // if the buffer crop is defined and there's a valid buffer, intersect buffer size and crop + // since the crop should never exceed the size of the buffer. + Rect sizeAndCrop; + externalTexture->getBounds().intersect(bufferCrop, &sizeAndCrop); + return sizeAndCrop; } else if (externalTexture != nullptr) { // otherwise we use the whole buffer return externalTexture->getBounds(); + } else if (!bufferCrop.isEmpty()) { + // if the buffer crop is defined, we use that + return bufferCrop; } else { // if we don't have a buffer yet, we use an empty/invalid crop return Rect(); diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS index 3270e4c95c..0aee7d497c 100644 --- a/services/surfaceflinger/OWNERS +++ b/services/surfaceflinger/OWNERS @@ -4,6 +4,7 @@ adyabr@google.com alecmouri@google.com chaviw@google.com domlaskowski@google.com +jreck@google.com lpy@google.com pdwilliams@google.com racarr@google.com diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index e918dc9045..be04c09fa3 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -19,6 +19,7 @@ #include "Client.h" #include "Layer.h" #include "RefreshRateOverlay.h" +#include "Utils/FlagUtils.h" #include <SkSurface.h> @@ -249,6 +250,14 @@ void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) { createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } +void RefreshRateOverlay::changeRenderRate(Fps renderFps) { + if (mFeatures.test(Features::RenderRate) && mVsyncRate && flagutils::vrrConfigEnabled()) { + mRenderFps = renderFps; + const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame]; + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); + } +} + void RefreshRateOverlay::animate() { if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return; diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index c0fc79b0ba..ae334e54ef 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -50,6 +50,7 @@ public: void setLayerStack(ui::LayerStack); void setViewport(ui::Size); void changeRefreshRate(Fps, Fps); + void changeRenderRate(Fps); void animate(); bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); } diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index c70ed2c544..21714da0ac 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -111,6 +111,15 @@ DisplayEventReceiver::Event makeHotplug(PhysicalDisplayId displayId, nsecs_t tim return event; } +DisplayEventReceiver::Event makeHotplugError(nsecs_t timestamp, int32_t connectionError) { + DisplayEventReceiver::Event event; + PhysicalDisplayId unusedDisplayId; + event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp}; + event.hotplug.connected = false; + event.hotplug.connectionError = connectionError; + return event; +} + DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t timestamp, uint32_t count, nsecs_t expectedPresentationTime, nsecs_t deadlineTimestamp) { @@ -408,6 +417,13 @@ void EventThread::onHotplugReceived(PhysicalDisplayId displayId, bool connected) mCondition.notify_all(); } +void EventThread::onHotplugConnectionError(int32_t errorCode) { + std::lock_guard<std::mutex> lock(mMutex); + + mPendingEvents.push_back(makeHotplugError(systemTime(), errorCode)); + mCondition.notify_all(); +} + void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) { std::lock_guard<std::mutex> lock(mMutex); @@ -439,11 +455,15 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& lock) { mPendingEvents.pop_front(); 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(); + if (event->hotplug.connectionError == 0) { + if (event->hotplug.connected && !mVSyncState) { + mVSyncState.emplace(event->header.displayId); + } else if (!event->hotplug.connected && mVSyncState && + mVSyncState->displayId == event->header.displayId) { + mVSyncState.reset(); + } + } else { + // Ignore vsync stuff on an error. } } } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 7023445581..576910ed6a 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -111,6 +111,8 @@ public: virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; + virtual void onHotplugConnectionError(int32_t connectionError) = 0; + // called when SF changes the active mode and apps needs to be notified about the change virtual void onModeChanged(const scheduler::FrameRateMode&) = 0; @@ -159,6 +161,8 @@ public: void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; + void onHotplugConnectionError(int32_t connectionError) override; + void onModeChanged(const scheduler::FrameRateMode&) override; void onFrameRateOverridesChanged(PhysicalDisplayId displayId, diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 03844ef183..875bdc84f5 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -116,12 +116,24 @@ LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const { } } + // Vote the small dirty when a layer contains at least HISTORY_SIZE of small dirty updates. + bool isSmallDirty = false; + if (smallDirtyCount >= kNumSmallDirtyThreshold) { + if (mLastSmallDirtyCount >= HISTORY_SIZE) { + isSmallDirty = true; + } else { + mLastSmallDirtyCount++; + } + } else { + mLastSmallDirtyCount = 0; + } + if (isFrequent || isInfrequent) { // 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, - /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold}; + isSmallDirty}; } // If we can't determine whether the layer is frequent or not, we return @@ -324,6 +336,7 @@ LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelec ATRACE_FORMAT_INSTANT("infrequent"); ALOGV("%s is infrequent", mName.c_str()); mLastRefreshRate.infrequent = true; + mLastSmallDirtyCount = 0; // Infrequent layers vote for minimal refresh rate for // battery saving purposes and also to prevent b/135718869. votes.push_back({LayerHistory::LayerVoteType::Min, Fps()}); @@ -334,7 +347,7 @@ LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelec clearHistory(now); } - // Return no vote if the latest frames are small dirty. + // Return no vote if the recent frames are small dirty. if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) { ATRACE_FORMAT_INSTANT("NoVote (small dirty)"); ALOGV("%s is small dirty", mName.c_str()); diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 3b4d8239d2..129b4c44d4 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -357,6 +357,10 @@ private: RefreshRateHistory mRefreshRateHistory; + // This will be accessed from only one thread when counting a layer is frequent or infrequent, + // and to determine whether a layer is in small dirty updating. + mutable int32_t mLastSmallDirtyCount = 0; + mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags; // Shared for all LayerInfo instances diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index e378946f1b..b06723ddab 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -38,6 +38,7 @@ #include "../SurfaceFlingerProperties.h" #include "RefreshRateSelector.h" +#include "Utils/FlagUtils.h" #include <com_android_graphics_surfaceflinger_flags.h> @@ -114,7 +115,7 @@ std::pair<unsigned, unsigned> divisorRange(Fps vsyncRate, Fps peakFps, FpsRange using fps_approx_ops::operator/; // use signed type as `fps / range.max` might be 0 auto start = std::max(1, static_cast<int>(peakFps / range.max) - 1); - if (flags::vrr_config()) { + if (flagutils::vrrConfigEnabled()) { start = std::max(1, static_cast<int>(vsyncRate / std::min(range.max, peakFps, fps_approx_ops::operator<)) - diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 595550bd3d..aa24f565f5 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -347,6 +347,17 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis thread->onHotplugReceived(displayId, connected); } +void Scheduler::onHotplugConnectionError(ConnectionHandle handle, int32_t errorCode) { + android::EventThread* thread; + { + std::lock_guard<std::mutex> lock(mConnectionsLock); + RETURN_IF_INVALID_HANDLE(handle); + thread = mConnections[handle].thread.get(); + } + + thread->onHotplugConnectionError(errorCode); +} + void Scheduler::enableSyntheticVsync(bool enable) { // TODO(b/241285945): Remove connection handles. const ConnectionHandle handle = mAppConnectionHandle; diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index d65df2a65d..822f7cc299 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -161,6 +161,8 @@ public: sp<EventThreadConnection> getEventConnection(ConnectionHandle); void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); + void onHotplugConnectionError(ConnectionHandle, int32_t errorCode); + void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 10768a9b0c..4959e257c3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -155,6 +155,7 @@ #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" #include "Utils/Dumper.h" +#include "Utils/FlagUtils.h" #include "WindowInfosListenerInvoker.h" #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h> @@ -498,7 +499,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mMiscFlagValue = flags::misc1(); mConnectedDisplayFlagValue = flags::connected_display(); mMisc2FlagEarlyBootValue = flags::late_boot_misc2(); - mVrrConfigFlagValue = flags::vrr_config(); } LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() { @@ -1355,8 +1355,7 @@ void SurfaceFlinger::initiateDisplayModeChanges() { continue; } - if (!display->isPoweredOn()) { - // Display is no longer powered on, so abort the mode change. + if (!shouldApplyRefreshRateSelectorPolicy(*display)) { clearDesiredActiveModeState(display); continue; } @@ -3953,8 +3952,9 @@ void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest if (!display) continue; - if (!display->isPoweredOn()) { - ALOGV("%s(%s): Display is powered off", __func__, to_string(displayId).c_str()); + if (ftl::FakeGuard guard(kMainThreadContext); + !shouldApplyRefreshRateSelectorPolicy(*display)) { + ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str()); continue; } @@ -5828,6 +5828,20 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n", pid, uid); } else { + Dumper hwclayersDump = [this](const DumpArgs&, bool, std::string& result) + FTL_FAKE_GUARD(mStateLock) -> void const { + if (mLayerLifecycleManagerEnabled) { + mScheduler + ->schedule([this, &result]() FTL_FAKE_GUARD(kMainThreadContext) + FTL_FAKE_GUARD(mStateLock) { + dumpHwcLayersMinidump(result); + }) + .get(); + } else { + dumpHwcLayersMinidumpLockedLegacy(result); + } + }; + static const std::unordered_map<std::string, Dumper> dumpers = { {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)}, {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)}, @@ -5836,7 +5850,7 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { {"--events"s, dumper(&SurfaceFlinger::dumpEvents)}, {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)}, {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)}, - {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy)}, + {"--hwclayers"s, std::move(hwclayersDump)}, {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)}, {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)}, {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)}, @@ -6404,7 +6418,8 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp StringAppendF(&result, "Misc2FlagValue: %s (%s after boot)\n", mMisc2FlagLateBootValue ? "true" : "false", mMisc2FlagEarlyBootValue == mMisc2FlagLateBootValue ? "stable" : "modified"); - StringAppendF(&result, "VrrConfigFlagValue: %s\n", mVrrConfigFlagValue ? "true" : "false"); + StringAppendF(&result, "VrrConfigFlagValue: %s\n", + flagutils::vrrConfigEnabled() ? "true" : "false"); getRenderEngine().dump(result); @@ -7013,7 +7028,10 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r } case 1041: { // Transaction tracing if (mTransactionTracing) { - if (data.readInt32()) { + int arg = data.readInt32(); + if (arg == -1) { + mTransactionTracing.reset(); + } else if (arg > 0) { // Transaction tracing is always running but allow the user to temporarily // increase the buffer when actively debugging. mTransactionTracing->setBufferSize( @@ -7822,10 +7840,12 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, displayBrightnessNits); } + } - if (requestedDataspace == ui::Dataspace::UNKNOWN) { - renderIntent = state.renderIntent; - } + // Screenshots leaving the device should be colorimetric + if (requestedDataspace == ui::Dataspace::UNKNOWN && + renderArea->getHintForSeamlessTransition()) { + renderIntent = state.renderIntent; } } } @@ -7868,6 +7888,10 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( } } + // Screenshots leaving the device must not dim in gamma space. + const bool dimInGammaSpaceForEnhancedScreenshots = mDimInGammaSpaceForEnhancedScreenshots && + renderArea->getHintForSeamlessTransition(); + std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput( ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine, .colorProfile = colorProfile, @@ -7880,7 +7904,7 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl( .regionSampling = regionSampling, .treat170mAsSrgb = mTreat170mAsSrgb, .dimInGammaSpaceForEnhancedScreenshots = - mDimInGammaSpaceForEnhancedScreenshots}); + dimInGammaSpaceForEnhancedScreenshots}); const float colorSaturation = grayscale ? 0 : 1; compositionengine::CompositionRefreshArgs refreshArgs{ @@ -8026,17 +8050,33 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( break; } - // TODO(b/255635711): Apply the policy once the display is powered on, which is currently only - // done for the internal display that becomes active on fold/unfold. For now, assume that DM - // always powers on the secondary (internal or external) display before setting its policy. - if (!display->isPoweredOn()) { - ALOGV("%s(%s): Display is powered off", __func__, to_string(displayId).c_str()); + if (!shouldApplyRefreshRateSelectorPolicy(*display)) { + ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str()); return NO_ERROR; } return applyRefreshRateSelectorPolicy(displayId, selector); } +bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const { + if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true; + + LOG_ALWAYS_FATAL_IF(display.isVirtual()); + const auto displayId = display.getPhysicalId(); + + // The display is powered off, and this is a multi-display device. If the display is the + // inactive internal display of a dual-display foldable, then the policy will be applied + // when it becomes active upon powering on. + // + // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once + // concurrent mode setting across multiple (potentially powered off) displays is supported. + // + return displayId == mActiveDisplayId || + !mPhysicalDisplays.get(displayId) + .transform(&PhysicalDisplay::isInternal) + .value_or(false); +} + status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) { const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index ef6b8159e4..f9ea2ee25a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -705,6 +705,9 @@ private: const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const + REQUIRES(mStateLock, kMainThreadContext); + // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter. status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId, const scheduler::RefreshRateSelector&, @@ -1457,7 +1460,6 @@ private: bool mConnectedDisplayFlagValue; bool mMisc2FlagEarlyBootValue; bool mMisc2FlagLateBootValue; - bool mVrrConfigFlagValue; }; class SurfaceComposerAIDL : public gui::BnSurfaceComposer { diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp index 474fef89a0..25e768e0ca 100644 --- a/services/surfaceflinger/Tracing/LayerDataSource.cpp +++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp @@ -52,7 +52,7 @@ void LayerDataSource::OnSetup(const LayerDataSource::SetupArgs& args) { mMode = static_cast<LayerTracing::Mode>(config.mode()); } else { mMode = LayerTracing::Mode::MODE_GENERATED; - ALOGV("Received config with unspecified 'mode'. Using 'GENERATED' as default"); + ALOGD("Received config with unspecified 'mode'. Using 'GENERATED' as default"); } mFlags = 0; @@ -62,21 +62,21 @@ void LayerDataSource::OnSetup(const LayerDataSource::SetupArgs& args) { } void LayerDataSource::OnStart(const LayerDataSource::StartArgs&) { - ALOGV("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); + ALOGD("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); if (auto* p = mLayerTracing.load()) { p->onStart(mMode, mFlags); } } void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs&) { - ALOGV("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); + ALOGD("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); if (auto* p = mLayerTracing.load()) { p->onFlush(mMode, mFlags); } } void LayerDataSource::OnStop(const LayerDataSource::StopArgs&) { - ALOGV("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); + ALOGD("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags); if (auto* p = mLayerTracing.load()) { p->onStop(mMode); } diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp index e55b4c2ce8..403e1050d1 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.cpp +++ b/services/surfaceflinger/Tracing/LayerTracing.cpp @@ -36,6 +36,10 @@ LayerTracing::LayerTracing() { LayerDataSource::Initialize(*this); } +LayerTracing::LayerTracing(std::ostream& outStream) : LayerTracing() { + mOutStream = std::ref(outStream); +} + LayerTracing::~LayerTracing() { LayerDataSource::UnregisterLayerTracing(); } @@ -49,10 +53,6 @@ void LayerTracing::setTransactionTracing(TransactionTracing& transactionTracing) mTransactionTracing = &transactionTracing; } -void LayerTracing::setOutputStream(std::ostream& outStream) { - mOutStream = std::ref(outStream); -} - void LayerTracing::onStart(Mode mode, uint32_t flags) { switch (mode) { case Mode::MODE_ACTIVE: { @@ -63,18 +63,17 @@ void LayerTracing::onStart(Mode mode, uint32_t flags) { // taken. Let's manually take a snapshot, so that the trace's first entry will contain // the current layers state. addProtoSnapshotToOstream(mTakeLayersSnapshotProto(flags), Mode::MODE_ACTIVE); - ALOGV("Started active tracing (traced initial snapshot)"); + ALOGD("Started active tracing (traced initial snapshot)"); break; } case Mode::MODE_GENERATED: { - ALOGV("Started generated tracing (waiting for OnFlush event to generated layers)"); + ALOGD("Started generated tracing (waiting for OnFlush event to generated layers)"); break; } case Mode::MODE_DUMP: { - ALOGV("Starting dump tracing (dumping single snapshot)"); auto snapshot = mTakeLayersSnapshotProto(flags); addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP); - ALOGV("Started dump tracing (dumped single snapshot)"); + ALOGD("Started dump tracing (dumped single snapshot)"); break; } default: { @@ -91,19 +90,19 @@ void LayerTracing::onFlush(Mode mode, uint32_t flags) { } if (!mTransactionTracing) { - ALOGV("Skipping layers trace generation (transactions tracing disabled)"); + ALOGD("Skipping layers trace generation (transactions tracing disabled)"); return; } auto transactionTrace = mTransactionTracing->writeToProto(); - LayerTraceGenerator{}.generate(transactionTrace, flags); - ALOGV("Flushed generated tracing"); + LayerTraceGenerator{}.generate(transactionTrace, flags, *this); + ALOGD("Flushed generated tracing"); } void LayerTracing::onStop(Mode mode) { if (mode == Mode::MODE_ACTIVE) { mIsActiveTracingStarted.store(false); - ALOGV("Stopped active tracing"); + ALOGD("Stopped active tracing"); } } diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h index 349cc40073..fe7f06d0f6 100644 --- a/services/surfaceflinger/Tracing/LayerTracing.h +++ b/services/surfaceflinger/Tracing/LayerTracing.h @@ -97,11 +97,11 @@ public: }; LayerTracing(); + LayerTracing(std::ostream&); ~LayerTracing(); void setTakeLayersSnapshotProtoFunction( const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>&); void setTransactionTracing(TransactionTracing&); - void setOutputStream(std::ostream&); // Start event from perfetto data source void onStart(Mode mode, uint32_t flags); diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.cpp b/services/surfaceflinger/Tracing/TransactionDataSource.cpp index 05b89b84ad..6c9ed301ff 100644 --- a/services/surfaceflinger/Tracing/TransactionDataSource.cpp +++ b/services/surfaceflinger/Tracing/TransactionDataSource.cpp @@ -49,26 +49,26 @@ void TransactionDataSource::OnSetup(const TransactionDataSource::SetupArgs& args mMode = static_cast<TransactionTracing::Mode>(config.mode()); } else { mMode = TransactionTracing::Mode::MODE_CONTINUOUS; - ALOGV("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default"); + ALOGD("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default"); } } void TransactionDataSource::OnStart(const StartArgs&) { - ALOGV("Received OnStart event"); + ALOGD("Received OnStart event"); if (auto* p = mTransactionTracing.load()) { p->onStart(mMode); } } void TransactionDataSource::OnFlush(const FlushArgs&) { - ALOGV("Received OnFlush event"); + ALOGD("Received OnFlush event"); if (auto* p = mTransactionTracing.load()) { p->onFlush(mMode); } } void TransactionDataSource::OnStop(const StopArgs&) { - ALOGV("Received OnStop event"); + ALOGD("Received OnStop event"); } TransactionTracing::Mode TransactionDataSource::GetMode() const { diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp index 0517984dab..9d6d87eded 100644 --- a/services/surfaceflinger/Tracing/TransactionTracing.cpp +++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp @@ -70,7 +70,7 @@ void TransactionTracing::onStart(TransactionTracing::Mode mode) { writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_ACTIVE); - ALOGV("Started active mode tracing (wrote initial transactions ring buffer to perfetto)"); + ALOGD("Started active mode tracing (wrote initial transactions ring buffer to perfetto)"); } void TransactionTracing::onFlush(TransactionTracing::Mode mode) { @@ -83,7 +83,7 @@ void TransactionTracing::onFlush(TransactionTracing::Mode mode) { writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_CONTINUOUS); - ALOGV("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto"); + ALOGD("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto"); } void TransactionTracing::writeRingBufferToPerfetto(TransactionTracing::Mode mode) { diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp index 62c362eb2d..23fe8fa886 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp @@ -41,8 +41,7 @@ namespace android { using namespace ftl::flag_operators; bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile, - std::uint32_t traceFlags, - std::optional<std::reference_wrapper<std::ostream>> outStream, + std::uint32_t traceFlags, LayerTracing& layerTracing, bool onlyLastEntry) { if (traceFile.entry_size() == 0) { ALOGD("Trace file is empty"); @@ -51,11 +50,6 @@ bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& TransactionProtoParser parser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()); - LayerTracing layerTracing; - if (outStream) { - layerTracing.setOutputStream(outStream->get()); - } - // frontend frontend::LayerLifecycleManager lifecycleManager; frontend::LayerHierarchyBuilder hierarchyBuilder{{}}; diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h index 2bb6f5129c..e4d02ca5ce 100644 --- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h +++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h @@ -30,7 +30,6 @@ class LayerTracing; class LayerTraceGenerator { public: bool generate(const perfetto::protos::TransactionTraceFile&, std::uint32_t traceFlags, - std::optional<std::reference_wrapper<std::ostream>> outStream = std::nullopt, - bool onlyLastEntry = false); + LayerTracing& layerTracing, bool onlyLastEntry = false); }; } // namespace android diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp index a8ac36a422..65e8479358 100644 --- a/services/surfaceflinger/Tracing/tools/main.cpp +++ b/services/surfaceflinger/Tracing/tools/main.cpp @@ -52,6 +52,8 @@ int main(int argc, char** argv) { (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope"; auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::app}; + auto layerTracing = LayerTracing{outStream}; + const bool generateLastEntryOnly = argc >= 4 && std::string_view(argv[3]) == "--last-entry-only"; @@ -60,7 +62,7 @@ int main(int argc, char** argv) { ALOGD("Generating %s...", outputLayersTracePath); std::cout << "Generating " << outputLayersTracePath << "\n"; - if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, outStream, + if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing, generateLastEntryOnly)) { std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath; return -1; diff --git a/services/surfaceflinger/Utils/FlagUtils.h b/services/surfaceflinger/Utils/FlagUtils.h new file mode 100644 index 0000000000..8435f04573 --- /dev/null +++ b/services/surfaceflinger/Utils/FlagUtils.h @@ -0,0 +1,33 @@ +/** + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <android-base/properties.h> +#include <com_android_graphics_surfaceflinger_flags.h> +#include <string> + +namespace android::flagutils { + +using namespace std::literals::string_literals; +using namespace com::android::graphics::surfaceflinger; + +inline bool vrrConfigEnabled() { + static const bool enable_vrr_config = + base::GetBoolProperty("debug.sf.enable_vrr_config"s, false); + return flags::vrr_config() || enable_vrr_config; +} +} // namespace android::flagutils diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp index b6cd7b8b7f..aeceadb5d4 100644 --- a/services/surfaceflinger/tests/tracing/Android.bp +++ b/services/surfaceflinger/tests/tracing/Android.bp @@ -29,9 +29,6 @@ cc_test { "skia_renderengine_deps", ], test_suites: ["device-tests"], - sanitize: { - address: true, - }, srcs: [ ":libsurfaceflinger_sources", ":libsurfaceflinger_mock_sources", diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp index 7a076347bb..2fcb9e0b48 100644 --- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp +++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp @@ -23,6 +23,7 @@ #include <unordered_map> #include <LayerProtoHelper.h> +#include <Tracing/LayerTracing.h> #include <Tracing/TransactionProtoParser.h> #include <Tracing/tools/LayerTraceGenerator.h> #include <layerproto/LayerProtoHeader.h> @@ -62,7 +63,8 @@ protected: { auto traceFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS; std::ofstream outStream{actualLayersTracePath, std::ios::binary | std::ios::app}; - EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, outStream, + auto layerTracing = LayerTracing{outStream}; + EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, layerTracing, /*onlyLastEntry=*/true)) << "Failed to generate layers trace from " << transactionTracePath; } diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index 32821b7891..d7ac038a84 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -384,6 +384,17 @@ protected: mLifecycleManager.applyTransactions(transactions); } + void setBufferCrop(uint32_t id, const Rect& bufferCrop) { + std::vector<TransactionState> transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged; + transactions.back().states.front().layerId = id; + transactions.back().states.front().state.bufferCrop = bufferCrop; + mLifecycleManager.applyTransactions(transactions); + } + void setDataspace(uint32_t id, ui::Dataspace dataspace) { std::vector<TransactionState> transactions; transactions.emplace_back(); diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index c432ad0a24..7e3e61f6fb 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -1124,8 +1124,8 @@ TEST_F(LayerHistoryTest, smallDirtyInMultiLayer) { LayerHistory::Summary summary; - // layer1 is active but infrequent. - for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + // layer1 is updating small dirty. + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) { auto props = layer1->getLayerProps(); props.isSmallDirty = true; history().record(layer1->getSequence(), props, 0 /*presentTime*/, time, diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 662f4bd466..1a9233d12c 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -846,4 +846,33 @@ TEST_F(LayerSnapshotTest, setRefreshRateIndicatorCompositionType) { aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR); } +TEST_F(LayerSnapshotTest, setBufferCrop) { + // validate no buffer but has crop + Rect crop = Rect(0, 0, 50, 50); + setBufferCrop(1, crop); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop); + + setBuffer(1, + std::make_shared<renderengine::mock::FakeExternalTexture>(100U /*width*/, + 100U /*height*/, + 42ULL /* bufferId */, + HAL_PIXEL_FORMAT_RGBA_8888, + 0 /*usage*/)); + // validate a buffer crop within the buffer bounds + setBufferCrop(1, crop); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop); + + // validate a buffer crop outside the buffer bounds + crop = Rect(0, 0, 150, 150); + setBufferCrop(1, crop); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100)); + + // validate no buffer crop + setBufferCrop(1, Rect()); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100)); +} } // namespace android::surfaceflinger::frontend diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp index 608fa762ef..9899d4290b 100644 --- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp +++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp @@ -19,6 +19,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <gui/FrameRateUtils.h> #include <gui/LayerMetadata.h> // TODO(b/129481165): remove the #pragma below and fix conversion issues @@ -325,48 +326,6 @@ INSTANTIATE_TEST_SUITE_P(PerLayerType, SetFrameRateTest, std::make_shared<EffectLayerFactory>()), PrintToStringParamName); -TEST_F(SetFrameRateTest, ValidateFrameRate) { - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, "")); - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - - // Privileged APIs. - EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - - constexpr bool kPrivileged = true; - EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "", - kPrivileged)); - EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "", - kPrivileged)); - - // Invalid frame rate. - EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, - ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - - // Invalid compatibility. - EXPECT_FALSE( - ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "")); - - // Invalid change frame rate strategy. - EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, "")); - EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, "")); -} - TEST_P(SetFrameRateTest, SetOnParentActivatesTree) { const auto& layerFactory = GetParam(); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 24eb31821a..3a5fdf98de 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -476,6 +476,37 @@ TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { } TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { + EXPECT_TRUE(mDisplay->isPoweredOn()); + EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60)); + + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId90.value(), + false, 0.f, 120.f))); + + EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); + + // Power off the display before the mode has been set. + mDisplay->setPowerMode(hal::PowerMode::OFF); + + const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kInnerDisplayHwcId, + hal::HWConfigId(kModeId90.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // Powering off should not abort the mode set. + EXPECT_FALSE(mDisplay->isPoweredOn()); + EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); + + mFlinger.commit(); + + EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90)); +} + +TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { const auto [innerDisplay, outerDisplay] = injectOuterDisplay(); EXPECT_TRUE(innerDisplay->isPoweredOn()); @@ -516,6 +547,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { mFlinger.commit(); + // Powering off the inactive display should abort the mode set. EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); @@ -523,6 +555,28 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + + innerDisplay->setPowerMode(hal::PowerMode::OFF); + outerDisplay->setPowerMode(hal::PowerMode::ON); + + // Only the outer display is powered on. + mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay); + + EXPECT_CALL(*mComposer, + setActiveConfigWithConstraints(kOuterDisplayHwcId, + hal::HWConfigId(kModeId60.value()), _, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE))); + + mFlinger.commit(); + + // The mode set should resume once the display becomes active. + EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); + EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + + mFlinger.commit(); + + EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index 9a1a16dd82..8e782ebbb1 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -33,6 +33,7 @@ public: (ResyncCallback, EventRegistrationFlags), (const, override)); MOCK_METHOD(void, enableSyntheticVsync, (bool), (override)); MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override)); + MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override)); MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override)); MOCK_METHOD(void, onFrameRateOverridesChanged, (PhysicalDisplayId, std::vector<FrameRateOverride>), (override)); diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h index 8e28a75ed4..11723c7509 100644 --- a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h +++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <android-base/properties.h> #include <gtest/gtest.h> #include <gui/SurfaceComposerClient.h> #include <private/android_filesystem_config.h> @@ -21,7 +22,8 @@ #include <future> namespace android { -using Transaction = SurfaceComposerClient::Transaction; + +using base::HwTimeoutMultiplier; using gui::DisplayInfo; using gui::WindowInfo; @@ -36,7 +38,8 @@ public: auto listener = sp<WindowInfosListener>::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; + bool satisfied = future.wait_for(std::chrono::seconds{5 * HwTimeoutMultiplier()}) == + std::future_status::ready; mClient->removeWindowInfosListener(listener); return satisfied; } diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp index b544245a7a..de4271d47d 100644 --- a/vulkan/vkjson/Android.bp +++ b/vulkan/vkjson/Android.bp @@ -7,8 +7,19 @@ package { default_applicable_licenses: ["frameworks_native_license"], } -cc_library_shared { +cc_defaults { + name: "libvkjson_deps", + shared_libs: [ + "libjsoncpp", + "libvulkan", + ], +} + +cc_library_static { name: "libvkjson", + defaults: [ + "libvkjson_deps", + ], srcs: [ "vkjson.cc", "vkjson_instance.cc", @@ -24,10 +35,6 @@ cc_library_shared { export_include_dirs: [ ".", ], - shared_libs: [ - "libjsoncpp", - "libvulkan", - ], export_shared_lib_headers: [ "libvulkan", ], |