Merge "Fix loading VVLs by surfaceflinger service" into main
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index fc0801c..3e6d2e0 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -93,6 +93,10 @@
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/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 4081514..a401838 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -18,6 +18,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <binder/BpBinder.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
@@ -117,10 +118,26 @@
});
if (!found) {
+ std::set<std::string> instances;
+ forEachManifest([&](const ManifestWithDescription& mwd) {
+ std::set<std::string> res = mwd.manifest->getAidlInstances(aname.package, aname.iface);
+ instances.insert(res.begin(), res.end());
+ return true;
+ });
+
+ std::string available;
+ if (instances.empty()) {
+ available = "No alternative instances declared in VINTF";
+ } else {
+ // for logging only. We can't return this information to the client
+ // because they may not have permissions to find or list those
+ // instances
+ available = "VINTF declared instances: " + base::Join(instances, ", ");
+ }
// Although it is tested, explicitly rebuilding qualified name, in case it
// becomes something unexpected.
- ALOGI("Could not find %s.%s/%s in the VINTF manifest.", aname.package.c_str(),
- aname.iface.c_str(), aname.instance.c_str());
+ ALOGI("Could not find %s.%s/%s in the VINTF manifest. %s.", aname.package.c_str(),
+ aname.iface.c_str(), aname.instance.c_str(), available.c_str());
}
return found;
diff --git a/include/ftl/OWNERS b/include/ftl/OWNERS
new file mode 100644
index 0000000..3f61292
--- /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 075d12b..2c86e2e 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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/include/input/InputVerifier.h b/include/input/InputVerifier.h
index b857482..14dd463 100644
--- a/include/input/InputVerifier.h
+++ b/include/input/InputVerifier.h
@@ -46,7 +46,7 @@
public:
InputVerifier(const std::string& name);
- android::base::Result<void> processMovement(int32_t deviceId, int32_t action,
+ android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action,
uint32_t pointerCount,
const PointerProperties* pointerProperties,
const PointerCoords* pointerCoords, int32_t flags);
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 589df9a..ef0ae4f 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -54,6 +54,11 @@
// 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 @@
}
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 @@
}
}
}
+ --sBinderProxyCount;
if (ipc) {
ipc->expungeHandle(binderHandle(), this);
@@ -688,6 +706,11 @@
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 5496d61..a5c6094 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -87,6 +87,7 @@
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 @@
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 76c7aac..4786c89 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 8ee396e..8fb755c 100644
--- a/libs/binder/ndk/tests/Android.bp
+++ b/libs/binder/ndk/tests/Android.bp
@@ -80,6 +80,28 @@
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 3724fa1..15708ca 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,22 +432,6 @@
EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
}
-// TEST(NdkBinder, IsUpdatable) {
-// bool isUpdatable =
-// AServiceManager_isUpdatableViaApex("android.hardware.light.ILights/default");
-// EXPECT_EQ(isUpdatable, true);
-// }
-//
-// 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_NE(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 0000000..0a3021d
--- /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 e021af0..0396869 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -1441,6 +1441,36 @@
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/parcel_fuzzer/random_fd.cpp b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
index e4dbb2d..7390d49 100644
--- a/libs/binder/tests/parcel_fuzzer/random_fd.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
@@ -29,40 +29,65 @@
const char* fdType;
std::vector<unique_fd> fds = provider->PickValueInArray<
- std::function<std::vector<unique_fd>()>>({
- [&]() {
- fdType = "ashmem";
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(
- ashmem_create_region("binder test region",
- provider->ConsumeIntegralInRange<size_t>(0, 4096))));
- return ret;
- },
- [&]() {
- fdType = "/dev/null";
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
- return ret;
- },
- [&]() {
- fdType = "pipefd";
+ std::function<std::vector<unique_fd>()>>(
+ {[&]() {
+ fdType = "ashmem";
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(
+ ashmem_create_region("binder test region",
+ provider->ConsumeIntegralInRange<size_t>(0, 4096))));
+ return ret;
+ },
+ [&]() {
+ fdType = "/dev/null";
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
+ return ret;
+ },
+ [&]() {
+ fdType = "pipefd";
- int pipefds[2];
+ int pipefds[2];
- int flags = O_CLOEXEC;
- if (provider->ConsumeBool()) flags |= O_DIRECT;
- if (provider->ConsumeBool()) flags |= O_NONBLOCK;
+ int flags = O_CLOEXEC;
+ if (provider->ConsumeBool()) flags |= O_DIRECT;
+ if (provider->ConsumeBool()) flags |= O_NONBLOCK;
- CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
+ CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
- if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
+ if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(pipefds[0]));
- ret.push_back(unique_fd(pipefds[1]));
- return ret;
- },
- })();
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(pipefds[0]));
+ ret.push_back(unique_fd(pipefds[1]));
+ return ret;
+ },
+ [&]() {
+ fdType = "tempfd";
+ char name[PATH_MAX];
+#if defined(__ANDROID__)
+ snprintf(name, sizeof(name), "/data/local/tmp/android-tempfd-test-%d-XXXXXX",
+ getpid());
+#else
+ snprintf(name, sizeof(name), "/tmp/android-tempfd-test-%d-XXXXXX", getpid());
+#endif
+ int fd = mkstemp(name);
+ CHECK_NE(fd, -1) << "Failed to create file " << name << ", errno: " << errno;
+ unlink(name);
+ if (provider->ConsumeBool()) {
+ CHECK_NE(TEMP_FAILURE_RETRY(
+ ftruncate(fd,
+ provider->ConsumeIntegralInRange<size_t>(0, 4096))),
+ -1)
+ << "Failed to truncate file, errno: " << errno;
+ }
+
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(fd));
+ return ret;
+ }
+
+ })();
for (const auto& fd : fds) CHECK(fd.ok()) << fd.get() << " " << fdType;
diff --git a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
index 09cb216..b80ac53 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 0000000..0ecf94c
--- /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 0000000..872193c
--- /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 0000000..67b95a5
--- /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 0000000..67910a1
--- /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 0000000..34e0eb4
--- /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 0000000..07d5da9
--- /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 0000000..2b068d1
--- /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 0000000..79fb331
--- /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 0000000..6f3b755
--- /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 0000000..6f3b755
--- /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
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
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
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
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 0000000..f8c6127
--- /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 0000000..e652102
--- /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 ff95148..7fcb222 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_defaults {
+ name: "libbufferstreams_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libnativewindow_rs",
+ ],
+ edition: "2021",
+}
+
rust_library {
name: "libbufferstreams",
crate_name: "bufferstreams",
- srcs: ["src/lib.rs"],
- edition: "2021",
- rlibs: [
- "libnativewindow_rs",
- ],
+ 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 1d321c8..87f3104 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 @@
///
/// 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 @@
///
/// 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 @@
///
/// 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 @@
/// 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 0000000..2fd518e
--- /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 0000000..1593b18
--- /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 0000000..d0c621b
--- /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 0000000..dd038c6
--- /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 0000000..46c58dc
--- /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 0000000..b7e9705
--- /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 0000000..e046dbb
--- /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 0000000..90275c7
--- /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 f835997..9df5632 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 0000000..3f61292
--- /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 5592a01..b68c2c3 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@
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::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_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 @@
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 @@
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/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 4842476..ed5d5c1 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -63,8 +63,7 @@
namespace {
static bool isVndkEnabled() {
#ifdef __BIONIC__
- // TODO(b/290159430) Use ro.vndk.version to check if VNDK is enabled instead
- static bool isVndkEnabled = !android::base::GetBoolProperty("ro.vndk.deprecate", false);
+ static bool isVndkEnabled = android::base::GetProperty("ro.vndk.version", "") != "";
return isVndkEnabled;
#endif
return false;
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 9a27d23..f17a654 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -200,6 +200,7 @@
cc_aconfig_library {
name: "libguiflags",
+ host_supported: true,
vendor_available: true,
aconfig_declarations: "libgui_flags",
}
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 46fb068..93df124 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -324,6 +324,12 @@
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 @@
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 8a88377..5dd058c 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -173,7 +173,13 @@
*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/LayerState.cpp b/libs/gui/LayerState.cpp
index 9847c05..613721e 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -85,6 +85,7 @@
changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+ frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
fixedTransformHint(ui::Transform::ROT_INVALID),
autoRefresh(false),
isTrustedOverlay(false),
@@ -161,6 +162,7 @@
SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
SAFE_PARCEL(output.writeByte, frameRateCategory);
+ SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
SAFE_PARCEL(output.writeUint32, fixedTransformHint);
SAFE_PARCEL(output.writeBool, autoRefresh);
SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -294,6 +296,7 @@
SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
SAFE_PARCEL(input.readByte, &frameRateCategory);
+ SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
SAFE_PARCEL(input.readUint32, &tmpUint32);
fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -667,6 +670,10 @@
what |= eFrameRateCategoryChanged;
frameRateCategory = other.frameRateCategory;
}
+ if (other.what & eFrameRateSelectionStrategyChanged) {
+ what |= eFrameRateSelectionStrategyChanged;
+ frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+ }
if (other.what & eFixedTransformHintChanged) {
what |= eFixedTransformHintChanged;
fixedTransformHint = other.fixedTransformHint;
@@ -778,6 +785,7 @@
CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
changeFrameRateStrategy);
CHECK_DIFF(diff, eFrameRateCategoryChanged, other, frameRateCategory);
+ CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index e0882ac..8a57f92 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1227,7 +1227,7 @@
flags |= ISurfaceComposer::eEarlyWakeupEnd;
}
- sp<IBinder> applyToken = mApplyToken ? mApplyToken : sApplyToken;
+ sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
@@ -1249,11 +1249,15 @@
sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
+std::mutex SurfaceComposerClient::Transaction::sApplyTokenMutex;
+
sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() {
+ std::scoped_lock lock{sApplyTokenMutex};
return sApplyToken;
}
void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
+ std::scoped_lock lock{sApplyTokenMutex};
sApplyToken = applyToken;
}
@@ -2105,6 +2109,19 @@
return *this;
}
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+ int8_t strategy) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+ s->frameRateSelectionStrategy = strategy;
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
index 6e4f074..0d2a52b 100644
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -62,7 +62,10 @@
}
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 177d5f8..bdf8856 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -197,6 +197,7 @@
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/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 1df9b11..9fef512 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -110,6 +110,7 @@
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 @@
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 140efa6..fe2dd20 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -53,6 +53,9 @@
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 7fd6c35..79582ce 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -88,6 +88,7 @@
struct Hotplug {
bool connected;
+ int32_t connectionError __attribute__((aligned(4)));
};
struct ModeChange {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 102a3c1..4371007 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -197,7 +197,7 @@
eInputInfoChanged = 0x40000000,
eCornerRadiusChanged = 0x80000000,
eDestinationFrameChanged = 0x1'00000000,
- /* unused = 0x2'00000000, */
+ eFrameRateSelectionStrategyChanged = 0x2'00000000,
eBackgroundColorChanged = 0x4'00000000,
eMetadataChanged = 0x8'00000000,
eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -268,6 +268,7 @@
layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+ layer_state_t::eFrameRateSelectionStrategyChanged |
layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
// Changes affecting data sent to input.
@@ -361,6 +362,9 @@
// Frame rate category to suggest what frame rate range a surface should run.
int8_t frameRateCategory;
+ // Strategy of the layer for frame rate selection.
+ int8_t frameRateSelectionStrategy;
+
// Set by window manager indicating the layer and all its children are
// in a different orientation than the display. The hint suggests that
// the graphic producers should receive a transform hint as if the
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 6fef5d2..26b1fbd 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -422,6 +422,7 @@
class Transaction : public Parcelable {
private:
static sp<IBinder> sApplyToken;
+ static std::mutex sApplyTokenMutex;
void releaseBufferIfOverwriting(const layer_state_t& state);
static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
@@ -687,6 +688,8 @@
Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category);
+ Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
// Set by window manager indicating the layer and all its children are
// in a different orientation than the display. The hint suggests that
// the graphic producers should receive a transform hint as if the
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 3949d70..29eeaa8 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -59,6 +59,7 @@
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/input/Android.bp b/libs/input/Android.bp
index 36a01d3..ab4af1a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -90,6 +90,28 @@
"--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
"--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
"--allowlist-var=MAX_POINTER_ID",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
+ "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
+ "--allowlist-var=AINPUT_SOURCE_DPAD",
+ "--allowlist-var=AINPUT_SOURCE_GAMEPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE",
+ "--allowlist-var=AINPUT_SOURCE_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_HDMI",
+ "--allowlist-var=AINPUT_SOURCE_SENSOR",
+ "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
],
static_libs: [
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 1600013..30cedb0 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -632,8 +632,8 @@
MotionEvent::actionToString(action).c_str()));
if (verifyEvents()) {
Result<void> result =
- mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties,
- pointerCoords, flags);
+ mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+ pointerProperties, pointerCoords, flags);
if (!result.ok()) {
LOG(FATAL) << "Bad stream: " << result.error();
}
@@ -1276,13 +1276,13 @@
PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
const PointerCoords& currentCoords = current->getPointerById(id);
resampledCoords = currentCoords;
+ resampledCoords.isResampled = true;
if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
const PointerCoords& otherCoords = other->getPointerById(id);
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
lerp(currentCoords.getX(), otherCoords.getX(), alpha));
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
lerp(currentCoords.getY(), otherCoords.getY(), alpha));
- resampledCoords.isResampled = true;
ALOGD_IF(debugResampling(),
"[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
"other (%0.3f, %0.3f), alpha %0.3f",
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index 6c602e0..cec2445 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -33,7 +33,7 @@
InputVerifier::InputVerifier(const std::string& name)
: mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
-Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t action,
+Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
uint32_t pointerCount,
const PointerProperties* pointerProperties,
const PointerCoords* pointerCoords, int32_t flags) {
@@ -43,8 +43,8 @@
}
rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
rust::String errorMessage =
- android::input::verifier::process_movement(*mVerifier, deviceId, action, properties,
- static_cast<uint32_t>(flags));
+ android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
+ properties, static_cast<uint32_t>(flags));
if (errorMessage.empty()) {
return {};
} else {
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c8d1da7..412931b 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -205,6 +205,13 @@
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/input_flags.aconfig b/libs/input/input_flags.aconfig
index a0563f9..e8575a6 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -27,3 +27,10 @@
description: "Set to true to enable timer support for the touchpad Gestures library"
bug: "297192727"
}
+
+flag {
+ name: "enable_multi_device_input"
+ namespace: "input"
+ description: "Set to true to enable multi-device input: touch and stylus can be active at the same time, but in different windows"
+ bug: "211379801"
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 9d3b386..9725b00 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -23,6 +23,54 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DeviceId(pub i32);
+#[repr(u32)]
+pub enum SourceClass {
+ None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
+ Button = input_bindgen::AINPUT_SOURCE_CLASS_BUTTON,
+ Pointer = input_bindgen::AINPUT_SOURCE_CLASS_POINTER,
+ Navigation = input_bindgen::AINPUT_SOURCE_CLASS_NAVIGATION,
+ Position = input_bindgen::AINPUT_SOURCE_CLASS_POSITION,
+ Joystick = input_bindgen::AINPUT_SOURCE_CLASS_JOYSTICK,
+}
+
+bitflags! {
+ /// Source of the input device or input events.
+ pub struct Source: u32 {
+ /// SOURCE_UNKNOWN
+ const Unknown = input_bindgen::AINPUT_SOURCE_UNKNOWN;
+ /// SOURCE_KEYBOARD
+ const Keyboard = input_bindgen::AINPUT_SOURCE_KEYBOARD;
+ /// SOURCE_DPAD
+ const Dpad = input_bindgen::AINPUT_SOURCE_DPAD;
+ /// SOURCE_GAMEPAD
+ const Gamepad = input_bindgen::AINPUT_SOURCE_GAMEPAD;
+ /// SOURCE_TOUCHSCREEN
+ const Touchscreen = input_bindgen::AINPUT_SOURCE_TOUCHSCREEN;
+ /// SOURCE_MOUSE
+ const Mouse = input_bindgen::AINPUT_SOURCE_MOUSE;
+ /// SOURCE_STYLUS
+ const Stylus = input_bindgen::AINPUT_SOURCE_STYLUS;
+ /// SOURCE_BLUETOOTH_STYLUS
+ const BluetoothStylus = input_bindgen::AINPUT_SOURCE_BLUETOOTH_STYLUS;
+ /// SOURCE_TRACKBALL
+ const Trackball = input_bindgen::AINPUT_SOURCE_TRACKBALL;
+ /// SOURCE_MOUSE_RELATIVE
+ const MouseRelative = input_bindgen::AINPUT_SOURCE_MOUSE_RELATIVE;
+ /// SOURCE_TOUCHPAD
+ const Touchpad = input_bindgen::AINPUT_SOURCE_TOUCHPAD;
+ /// SOURCE_TOUCH_NAVIGATION
+ const TouchNavigation = input_bindgen::AINPUT_SOURCE_TOUCH_NAVIGATION;
+ /// SOURCE_JOYSTICK
+ const Joystick = input_bindgen::AINPUT_SOURCE_JOYSTICK;
+ /// SOURCE_HDMI
+ const HDMI = input_bindgen::AINPUT_SOURCE_HDMI;
+ /// SOURCE_SENSOR
+ const Sensor = input_bindgen::AINPUT_SOURCE_SENSOR;
+ /// SOURCE_ROTARY_ENCODER
+ const RotaryEncoder = input_bindgen::AINPUT_SOURCE_ROTARY_ENCODER;
+ }
+}
+
/// A rust enum representation of a MotionEvent action.
#[repr(u32)]
pub enum MotionAction {
@@ -134,3 +182,11 @@
const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
}
}
+
+impl Source {
+ /// Return true if this source is from the provided class
+ pub fn is_from_class(&self, source_class: SourceClass) -> bool {
+ let class_bits = source_class as u32;
+ self.bits() & class_bits == class_bits
+ }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index 64c0466..2d94e70 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,7 +17,7 @@
//! Contains the InputVerifier, used to validate a stream of input events.
use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionFlags};
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
use log::info;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -51,10 +51,15 @@
pub fn process_movement(
&mut self,
device_id: DeviceId,
+ source: Source,
action: u32,
pointer_properties: &[RustPointerProperties],
flags: MotionFlags,
) -> Result<(), String> {
+ if !source.is_from_class(SourceClass::Pointer) {
+ // Skip non-pointer sources like MOUSE_RELATIVE for now
+ return Ok(());
+ }
if self.should_log {
info!(
"Processing {} for device {:?} ({} pointer{}) on {}",
@@ -68,6 +73,13 @@
match action.into() {
MotionAction::Down => {
+ if pointer_properties.len() != 1 {
+ return Err(format!(
+ "{}: Invalid DOWN event: there are {} pointers in the event",
+ self.name,
+ pointer_properties.len()
+ ));
+ }
let it = self
.touching_pointer_ids_by_device
.entry(device_id)
@@ -90,10 +102,19 @@
));
}
let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != pointer_properties.len() - 1 {
+ return Err(format!(
+ "{}: There are currently {} touching pointers, but the incoming \
+ POINTER_DOWN event has {}",
+ self.name,
+ it.len(),
+ pointer_properties.len()
+ ));
+ }
let pointer_id = pointer_properties[action_index].id;
if it.contains(&pointer_id) {
return Err(format!(
- "{}: Pointer with id={} not found in the properties",
+ "{}: Pointer with id={} already present found in the properties",
self.name, pointer_id
));
}
@@ -108,11 +129,10 @@
}
}
MotionAction::PointerUp { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
return Err(format!(
- "{}: Received POINTER_UP but no pointers are currently down for device \
- {:?}",
- self.name, device_id
+ "{}: ACTION_POINTER_UP touching pointers don't match",
+ self.name
));
}
let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
@@ -120,6 +140,13 @@
it.remove(&pointer_id);
}
MotionAction::Up => {
+ if pointer_properties.len() != 1 {
+ return Err(format!(
+ "{}: Invalid UP event: there are {} pointers in the event",
+ self.name,
+ pointer_properties.len()
+ ));
+ }
if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
return Err(format!(
"{} Received ACTION_UP but no pointers are currently down for device {:?}",
@@ -246,6 +273,7 @@
use crate::DeviceId;
use crate::MotionFlags;
use crate::RustPointerProperties;
+ use crate::Source;
#[test]
fn single_pointer_stream() {
let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
@@ -253,6 +281,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
&pointer_properties,
MotionFlags::empty(),
@@ -261,6 +290,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
&pointer_properties,
MotionFlags::empty(),
@@ -269,6 +299,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
&pointer_properties,
MotionFlags::empty(),
@@ -283,6 +314,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
&pointer_properties,
MotionFlags::empty(),
@@ -291,6 +323,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
&pointer_properties,
MotionFlags::empty(),
@@ -299,6 +332,7 @@
assert!(verifier
.process_movement(
DeviceId(2),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
&pointer_properties,
MotionFlags::empty(),
@@ -307,6 +341,7 @@
assert!(verifier
.process_movement(
DeviceId(2),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_MOVE,
&pointer_properties,
MotionFlags::empty(),
@@ -315,6 +350,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
&pointer_properties,
MotionFlags::empty(),
@@ -329,6 +365,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
&pointer_properties,
MotionFlags::empty(),
@@ -337,6 +374,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
&pointer_properties,
MotionFlags::CANCELED,
@@ -351,6 +389,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_DOWN,
&pointer_properties,
MotionFlags::empty(),
@@ -359,6 +398,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
&pointer_properties,
MotionFlags::empty(), // forgot to set FLAG_CANCELED
@@ -373,6 +413,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_UP,
&pointer_properties,
MotionFlags::empty(),
@@ -387,6 +428,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
&pointer_properties,
MotionFlags::empty(),
@@ -396,6 +438,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
&pointer_properties,
MotionFlags::empty(),
@@ -405,6 +448,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
&pointer_properties,
MotionFlags::empty(),
@@ -414,6 +458,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
&pointer_properties,
MotionFlags::empty(),
@@ -428,6 +473,7 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
&pointer_properties,
MotionFlags::empty(),
@@ -437,10 +483,28 @@
assert!(verifier
.process_movement(
DeviceId(1),
+ Source::Touchscreen,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
&pointer_properties,
MotionFlags::empty(),
)
.is_err());
}
+
+ // Send a MOVE without a preceding DOWN event. This is OK because it's from source
+ // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
+ #[test]
+ fn relative_mouse_move() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::MouseRelative,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 1d3c434..01d9599 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -19,7 +19,7 @@
mod input;
mod input_verifier;
-pub use input::{DeviceId, MotionAction, MotionFlags};
+pub use input::{DeviceId, MotionAction, MotionFlags, Source};
pub use input_verifier::InputVerifier;
#[cxx::bridge(namespace = "android::input")]
@@ -51,6 +51,7 @@
fn process_movement(
verifier: &mut InputVerifier,
device_id: i32,
+ source: u32,
action: u32,
pointer_properties: &[RustPointerProperties],
flags: u32,
@@ -73,12 +74,14 @@
fn process_movement(
verifier: &mut InputVerifier,
device_id: i32,
+ source: u32,
action: u32,
pointer_properties: &[RustPointerProperties],
flags: u32,
) -> String {
let result = verifier.process_movement(
DeviceId(device_id),
+ Source::from_bits(source).unwrap(),
action,
pointer_properties,
MotionFlags::from_bits(flags).unwrap(),
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 3ecf8ee..06b841b 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -25,7 +25,22 @@
namespace android {
-constexpr static float EPSILON = MotionEvent::ROUNDING_PRECISION;
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t POINTER_1_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+struct Pointer {
+ int32_t id;
+ float x;
+ float y;
+ bool isResampled = false;
+};
+
+} // namespace
class InputPublisherAndConsumerTest : public testing::Test {
protected:
@@ -46,12 +61,28 @@
mConsumer = std::make_unique<InputConsumer>(mClientChannel);
}
- void PublishAndConsumeKeyEvent();
- void PublishAndConsumeMotionEvent();
- void PublishAndConsumeFocusEvent();
- void PublishAndConsumeCaptureEvent();
- void PublishAndConsumeDragEvent();
- void PublishAndConsumeTouchModeEvent();
+ void publishAndConsumeKeyEvent();
+ void publishAndConsumeMotionStream();
+ void publishAndConsumeFocusEvent();
+ void publishAndConsumeCaptureEvent();
+ void publishAndConsumeDragEvent();
+ void publishAndConsumeTouchModeEvent();
+ void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+ const std::vector<Pointer>& pointers);
+
+private:
+ // The sequence number to use when publishing the next event
+ uint32_t mSeq = 1;
+
+ void publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+ int32_t metaState, int32_t buttonState, MotionClassification classification,
+ float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
+ float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
+ float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
+ nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords);
};
TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -63,10 +94,10 @@
mConsumer->getChannel()->getConnectionToken());
}
-void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() {
status_t status;
- constexpr uint32_t seq = 15;
+ const uint32_t seq = mSeq++;
int32_t eventId = InputEvent::nextId();
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
@@ -132,20 +163,43 @@
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
- status_t status;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() {
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
- constexpr uint32_t seq = 15;
- int32_t eventId = InputEvent::nextId();
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
constexpr std::array<uint8_t, 32> hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
- constexpr int32_t action = AMOTION_EVENT_ACTION_MOVE;
constexpr int32_t actionButton = 0;
- constexpr int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ const size_t pointerCount = pointers.size();
constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
@@ -162,20 +216,21 @@
constexpr float yPrecision = 0.5;
constexpr float xCursorPosition = 1.3;
constexpr float yCursorPosition = 50.6;
- constexpr nsecs_t downTime = 3;
- constexpr size_t pointerCount = 3;
- constexpr nsecs_t eventTime = 4;
- const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
- PointerProperties pointerProperties[pointerCount];
- PointerCoords pointerCoords[pointerCount];
+
+ const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties.push_back({});
pointerProperties[i].clear();
- pointerProperties[i].id = (i + 2) % pointerCount;
+ pointerProperties[i].id = pointers[i].id;
pointerProperties[i].toolType = ToolType::FINGER;
+ pointerCoords.push_back({});
pointerCoords[i].clear();
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
+ pointerCoords[i].isResampled = pointers[i].isResampled;
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
@@ -185,18 +240,40 @@
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
}
+ publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
+ edgeFlags, metaState, buttonState, classification, xScale, yScale,
+ xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
+ yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
+ downTime, eventTime, pointerProperties, pointerCoords);
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+ int32_t buttonState, MotionClassification classification, float xScale, float yScale,
+ float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
+ float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
+ nsecs_t downTime, nsecs_t eventTime,
+ const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords) {
+ const uint32_t seq = mSeq++;
+ const int32_t eventId = InputEvent::nextId();
ui::Transform transform;
transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
ui::Transform rawTransform;
rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+
+ status_t status;
+ ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
+ const size_t pointerCount = pointerProperties.size();
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
actionButton, flags, edgeFlags, metaState, buttonState,
classification, transform, xPrecision, yPrecision,
xCursorPosition, yCursorPosition, rawTransform,
- downTime, eventTime, pointerCount, pointerProperties,
- pointerCoords);
- ASSERT_EQ(OK, status)
- << "publisher publishMotionEvent should return OK";
+ downTime, eventTime, pointerCount,
+ pointerProperties.data(), pointerCoords.data());
+ ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
uint32_t consumeSeq;
InputEvent* event;
@@ -280,7 +357,7 @@
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -321,7 +398,7 @@
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeCaptureEvent() {
status_t status;
constexpr uint32_t seq = 42;
@@ -361,7 +438,7 @@
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeDragEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -405,7 +482,7 @@
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeTouchModeEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -462,27 +539,27 @@
}
TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
}
TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishCaptureEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishDragEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
@@ -546,17 +623,29 @@
}
TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
} // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 655de80..1cb7f7b 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -27,10 +27,13 @@
namespace android {
+namespace {
+
struct Pointer {
int32_t id;
float x;
float y;
+ ToolType toolType = ToolType::FINGER;
bool isResampled = false;
};
@@ -40,6 +43,8 @@
int32_t action;
};
+} // namespace
+
class TouchResamplingTest : public testing::Test {
protected:
std::unique_ptr<InputPublisher> mPublisher;
@@ -99,7 +104,7 @@
properties.push_back({});
properties.back().clear();
properties.back().id = pointer.id;
- properties.back().toolType = ToolType::FINGER;
+ properties.back().toolType = pointer.toolType;
coords.push_back({});
coords.back().clear();
@@ -292,6 +297,48 @@
}
/**
+ * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
+ * a resampled timestamp and should be marked as such.
+ */
+TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+ std::chrono::nanoseconds frameTime;
+ std::vector<InputEventEntry> entries, expectedEntries;
+
+ // Initial ACTION_DOWN should be separate, because the first consume event will only return
+ // InputEvent with a single action.
+ entries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 5ms;
+ expectedEntries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ entries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 35ms;
+ expectedEntries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ // A resampled event is generated, but the stylus coordinates are not resampled.
+ {25ms,
+ {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
* Event should not be resampled when sample time is equal to event time.
*/
TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
@@ -544,13 +591,13 @@
// First pointer id=0 leaves the screen
entries = {
// id x y
- {80ms, {{1, 600, 600}}, actionPointer0Up},
+ {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
};
publishInputEventEntries(entries);
frameTime = 90ms;
expectedEntries = {
// id x y
- {80ms, {{1, 600, 600}}, actionPointer0Up},
+ {80ms, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
// no resampled event for ACTION_POINTER_UP
};
consumeInputEventEntries(expectedEntries, frameTime);
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 8d8a2bc..342f5de 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -73,6 +73,8 @@
"libGLESv2",
],
+ static_libs: ["libguiflags"],
+
export_header_lib_headers: ["jni_headers"],
header_libs: [
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 0f119f3..32fb350 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,6 +19,7 @@
#include <android/hardware_buffer.h>
#include <gui/BufferQueueDefs.h>
#include <gui/ConsumerBase.h>
+#include <gui/Flags.h>
#include <gui/IGraphicBufferProducer.h>
#include <sys/cdefs.h>
#include <system/graphics.h>
@@ -290,6 +291,20 @@
*/
void releaseConsumerOwnership();
+ /**
+ * Interface for SurfaceTexture callback(s).
+ */
+ struct SurfaceTextureListener : public RefBase {
+ virtual void onFrameAvailable(const BufferItem& item) = 0;
+ virtual void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) = 0;
+ };
+
+ /**
+ * setSurfaceTextureListener registers a SurfaceTextureListener.
+ */
+ void setSurfaceTextureListener(const sp<SurfaceTextureListener>&);
+
protected:
/**
* abandonLocked overrides the ConsumerBase method to clear
@@ -335,6 +350,14 @@
void computeCurrentTransformMatrixLocked();
/**
+ * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
+ */
+#if FLAG_BQ_SET_FRAME_RATE
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
+
+ /**
* The default consumer usage flags that SurfaceTexture always sets on its
* BufferQueue instance; these will be OR:d with any additional flags passed
* from the SurfaceTexture user. In particular, SurfaceTexture will always
@@ -465,8 +488,30 @@
*/
ImageConsumer mImageConsumer;
+ /**
+ * mSurfaceTextureListener holds the registered SurfaceTextureListener.
+ * Note that SurfaceTexture holds the lister with an sp<>, which means that the listener
+ * must only hold a wp<> to SurfaceTexture and not an sp<>.
+ */
+ sp<SurfaceTextureListener> mSurfaceTextureListener;
+
friend class ImageConsumer;
friend class EGLConsumer;
+
+private:
+ // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
+ // is extending ConsumerBase which also implements FrameAvailableListener.
+ class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
+ public:
+ FrameAvailableListenerProxy(const wp<SurfaceTextureListener>& listener)
+ : mSurfaceTextureListener(listener) {}
+
+ private:
+ void onFrameAvailable(const BufferItem& item) override;
+
+ const wp<SurfaceTextureListener> mSurfaceTextureListener;
+ };
+ sp<FrameAvailableListenerProxy> mFrameAvailableListenerProxy;
};
// ----------------------------------------------------------------------------
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 9f610e1..c2535e0 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -23,6 +23,8 @@
#include <system/window.h>
#include <utils/Trace.h>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
// Macros for including the SurfaceTexture name in log messages
@@ -491,4 +493,42 @@
return buffer;
}
+void SurfaceTexture::setSurfaceTextureListener(
+ const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
+ SFT_LOGV("setSurfaceTextureListener");
+
+ Mutex::Autolock _l(mMutex);
+ mSurfaceTextureListener = listener;
+ if (mSurfaceTextureListener != nullptr) {
+ mFrameAvailableListenerProxy =
+ sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
+ setFrameAvailableListener(mFrameAvailableListenerProxy);
+ } else {
+ mFrameAvailableListenerProxy.clear();
+ }
+}
+
+void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
+ const auto listener = mSurfaceTextureListener.promote();
+ if (listener) {
+ listener->onFrameAvailable(item);
+ }
+}
+
+#if FLAG_BQ_SET_FRAME_RATE
+void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ SFT_LOGV("onSetFrameRate: %.2f", frameRate);
+
+ auto listener = [&] {
+ Mutex::Autolock _l(mMutex);
+ return mSurfaceTextureListener;
+ }();
+
+ if (listener) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
} // namespace android
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index e158f01..b068f48 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1096,6 +1096,26 @@
ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
};
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+ /**
+ * Default value. The layer uses its own frame rate specifications, assuming it has any
+ * specifications, instead of its parent's.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 0,
+
+ /**
+ * The layer's frame rate specifications will propagate to and override those of its descendant
+ * layers.
+ * The layer with this strategy has the ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF
+ * behavior for itself.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+};
+
static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
int8_t compatibility, int8_t changeFrameRateStrategy) {
return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index cc1d12b..2053c6a 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -27,6 +27,7 @@
#include <GrTypes.h>
#include <android-base/stringprintf.h>
#include <gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <gui/TraceUtils.h>
#include <sync/sync.h>
#include <ui/DebugUtils.h>
@@ -299,10 +300,10 @@
LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
SkiaRenderEngine::Contexts contexts;
- contexts.first = GrDirectContext::MakeGL(glInterface, options);
+ contexts.first = GrDirectContexts::MakeGL(glInterface, options);
if (supportsProtectedContentImpl()) {
useProtectedContextImpl(GrProtected::kYes);
- contexts.second = GrDirectContext::MakeGL(glInterface, options);
+ contexts.second = GrDirectContexts::MakeGL(glInterface, options);
useProtectedContextImpl(GrProtected::kNo);
}
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 6ecc6ab..17f263d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -681,7 +681,7 @@
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/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 475dc15..7289fe7 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -98,7 +98,6 @@
}
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
- EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
mThreadedRE->cleanupPostRender();
@@ -107,8 +106,25 @@
}
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_notSkipped) {
- EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(false));
+ renderengine::DisplaySettings settings;
+ std::vector<renderengine::LayerSettings> layers;
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::make(), *mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ base::unique_fd bufferFence;
+
+ EXPECT_CALL(*mRenderEngine, useProtectedContext(false));
+ EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+ .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return());
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
mThreadedRE->cleanupPostRender();
// call ANY synchronous function to ensure that cleanupPostRender has completed.
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 2cb66cb..786a6fe 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -231,13 +231,13 @@
ATRACE_NAME("REThreaded::cleanupPostRender");
instance.cleanupPostRender();
});
+ mNeedsPostRenderCleanup = false;
}
mCondition.notify_one();
}
bool RenderEngineThreaded::canSkipPostRenderCleanup() const {
- waitUntilInitialized();
- return mRenderEngine->canSkipPostRenderCleanup();
+ return !mNeedsPostRenderCleanup;
}
void RenderEngineThreaded::drawLayersInternal(
@@ -257,6 +257,7 @@
int fd = bufferFence.release();
{
std::lock_guard lock(mThreadMutex);
+ mNeedsPostRenderCleanup = true;
mFunctionCalls.push(
[resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
ATRACE_NAME("REThreaded::drawLayers");
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 43ec011..1093f5f 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -89,6 +89,7 @@
mutable std::mutex mThreadMutex;
std::thread mThread GUARDED_BY(mThreadMutex);
std::atomic<bool> mRunning = true;
+ std::atomic<bool> mNeedsPostRenderCleanup = false;
using Work = std::function<void(renderengine::RenderEngine&)>;
mutable std::queue<Work> mFunctionCalls GUARDED_BY(mThreadMutex);
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 7ffdac7..ed3c616 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -48,9 +48,8 @@
void freeHotCacheEntry(android::MultifileHotCache& entry) {
if (entry.entryFd != -1) {
// If we have an fd, then this entry was added to hot cache via INIT or GET
- // We need to unmap and close the entry
+ // We need to unmap the entry
munmap(entry.entryBuffer, entry.entrySize);
- close(entry.entryFd);
} else {
// Otherwise, this was added to hot cache during SET, so it was never mapped
// and fd was only on the deferred thread.
@@ -143,6 +142,7 @@
if (result != sizeof(MultifileHeader)) {
ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
fullPath.c_str(), std::strerror(errno));
+ close(fd);
return;
}
@@ -152,6 +152,7 @@
if (remove(fullPath.c_str()) != 0) {
ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
}
+ close(fd);
continue;
}
@@ -161,6 +162,10 @@
// Memory map the file
uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+ // We can close the file now and the mmap will remain
+ close(fd);
+
if (mappedEntry == MAP_FAILED) {
ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
return;
@@ -206,13 +211,11 @@
if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
ALOGE("INIT Failed to add %u to hot cache", entryHash);
munmap(mappedEntry, fileSize);
- close(fd);
return;
}
} else {
// If we're not keeping it in hot cache, unmap it now
munmap(mappedEntry, fileSize);
- close(fd);
}
}
closedir(dir);
@@ -401,9 +404,12 @@
// Memory map the file
cacheEntry =
reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+ // We can close the file now and the mmap will remain
+ close(fd);
+
if (cacheEntry == MAP_FAILED) {
ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
- close(fd);
return 0;
}
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index dbee13b..1639be6 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -42,6 +42,8 @@
virtual void TearDown() { mMBC.reset(); }
+ int getFileDescriptorCount();
+
std::unique_ptr<TemporaryFile> mTempFile;
std::unique_ptr<MultifileBlobCache> mMBC;
};
@@ -216,4 +218,56 @@
ASSERT_EQ('y', buf[0]);
}
+int MultifileBlobCacheTest::getFileDescriptorCount() {
+ DIR* directory = opendir("/proc/self/fd");
+
+ int fileCount = 0;
+ struct dirent* entry;
+ while ((entry = readdir(directory)) != NULL) {
+ fileCount++;
+ // printf("File: %s\n", entry->d_name);
+ }
+
+ closedir(directory);
+ return fileCount;
+}
+
+TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
+ // Populate the cache with a bunch of entries
+ size_t kLargeNumberOfEntries = 1024;
+ for (int i = 0; i < kLargeNumberOfEntries; i++) {
+ // printf("Caching: %i", i);
+
+ // Use the index as the key and value
+ mMBC->set(&i, sizeof(i), &i, sizeof(i));
+
+ int result = 0;
+ ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+ ASSERT_EQ(i, result);
+ }
+
+ // Ensure we don't have a bunch of open fds
+ ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Now open it again and ensure we still don't have a bunch of open fds
+ mMBC.reset(
+ new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+
+ // Check after initialization
+ ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+
+ for (int i = 0; i < kLargeNumberOfEntries; i++) {
+ int result = 0;
+ ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+ ASSERT_EQ(i, result);
+ }
+
+ // And again after we've actually used it
+ ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+}
+
} // namespace android
diff --git a/services/gpuservice/gpumem/GpuMem.cpp b/services/gpuservice/gpumem/GpuMem.cpp
index dd3cc3b..141fe02 100644
--- a/services/gpuservice/gpumem/GpuMem.cpp
+++ b/services/gpuservice/gpumem/GpuMem.cpp
@@ -77,7 +77,7 @@
mInitialized.store(true);
}
-void GpuMem::setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+void GpuMem::setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
mGpuMemTotalMap = std::move(map);
}
diff --git a/services/gpuservice/gpumem/include/gpumem/GpuMem.h b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
index 7588b54..9aa74d6 100644
--- a/services/gpuservice/gpumem/include/gpumem/GpuMem.h
+++ b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
@@ -44,12 +44,12 @@
friend class TestableGpuMem;
// set gpu memory total map
- void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map);
+ void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map);
// indicate whether ebpf has been initialized
std::atomic<bool> mInitialized = false;
// bpf map for GPU memory total data
- android::bpf::BpfMap<uint64_t, uint64_t> mGpuMemTotalMap;
+ android::bpf::BpfMapRO<uint64_t, uint64_t> mGpuMemTotalMap;
// gpu memory tracepoint event category
static constexpr char kGpuMemTraceGroup[] = "gpu_mem";
diff --git a/services/gpuservice/tests/unittests/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
index 8dabe4f..1f5b288 100644
--- a/services/gpuservice/tests/unittests/GpuMemTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -66,9 +66,7 @@
mTestableGpuMem = TestableGpuMem(mGpuMem.get());
mTestableGpuMem.setInitialized();
errno = 0;
- mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
- TEST_MAP_SIZE,
- BPF_F_NO_PREALLOC));
+ mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
EXPECT_EQ(0, errno);
EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
index 5c04210..6550df9 100644
--- a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
@@ -65,9 +65,7 @@
mTestableGpuMem = TestableGpuMem(mGpuMem.get());
errno = 0;
- mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
- TEST_MAP_SIZE,
- BPF_F_NO_PREALLOC));
+ mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
EXPECT_EQ(0, errno);
EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/TestableGpuMem.h b/services/gpuservice/tests/unittests/TestableGpuMem.h
index 6c8becb..f21843f 100644
--- a/services/gpuservice/tests/unittests/TestableGpuMem.h
+++ b/services/gpuservice/tests/unittests/TestableGpuMem.h
@@ -28,7 +28,7 @@
void setInitialized() { mGpuMem->mInitialized.store(true); }
- void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+ void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
mGpuMem->setGpuMemTotalMap(map);
}
diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h
index 5693848..f848c82 100644
--- a/services/inputflinger/BlockingQueue.h
+++ b/services/inputflinger/BlockingQueue.h
@@ -17,6 +17,7 @@
#pragma once
#include <condition_variable>
+#include <functional>
#include <list>
#include <mutex>
#include <optional>
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 56a3fb4..8e04150 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -64,14 +64,13 @@
class : public InputDeviceMetricsLogger {
nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
- void logInputDeviceUsageReported(const InputDeviceInfo& info,
+ void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
const DeviceUsageReport& report) override {
const int32_t durationMillis =
std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
const static std::vector<int32_t> empty;
- const auto& identifier = info.getIdentifier();
- ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
+ ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis);
ALOGD_IF(DEBUG, " Source breakdown:");
@@ -96,11 +95,9 @@
ALOGD_IF(DEBUG, " - uid: %s\t duration: %dms", uid.toString().c_str(),
durMillis);
}
- util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
- identifier.version,
- linuxBusToInputDeviceBusEnum(identifier.bus,
- info.getUsiVersion().has_value()),
- durationMillis, sources, durationsPerSource, uids, durationsPerUid);
+ util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
+ linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
+ sources, durationsPerSource, uids, durationsPerUid);
}
} sStatsdLogger;
@@ -116,7 +113,7 @@
} // namespace
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo& info,
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
const NotifyKeyArgs& keyArgs) {
if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
return InputDeviceUsageSource::UNKNOWN;
@@ -132,7 +129,7 @@
return InputDeviceUsageSource::GAMEPAD;
}
- if (info.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+ if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
return InputDeviceUsageSource::KEYBOARD;
}
@@ -232,8 +229,8 @@
void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
reportCompletedSessions();
- const SourceProvider getSources = [&args](const InputDeviceInfo& info) {
- return std::set{getUsageSourceForKeyArgs(info, args)};
+ const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+ return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
};
onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
@@ -291,13 +288,23 @@
}
void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
- std::map<DeviceId, InputDeviceInfo> newDeviceInfos;
+ std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
for (const InputDeviceInfo& info : infos) {
if (isIgnoredInputDeviceId(info.getId())) {
continue;
}
- newDeviceInfos.emplace(info.getId(), info);
+ const auto& i = info.getIdentifier();
+ newDeviceInfos.emplace(info.getId(),
+ MetricsDeviceInfo{
+ .deviceId = info.getId(),
+ .vendor = i.vendor,
+ .product = i.product,
+ .version = i.version,
+ .bus = i.bus,
+ .isUsiStylus = info.getUsiVersion().has_value(),
+ .keyboardType = info.getKeyboardType(),
+ });
}
for (auto [deviceId, info] : mLoggedDeviceInfos) {
@@ -311,7 +318,7 @@
}
void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
- const InputDeviceInfo& info) {
+ const MetricsDeviceInfo& info) {
auto it = mActiveUsageSessions.find(deviceId);
if (it == mActiveUsageSessions.end()) {
return;
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 1f7c5d9..7775087 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -79,7 +79,7 @@
};
/** Returns the InputDeviceUsageSource that corresponds to the key event. */
-InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo&, const NotifyKeyArgs&);
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
/** Returns the InputDeviceUsageSources that correspond to the motion event. */
std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
@@ -110,7 +110,19 @@
UidUsageBreakdown uidBreakdown;
};
- virtual void logInputDeviceUsageReported(const InputDeviceInfo&, const DeviceUsageReport&) = 0;
+ // A subset of information from the InputDeviceInfo class that is used for metrics collection,
+ // used to avoid copying and storing all of the fields and strings in InputDeviceInfo.
+ struct MetricsDeviceInfo {
+ int32_t deviceId;
+ int32_t vendor;
+ int32_t product;
+ int32_t version;
+ int32_t bus;
+ bool isUsiStylus;
+ int32_t keyboardType;
+ };
+ virtual void logInputDeviceUsageReported(const MetricsDeviceInfo&,
+ const DeviceUsageReport&) = 0;
virtual ~InputDeviceMetricsLogger() = default;
};
@@ -153,8 +165,9 @@
}
using Uid = gui::Uid;
+ using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
- std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+ std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos;
using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
SyncQueue<Interaction> mInteractionsQueue;
@@ -188,8 +201,9 @@
std::map<DeviceId, ActiveSession> mActiveUsageSessions;
void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
- void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceInfo& info);
- using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
+ void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info);
+ using SourceProvider =
+ std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
const SourceProvider& getSources);
void onInputDeviceInteraction(const Interaction&);
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index f889de5..0f62324 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -18,6 +18,7 @@
#include "UnwantedInteractionBlocker.h"
#include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
#include <ftl/enum.h>
#include <input/PrintTools.h>
#include <inttypes.h>
@@ -28,6 +29,8 @@
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
+namespace input_flags = com::android::input::flags;
+
using android::base::StringPrintf;
/**
@@ -344,10 +347,14 @@
ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
{ // acquire lock
std::scoped_lock lock(mLock);
- const std::vector<NotifyMotionArgs> processedArgs =
- mPreferStylusOverTouchBlocker.processMotion(args);
- for (const NotifyMotionArgs& loopArgs : processedArgs) {
- notifyMotionLocked(loopArgs);
+ if (input_flags::enable_multi_device_input()) {
+ notifyMotionLocked(args);
+ } else {
+ const std::vector<NotifyMotionArgs> processedArgs =
+ mPreferStylusOverTouchBlocker.processMotion(args);
+ for (const NotifyMotionArgs& loopArgs : processedArgs) {
+ notifyMotionLocked(loopArgs);
+ }
}
} // release lock
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index ed95de7..f304712 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -38,13 +38,4 @@
return "?";
}
-std::deque<DispatchEntry*>::iterator Connection::findWaitQueueEntry(uint32_t seq) {
- for (std::deque<DispatchEntry*>::iterator it = waitQueue.begin(); it != waitQueue.end(); it++) {
- if ((*it)->seq == seq) {
- return it;
- }
- }
- return waitQueue.end();
-}
-
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index 2929d61..c17baea 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -53,11 +53,11 @@
bool responsive = true;
// Queue of events that need to be published to the connection.
- std::deque<DispatchEntry*> outboundQueue;
+ std::deque<std::unique_ptr<DispatchEntry>> outboundQueue;
// Queue of events that have been published to the connection but that have not
// yet received a "finished" response from the application.
- std::deque<DispatchEntry*> waitQueue;
+ std::deque<std::unique_ptr<DispatchEntry>> waitQueue;
Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
const IdGenerator& idGenerator);
@@ -65,8 +65,6 @@
inline const std::string getInputChannelName() const { return inputChannel->getName(); }
const std::string getWindowName() const;
-
- std::deque<DispatchEntry*>::iterator findWaitQueueEntry(uint32_t seq);
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index dd4aab8..98e2507 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -226,7 +226,7 @@
const uint32_t seq; // unique sequence number, never 0
std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
- ftl::Flags<InputTarget::Flags> targetFlags;
+ const ftl::Flags<InputTarget::Flags> targetFlags;
ui::Transform transform;
ui::Transform rawTransform;
float globalScaleFactor;
@@ -244,6 +244,8 @@
DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
const ui::Transform& rawTransform, float globalScaleFactor);
+ DispatchEntry(const DispatchEntry&) = delete;
+ DispatchEntry& operator=(const DispatchEntry&) = delete;
inline bool hasForegroundTarget() const {
return targetFlags.test(InputTarget::Flags::FOREGROUND);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 0a1e889..4251682 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -271,7 +271,8 @@
return dump;
}
-std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
+std::string dumpQueue(const std::deque<std::unique_ptr<DispatchEntry>>& queue,
+ nsecs_t currentTime) {
constexpr size_t maxEntries = 50; // max events to print
constexpr size_t skipBegin = maxEntries / 2;
const size_t skipEnd = queue.size() - maxEntries / 2;
@@ -492,8 +493,8 @@
*/
bool isConnectionResponsive(const Connection& connection) {
const nsecs_t currentTime = now();
- for (const DispatchEntry* entry : connection.waitQueue) {
- if (entry->timeoutTime < currentTime) {
+ for (const auto& dispatchEntry : connection.waitQueue) {
+ if (dispatchEntry->timeoutTime < currentTime) {
return false;
}
}
@@ -714,6 +715,40 @@
});
}
+/**
+ * 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 ---
@@ -1259,7 +1294,7 @@
}
std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
- int32_t displayId, const sp<WindowInfoHandle>& touchedWindow) const {
+ int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
if (touchedWindow == nullptr) {
return {};
}
@@ -1275,9 +1310,10 @@
const WindowInfo& info = *windowHandle->getInfo();
if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
- addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
- /*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
- outsideTargets);
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId);
+ addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE, pointerIds,
+ /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
}
}
return outsideTargets;
@@ -2218,40 +2254,6 @@
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) {
@@ -2333,6 +2335,7 @@
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
const auto [x, y] = resolveTouchedPosition(entry);
const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+ const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
// Outside targets should be added upon first dispatched DOWN event. That means, this should
// be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
@@ -2340,7 +2343,7 @@
findTouchedWindowAtLocked(displayId, x, y, isStylus);
if (isDown) {
- targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle);
+ targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
}
// Handle the case where we did not find a window.
if (newTouchedWindowHandle == nullptr) {
@@ -2398,7 +2401,6 @@
if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
- const int32_t pointerId = entry.pointerProperties[0].id;
// The "windowHandle" is the target of this hovering pointer.
tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
}
@@ -2423,7 +2425,7 @@
// Update the temporary touch state.
std::bitset<MAX_POINTER_ID + 1> pointerIds;
if (!isHoverAction) {
- pointerIds.set(entry.pointerProperties[pointerIndex].id);
+ pointerIds.set(pointerId);
}
const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
@@ -2467,7 +2469,6 @@
// If a window is already pilfering some pointers, give it this new pointer as well and
// make it pilfering. This will prevent other non-spy windows from getting this pointer,
// which is a specific behaviour that we want.
- const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
for (TouchedWindow& touchedWindow : tempTouchState.windows) {
if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
touchedWindow.hasPilferingPointers(entry.deviceId)) {
@@ -3415,7 +3416,7 @@
}
// Enqueue the dispatch entry.
- connection->outboundQueue.push_back(dispatchEntry.release());
+ connection->outboundQueue.emplace_back(std::move(dispatchEntry));
traceOutboundQueueLength(*connection);
}
@@ -3584,7 +3585,7 @@
}
while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
- DispatchEntry* dispatchEntry = connection->outboundQueue.front();
+ std::unique_ptr<DispatchEntry>& dispatchEntry = connection->outboundQueue.front();
dispatchEntry->deliveryTime = currentTime;
const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
dispatchEntry->timeoutTime = currentTime + timeout.count();
@@ -3699,14 +3700,12 @@
}
// Re-enqueue the event on the wait queue.
- connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
- connection->outboundQueue.end(),
- dispatchEntry));
+ const nsecs_t timeoutTime = dispatchEntry->timeoutTime;
+ connection->waitQueue.emplace_back(std::move(dispatchEntry));
+ connection->outboundQueue.erase(connection->outboundQueue.begin());
traceOutboundQueueLength(*connection);
- connection->waitQueue.push_back(dispatchEntry);
if (connection->responsive) {
- mAnrTracker.insert(dispatchEntry->timeoutTime,
- connection->inputChannel->getConnectionToken());
+ mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken());
}
traceWaitQueueLength(*connection);
}
@@ -3805,19 +3804,17 @@
}
}
-void InputDispatcher::drainDispatchQueue(std::deque<DispatchEntry*>& queue) {
+void InputDispatcher::drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue) {
while (!queue.empty()) {
- DispatchEntry* dispatchEntry = queue.front();
+ releaseDispatchEntry(std::move(queue.front()));
queue.pop_front();
- releaseDispatchEntry(dispatchEntry);
}
}
-void InputDispatcher::releaseDispatchEntry(DispatchEntry* dispatchEntry) {
+void InputDispatcher::releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry) {
if (dispatchEntry->hasForegroundTarget()) {
decrementPendingForegroundDispatches(*(dispatchEntry->eventEntry));
}
- delete dispatchEntry;
}
int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
@@ -3948,8 +3945,13 @@
<< connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
InputTarget target;
- sp<WindowInfoHandle> windowHandle =
- getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+ sp<WindowInfoHandle> windowHandle;
+ if (options.displayId) {
+ windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken(),
+ options.displayId.value());
+ } else {
+ windowHandle = getWindowHandleLocked(connection->inputChannel->getConnectionToken());
+ }
if (windowHandle != nullptr) {
const WindowInfo* windowInfo = windowHandle->getInfo();
target.setDefaultPointerTransform(windowInfo->transform);
@@ -4319,9 +4321,9 @@
mVerifiersByDisplay.try_emplace(args.displayId,
StringPrintf("display %" PRId32, args.displayId));
Result<void> result =
- it->second.processMovement(args.deviceId, args.action, args.getPointerCount(),
- args.pointerProperties.data(), args.pointerCoords.data(),
- args.flags);
+ it->second.processMovement(args.deviceId, args.source, args.action,
+ args.getPointerCount(), args.pointerProperties.data(),
+ args.pointerCoords.data(), args.flags);
if (!result.ok()) {
LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
}
@@ -4526,7 +4528,7 @@
// the injected event, it is responsible for setting POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY.
// For those events, we will set FLAG_IS_ACCESSIBILITY_EVENT to allow apps to distinguish them
// from events that originate from actual hardware.
- int32_t resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
+ DeviceId resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
if (policyFlags & POLICY_FLAG_FILTERED) {
resolvedDeviceId = event->getDeviceId();
}
@@ -5913,14 +5915,16 @@
status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
if (!requestingChannel) {
- ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+ LOG(WARNING)
+ << "Attempted to pilfer pointers from an un-registered channel or invalid token";
return BAD_VALUE;
}
auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
if (statePtr == nullptr || windowPtr == nullptr) {
- ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
- " Ignoring.");
+ LOG(WARNING)
+ << "Attempted to pilfer points from a channel without any on-going pointer streams."
+ " Ignoring.";
return BAD_VALUE;
}
std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
@@ -5929,36 +5933,38 @@
<< " in window: " << windowPtr->dump();
return BAD_VALUE;
}
- const int32_t deviceId = *deviceIds.begin();
- TouchState& state = *statePtr;
- TouchedWindow& window = *windowPtr;
- // Send cancel events to all the input channels we're stealing from.
- CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
- "input channel stole pointer stream");
- options.deviceId = deviceId;
- options.displayId = displayId;
- std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
- options.pointerIds = pointerIds;
- std::string canceledWindows;
- for (const TouchedWindow& w : state.windows) {
- const std::shared_ptr<InputChannel> channel =
- getInputChannelLocked(w.windowHandle->getToken());
- if (channel != nullptr && channel->getConnectionToken() != token) {
- synthesizeCancelationEventsForInputChannelLocked(channel, options);
- canceledWindows += canceledWindows.empty() ? "[" : ", ";
- canceledWindows += channel->getName();
+ for (const DeviceId deviceId : deviceIds) {
+ TouchState& state = *statePtr;
+ TouchedWindow& window = *windowPtr;
+ // Send cancel events to all the input channels we're stealing from.
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "input channel stole pointer stream");
+ options.deviceId = deviceId;
+ options.displayId = displayId;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+ options.pointerIds = pointerIds;
+ std::string canceledWindows;
+ for (const TouchedWindow& w : state.windows) {
+ const std::shared_ptr<InputChannel> channel =
+ getInputChannelLocked(w.windowHandle->getToken());
+ if (channel != nullptr && channel->getConnectionToken() != token) {
+ synthesizeCancelationEventsForInputChannelLocked(channel, options);
+ canceledWindows += canceledWindows.empty() ? "[" : ", ";
+ canceledWindows += channel->getName();
+ }
}
+ canceledWindows += canceledWindows.empty() ? "[]" : "]";
+ LOG(INFO) << "Channel " << requestingChannel->getName()
+ << " is stealing input gesture for device " << deviceId << " from "
+ << canceledWindows;
+
+ // Prevent the gesture from being sent to any other windows.
+ // This only blocks relevant pointers to be sent to other windows
+ window.addPilferingPointers(deviceId, pointerIds);
+
+ state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
}
- canceledWindows += canceledWindows.empty() ? "[]" : "]";
- ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
- canceledWindows.c_str());
-
- // Prevent the gesture from being sent to any other windows.
- // This only blocks relevant pointers to be sent to other windows
- window.addPilferingPointers(deviceId, pointerIds);
-
- state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
return OK;
}
@@ -6056,43 +6062,52 @@
uint32_t seq, bool handled,
nsecs_t consumeTime) {
// Handle post-event policy actions.
- std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
- if (dispatchEntryIt == connection->waitQueue.end()) {
- return;
- }
- DispatchEntry* dispatchEntry = *dispatchEntryIt;
- const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
- if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
- ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
- ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
- }
- if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
- mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
- connection->inputChannel->getConnectionToken(),
- dispatchEntry->deliveryTime, consumeTime, finishTime);
- }
-
bool restartEvent;
- if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
- KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
- restartEvent =
- afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
- } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
- MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
- restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry, motionEntry,
- handled);
- } else {
- restartEvent = false;
- }
+
+ { // Start critical section
+ auto dispatchEntryIt =
+ std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+ [seq](auto& e) { return e->seq == seq; });
+ if (dispatchEntryIt == connection->waitQueue.end()) {
+ return;
+ }
+
+ DispatchEntry& dispatchEntry = **dispatchEntryIt;
+
+ const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime;
+ if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+ ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+ ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str());
+ }
+ if (shouldReportFinishedEvent(dispatchEntry, *connection)) {
+ mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id,
+ connection->inputChannel->getConnectionToken(),
+ dispatchEntry.deliveryTime, consumeTime, finishTime);
+ }
+
+ if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
+ KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry.eventEntry));
+ restartEvent =
+ afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+ } else if (dispatchEntry.eventEntry->type == EventEntry::Type::MOTION) {
+ MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry.eventEntry));
+ restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry,
+ motionEntry, handled);
+ } else {
+ restartEvent = false;
+ }
+ } // End critical section: The -LockedInterruptable methods may have released the lock.
// Dequeue the event and start the next cycle.
// Because the lock might have been released, it is possible that the
// contents of the wait queue to have been drained, so we need to double-check
// a few things.
- dispatchEntryIt = connection->findWaitQueueEntry(seq);
- if (dispatchEntryIt != connection->waitQueue.end()) {
- dispatchEntry = *dispatchEntryIt;
- connection->waitQueue.erase(dispatchEntryIt);
+ auto entryIt = std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+ [seq](auto& e) { return e->seq == seq; });
+ if (entryIt != connection->waitQueue.end()) {
+ std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt);
+ connection->waitQueue.erase(entryIt);
+
const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
if (!connection->responsive) {
@@ -6104,10 +6119,10 @@
}
traceWaitQueueLength(*connection);
if (restartEvent && connection->status == Connection::Status::NORMAL) {
- connection->outboundQueue.push_front(dispatchEntry);
+ connection->outboundQueue.emplace_front(std::move(dispatchEntry));
traceOutboundQueueLength(*connection);
} else {
- releaseDispatchEntry(dispatchEntry);
+ releaseDispatchEntry(std::move(dispatchEntry));
}
}
@@ -6151,13 +6166,13 @@
* processes the events linearly. So providing information about the oldest entry seems to be
* most useful.
*/
- DispatchEntry* oldestEntry = *connection->waitQueue.begin();
- const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
+ DispatchEntry& oldestEntry = *connection->waitQueue.front();
+ const nsecs_t currentWait = now() - oldestEntry.deliveryTime;
std::string reason =
android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
connection->inputChannel->getName().c_str(),
ns2ms(currentWait),
- oldestEntry->eventEntry->getDescription().c_str());
+ oldestEntry.eventEntry->getDescription().c_str());
sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
@@ -6294,7 +6309,7 @@
}
bool InputDispatcher::afterKeyEventLockedInterruptable(
- const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+ const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
KeyEntry& keyEntry, bool handled) {
if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
if (!handled) {
@@ -6312,7 +6327,7 @@
connection->inputState.removeFallbackKey(originalKeyCode);
}
- if (handled || !dispatchEntry->hasForegroundTarget()) {
+ if (handled || !dispatchEntry.hasForegroundTarget()) {
// If the application handles the original key for which we previously
// generated a fallback or if the window is not a foreground window,
// then cancel the associated fallback key, if any.
@@ -6479,7 +6494,7 @@
}
bool InputDispatcher::afterMotionEventLockedInterruptable(
- const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+ const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
MotionEntry& motionEntry, bool handled) {
return false;
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 512bc4f..62e2d58 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -242,8 +242,8 @@
int32_t displayId, float x, float y, bool isStylus = false,
bool ignoreDragWindow = false) const REQUIRES(mLock);
std::vector<InputTarget> findOutsideTargetsLocked(
- int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow) const
- REQUIRES(mLock);
+ int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+ int32_t pointerId) const REQUIRES(mLock);
std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
@@ -522,7 +522,6 @@
// 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,
@@ -599,8 +598,8 @@
void abortBrokenDispatchCycleLocked(nsecs_t currentTime,
const std::shared_ptr<Connection>& connection, bool notify)
REQUIRES(mLock);
- void drainDispatchQueue(std::deque<DispatchEntry*>& queue);
- void releaseDispatchEntry(DispatchEntry* dispatchEntry);
+ void drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue);
+ void releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry);
int handleReceiveCallback(int events, sp<IBinder> connectionToken);
// The action sent should only be of type AMOTION_EVENT_*
void dispatchPointerDownOutsideFocus(uint32_t source, int32_t action,
@@ -665,10 +664,10 @@
REQUIRES(mLock);
std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
bool afterKeyEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
- DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
+ DispatchEntry& dispatchEntry, KeyEntry& keyEntry,
bool handled) REQUIRES(mLock);
bool afterMotionEventLockedInterruptable(const std::shared_ptr<Connection>& connection,
- DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
+ DispatchEntry& dispatchEntry, MotionEntry& motionEntry,
bool handled) REQUIRES(mLock);
// Find touched state and touched window by token.
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index ccffe26..b21427d 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -83,6 +83,11 @@
}
}
+/**
+ * Return:
+ * true if the incoming event was correctly tracked,
+ * false if the incoming event should be dropped.
+ */
bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
switch (actionMasked) {
@@ -310,7 +315,7 @@
nsecs_t currentTime) {
std::vector<std::unique_ptr<EventEntry>> events;
for (MotionMemento& memento : mMotionMementos) {
- if (!(memento.source & AINPUT_SOURCE_CLASS_POINTER)) {
+ if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
continue;
}
@@ -443,7 +448,7 @@
MotionMemento& memento = mMotionMementos[i];
// Since we support split pointers we need to merge touch events
// from the same source + device + screen.
- if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ if (isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
bool merged = false;
for (size_t j = 0; j < other.mMotionMementos.size(); j++) {
MotionMemento& otherMemento = other.mMotionMementos[j];
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 11f3413..343630c 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -16,7 +16,9 @@
#include "InputTarget.h"
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <inttypes.h>
#include <string>
@@ -34,7 +36,10 @@
}
// Ensure that the new set of pointers doesn't overlap with the current set of pointers.
- LOG_ALWAYS_FATAL_IF((pointerIds & newPointerIds).any());
+ if ((pointerIds & newPointerIds).any()) {
+ LOG(FATAL) << __func__ << " - overlap with incoming pointers "
+ << bitsetToString(newPointerIds) << " in " << *this;
+ }
pointerIds |= newPointerIds;
for (size_t i = 0; i < newPointerIds.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 58b29b8..531fc67 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -451,7 +451,8 @@
void KeyboardInputMapper::onKeyDownProcessed() {
InputReaderContext& context = *getContext();
if (context.isPreventingTouchpadTaps()) {
- // avoid pinging java service unnecessarily
+ // avoid pinging java service unnecessarily, just fade pointer again if it became visible
+ context.fadePointer();
return;
}
// Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 6774b17..e630915 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -28,6 +28,7 @@
namespace android {
+using testing::AllOf;
using testing::Return;
using testing::VariantWith;
constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
@@ -36,6 +37,7 @@
constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
/**
* Unit tests for CursorInputMapper.
@@ -58,10 +60,23 @@
EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
.WillRepeatedly(Return(false));
- EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1));
-
mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
}
+
+ void setPointerCapture(bool enabled) {
+ mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+ mReaderConfiguration.pointerCaptureRequest.seq = 1;
+ int32_t generation = mDevice->getGeneration();
+ std::list<NotifyArgs> args =
+ mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+ InputReaderConfiguration::Change::POINTER_CAPTURE);
+ ASSERT_THAT(args,
+ ElementsAre(
+ VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+
+ // Check that generation also got bumped
+ ASSERT_GT(mDevice->getGeneration(), generation);
+ }
};
/**
@@ -102,4 +117,83 @@
VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
}
+/**
+ * Set pointer capture and check that ACTION_MOVE events are emitted from CursorInputMapper.
+ * During pointer capture, source should be set to MOUSE_RELATIVE. When the capture is disabled,
+ * the events should be generated normally:
+ * 1) The source should return to SOURCE_MOUSE
+ * 2) Cursor position should be incremented by the relative device movements
+ * 3) Cursor position of NotifyMotionArgs should now be getting populated.
+ * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
+ */
+TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+ setPointerCapture(true);
+ std::list<NotifyArgs> args;
+
+ // Move.
+ args += process(EV_REL, REL_X, 10);
+ args += process(EV_REL, REL_Y, 20);
+ args += process(EV_SYN, SYN_REPORT, 0);
+
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+ WithCursorPosition(INVALID_CURSOR_POSITION,
+ INVALID_CURSOR_POSITION)))));
+
+ // Button press.
+ args.clear();
+ args += process(EV_KEY, BTN_MOUSE, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_PRESS),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+
+ // Button release.
+ args.clear();
+ args += process(EV_KEY, BTN_MOUSE, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_RELEASE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_UP),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+
+ // Another move.
+ args.clear();
+ args += process(EV_REL, REL_X, 30);
+ args += process(EV_REL, REL_Y, 40);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(30.0f, 40.0f)))));
+
+ // Disable pointer capture. Afterwards, events should be generated the usual way.
+ setPointerCapture(false);
+
+ args.clear();
+ args += process(EV_REL, REL_X, 10);
+ args += process(EV_REL, REL_Y, 20);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+ WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f),
+ WithCursorPosition(INITIAL_CURSOR_X + 10.0f,
+ INITIAL_CURSOR_Y + 20.0f)))));
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 7ccfaca..fdf9ed1 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -66,21 +66,14 @@
}
InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID,
- uint32_t sources = TOUCHSCREEN | STYLUS,
- bool isAlphabetic = false) {
+ uint32_t sources = TOUCHSCREEN | STYLUS) {
auto info = InputDeviceInfo();
info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
"alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
info.addSource(sources);
- info.setKeyboardType(isAlphabetic ? AINPUT_KEYBOARD_TYPE_ALPHABETIC
- : AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
return info;
}
-const InputDeviceInfo ALPHABETIC_KEYBOARD_INFO =
- generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/true);
-const InputDeviceInfo NON_ALPHABETIC_KEYBOARD_INFO =
- generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/false);
const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
@@ -106,7 +99,7 @@
switch (usageSource) {
case InputDeviceUsageSource::UNKNOWN: {
ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
- getUsageSourceForKeyArgs(generateTestDeviceInfo(),
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
.build()));
@@ -123,7 +116,7 @@
case InputDeviceUsageSource::BUTTONS: {
ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
- getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
.build()));
@@ -132,7 +125,7 @@
case InputDeviceUsageSource::KEYBOARD: {
ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
- getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.build()));
break;
@@ -140,13 +133,13 @@
case InputDeviceUsageSource::DPAD: {
ASSERT_EQ(InputDeviceUsageSource::DPAD,
- getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.keyCode(AKEYCODE_DPAD_CENTER)
.build()));
ASSERT_EQ(InputDeviceUsageSource::DPAD,
- getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.keyCode(AKEYCODE_DPAD_CENTER)
.build()));
@@ -155,13 +148,13 @@
case InputDeviceUsageSource::GAMEPAD: {
ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
- getUsageSourceForKeyArgs(NON_ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.keyCode(AKEYCODE_BUTTON_A)
.build()));
ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
- getUsageSourceForKeyArgs(ALPHABETIC_KEYBOARD_INFO,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
.keyCode(AKEYCODE_BUTTON_A)
.build()));
@@ -358,7 +351,13 @@
std::optional<UidUsageBreakdown> uidBreakdown = {}) {
ASSERT_GE(mLoggedUsageSessions.size(), 1u);
const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
- ASSERT_EQ(info.getIdentifier(), loggedInfo.getIdentifier());
+ const auto& i = info.getIdentifier();
+ ASSERT_EQ(info.getId(), loggedInfo.deviceId);
+ ASSERT_EQ(i.vendor, loggedInfo.vendor);
+ ASSERT_EQ(i.product, loggedInfo.product);
+ ASSERT_EQ(i.version, loggedInfo.version);
+ ASSERT_EQ(i.bus, loggedInfo.bus);
+ ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus);
ASSERT_EQ(duration, report.usageDuration);
if (sourceBreakdown) {
ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
@@ -389,12 +388,12 @@
}
private:
- std::vector<std::tuple<InputDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
+ std::vector<std::tuple<MetricsDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
nanoseconds mCurrentTime{TIME};
nanoseconds getCurrentTime() override { return mCurrentTime; }
- void logInputDeviceUsageReported(const InputDeviceInfo& info,
+ void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
const DeviceUsageReport& report) override {
mLoggedUsageSessions.emplace_back(info, report);
}
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index dd003a6..f0602df 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -28,6 +28,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/Input.h>
+#include <input/PrintTools.h>
#include <linux/input.h>
#include <sys/epoll.h>
@@ -50,6 +51,8 @@
using namespace ftl::flag_operators;
using testing::AllOf;
+namespace {
+
// An arbitrary time value.
static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -136,6 +139,10 @@
auto operator<=>(const PointF&) const = default;
};
+inline std::string pointFToString(const PointF& p) {
+ return std::string("(") + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
+}
+
/**
* Return a DOWN key event with KEYCODE_A.
*/
@@ -148,13 +155,8 @@
return event;
}
-static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
- ASSERT_EQ(expectedAction, receivedAction)
- << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
- << MotionEvent::actionToString(receivedAction);
-}
-
MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+ *result_listener << "expected downTime " << downTime << ", but got " << arg.getDownTime();
return arg.getDownTime() == downTime;
}
@@ -165,6 +167,7 @@
}
MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
+ *result_listener << "expected flags " << std::hex << flags << ", but got " << arg.getFlags();
return arg.getFlags() == flags;
}
@@ -173,10 +176,16 @@
*result_listener << "Expected 1 pointer, got " << arg.getPointerCount();
return false;
}
- return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y;
+ const float receivedX = arg.getX(/*pointerIndex=*/0);
+ const float receivedY = arg.getY(/*pointerIndex=*/0);
+ *result_listener << "expected coords (" << x << ", " << y << "), but got (" << receivedX << ", "
+ << receivedY << ")";
+ return receivedX == x && receivedY == y;
}
MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") {
+ *result_listener << "expected pointerCount " << pointerCount << ", but got "
+ << arg.getPointerCount();
return arg.getPointerCount() == pointerCount;
}
@@ -187,6 +196,8 @@
const int32_t pointerId = arg.getPointerId(pointerIndex);
actualPointers[pointerId] = {arg.getX(pointerIndex), arg.getY(pointerIndex)};
}
+ *result_listener << "expected pointers " << dumpMap(pointers, constToString, pointFToString)
+ << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
return pointers == actualPointers;
}
@@ -617,6 +628,7 @@
mFilteredEvent = nullptr;
}
};
+} // namespace
// --- InputDispatcherTest ---
@@ -959,7 +971,7 @@
switch (expectedEventType) {
case InputEventType::KEY: {
const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
- EXPECT_EQ(expectedAction, keyEvent.getAction());
+ ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
}
@@ -967,8 +979,7 @@
}
case InputEventType::MOTION: {
const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
- assertMotionAction(expectedAction, motionEvent.getAction());
-
+ ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
}
@@ -4003,6 +4014,60 @@
}
/**
+ * When there are multiple screens, such as screen projection to TV or screen recording, if the
+ * cancel event occurs, the coordinates of the cancel event should be sent to the target screen, and
+ * its coordinates should be converted by the transform of the windows of target screen.
+ */
+TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTargetDisplay) {
+ // This case will create a window and a spy window on the default display and mirror
+ // window on the second display. cancel event is sent through spy window pilferPointers
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindowDefaultDisplay =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindowDefaultDisplay->setTrustedOverlay(true);
+ spyWindowDefaultDisplay->setSpy(true);
+
+ sp<FakeWindowHandle> windowDefaultDisplay =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
+ ADISPLAY_ID_DEFAULT);
+ windowDefaultDisplay->setWindowTransform(1, 0, 0, 1);
+
+ sp<FakeWindowHandle> windowSecondDisplay = windowDefaultDisplay->clone(SECOND_DISPLAY_ID);
+ windowSecondDisplay->setWindowTransform(2, 0, 0, 2);
+
+ // Add the windows to the dispatcher
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindowDefaultDisplay->getInfo(), *windowDefaultDisplay->getInfo(),
+ *windowSecondDisplay->getInfo()},
+ {},
+ 0,
+ 0});
+
+ // Send down to ADISPLAY_ID_DEFAULT
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 100}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ spyWindowDefaultDisplay->consumeMotionDown();
+ windowDefaultDisplay->consumeMotionDown();
+
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken()));
+
+ // windowDefaultDisplay gets cancel
+ MotionEvent* event = windowDefaultDisplay->consumeMotion();
+ EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
+
+ // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
+ // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
+ // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
+ // SECOND_DISPLAY_ID, the x and y coordinates are 200
+ EXPECT_EQ(100, event->getX(0));
+ EXPECT_EQ(100, event->getY(0));
+}
+
+/**
* Ensure the correct coordinate spaces are used by InputDispatcher.
*
* InputDispatcher works in the display space, so its coordinate system is relative to the display
@@ -6413,7 +6478,7 @@
ASSERT_NE(nullptr, motionEvent)
<< name.c_str() << ": consumer should have returned non-NULL event.";
- assertMotionAction(expectedAction, motionEvent->getAction());
+ ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
ASSERT_EQ(points.size(), motionEvent->getPointerCount());
for (size_t i = 0; i < points.size(); i++) {
@@ -6794,6 +6859,10 @@
// We have a focused application, but no focused window
// Make sure that we don't notify policy twice about the same ANR.
TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
+ const std::chrono::duration appTimeout = 400ms;
+ mApplication->setDispatchingTimeout(appTimeout);
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
mWindow->setFocusable(false);
mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
@@ -6801,13 +6870,18 @@
// Once a focused event arrives, we get an ANR for this application
// We specify the injection timeout to be smaller than the application timeout, to ensure that
// injection times out (instead of failing).
+ const std::chrono::duration eventInjectionTimeout = 100ms;
+ ASSERT_LT(eventInjectionTimeout, appTimeout);
const InputEventInjectionResult result =
injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::WAIT_FOR_RESULT, 100ms, /*allowKeyRepeat=*/false);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
- const std::chrono::duration appTimeout =
- mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(appTimeout, mApplication);
+ InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+ /*allowKeyRepeat=*/false);
+ ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
+ << "result=" << ftl::enum_string(result);
+ // We already waited for 'eventInjectionTimeout`, because the countdown started when the event
+ // was first injected. So now we have (appTimeout - eventInjectionTimeout) left to wait.
+ std::chrono::duration remainingWaitTime = appTimeout - eventInjectionTimeout;
+ mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(remainingWaitTime, mApplication);
std::this_thread::sleep_for(appTimeout);
// ANR should not be raised again. It is up to policy to do that if it desires.
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 0eee2b9..dac4ea0 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -27,7 +27,7 @@
void InputMapperUnitTest::SetUp() {
mFakePointerController = std::make_shared<FakePointerController>();
mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
- mFakePointerController->setPosition(400, 240);
+ mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
.WillRepeatedly(Return(mFakePointerController));
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 909bd9c..c2ac258 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -39,6 +39,8 @@
protected:
static constexpr int32_t EVENTHUB_ID = 1;
static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr float INITIAL_CURSOR_X = 400;
+ static constexpr float INITIAL_CURSOR_Y = 240;
virtual void SetUp() override;
void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 6539593..6032e30 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -4744,92 +4744,6 @@
ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
-TEST_F(CursorInputMapperTest, Process_PointerCapture) {
- addConfigurationProperty("cursor.mode", "pointer");
- mFakePolicy->setPointerCapture(true);
- CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
-
- NotifyDeviceResetArgs resetArgs;
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
- ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
- mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
- mFakePointerController->setPosition(100, 200);
-
- NotifyMotionArgs args;
-
- // Move.
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 10.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
- // Button press.
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MOUSE, 1);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
- // Button release.
- process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_KEY, BTN_MOUSE, 0);
- process(mapper, ARBITRARY_TIME + 2, READ_TIME, EV_SYN, SYN_REPORT, 0);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
-
- // Another move.
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 30);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 40);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 30.0f, 40.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(100.0f, 200.0f));
-
- // Disable pointer capture and check that the device generation got bumped
- // and events are generated the usual way.
- const uint32_t generation = mReader->getContext()->getGeneration();
- mFakePolicy->setPointerCapture(false);
- configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
- ASSERT_TRUE(mReader->getContext()->getGeneration() != generation);
-
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
- process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
- ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
- ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
- 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
/**
* When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
* pointer acceleration or speed processing should not be applied.
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 5d50b94..05823cd 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -59,7 +59,7 @@
(int32_t deviceId), (override));
MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
- MOCK_METHOD(int32_t, bumpGeneration, (), (override));
+ int32_t bumpGeneration() override { return ++mGeneration; }
MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
(override));
@@ -76,6 +76,9 @@
MOCK_METHOD(void, setPreventingTouchpadTaps, (bool prevent), (override));
MOCK_METHOD(bool, isPreventingTouchpadTaps, (), (override));
+
+private:
+ int32_t mGeneration = 0;
};
class MockEventHubInterface : public EventHubInterface {
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index 08a5559..48f5673 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -112,6 +112,15 @@
}
/**
+ * Pointer should still hide if touchpad taps are already disabled
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
+ testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
+}
+
+/**
* Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
* active
*/
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 183383f..31ad569 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -185,6 +185,14 @@
return argX == x && argY == y;
}
+MATCHER_P2(WithCursorPosition, x, y, "InputEvent with specified cursor position") {
+ const auto argX = arg.xCursorPosition;
+ const auto argY = arg.yCursorPosition;
+ *result_listener << "expected cursor position (" << x << ", " << y << "), but got (" << argX
+ << ", " << argY << ")";
+ return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
+}
+
MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
const auto argX = arg.pointerCoords[pointer].getX();
const auto argY = arg.pointerCoords[pointer].getY();
diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
index e9016bb..219b662 100644
--- a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
@@ -50,8 +50,9 @@
// Pops blocks if it is empty, so only pop up to num elements inserted.
size_t numPops = fdp.ConsumeIntegralInRange<size_t>(0, filled);
for (size_t i = 0; i < numPops; i++) {
- queue.popWithTimeout(
- std::chrono::nanoseconds{fdp.ConsumeIntegral<int64_t>()});
+ // Provide a random timeout up to 1 second
+ queue.popWithTimeout(std::chrono::nanoseconds(
+ fdp.ConsumeIntegralInRange<int64_t>(0, 1E9)));
}
filled > numPops ? filled -= numPops : filled = 0;
},
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index f8ebc97..3b3ed9b 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -73,9 +73,11 @@
},
[&]() -> void {
// SendToNextStage_NotifyKeyArgs
- const nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
- const nsecs_t readTime =
- eventTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
+ const nsecs_t eventTime =
+ fdp.ConsumeIntegralInRange<nsecs_t>(0,
+ systemTime(SYSTEM_TIME_MONOTONIC));
+ const nsecs_t readTime = fdp.ConsumeIntegralInRange<
+ nsecs_t>(eventTime, std::numeric_limits<nsecs_t>::max());
mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
eventTime, readTime,
/*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index ed4829a..6870d4e 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -46,7 +46,6 @@
"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 22db247..fe56969 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -844,10 +844,16 @@
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 9039d16..630906a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -1614,5 +1614,20 @@
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 252ba8e..1faf6a1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -477,8 +477,12 @@
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 422513b..1775a7a 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 @@
// 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/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 7537a39..a5e9368 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -85,6 +85,7 @@
bool isTrustedOverlay;
gui::GameMode gameMode;
scheduler::LayerInfo::FrameRate frameRate;
+ scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
ui::Transform::RotationFlags fixedTransformHint;
std::optional<ui::Transform::RotationFlags> transformHint;
bool handleSkipScreenshotFlag = false;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index da84e44..4c9fb06 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -813,11 +813,23 @@
RequestedLayerState::Changes::Hierarchy) ||
snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
RequestedLayerState::Changes::Hierarchy)) {
- snapshot.frameRate = requested.requestedFrameRate.isValid() ? requested.requestedFrameRate
- : parentSnapshot.frameRate;
+ bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+ snapshot.frameRate = !requested.requestedFrameRate.isValid() || shouldOverrideChildren
+ ? parentSnapshot.frameRate
+ : requested.requestedFrameRate;
snapshot.changes |= RequestedLayerState::Changes::FrameRate;
}
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
+ const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+ requested.frameRateSelectionStrategy);
+ snapshot.frameRateSelectionStrategy =
+ strategy == scheduler::LayerInfo::FrameRateSelectionStrategy::Self
+ ? parentSnapshot.frameRateSelectionStrategy
+ : strategy;
+ }
+
if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
snapshot.frameRateSelectionPriority =
(requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 2bf8bc9..dc8694c 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -125,6 +125,8 @@
defaultFrameRateCompatibility =
static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+ frameRateSelectionStrategy =
+ static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
dataspace = ui::Dataspace::V0_SRGB;
gameMode = gui::GameMode::Unsupported;
requestedFrameRate = {};
@@ -403,10 +405,19 @@
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;
}
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 2684000..5890050 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -192,6 +192,7 @@
mDrawingState.dropInputMode = gui::DropInputMode::NONE;
mDrawingState.dimmingEnabled = true;
mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
+ mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Self;
if (args.flags & ISurfaceComposerClient::eNoColorFill) {
// Set an invalid color so there is no color fill.
@@ -1251,9 +1252,14 @@
return mBorderColor;
}
-bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) {
- // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate
+bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+ bool* transactionNeeded) {
+ // Gets the frame rate to propagate to children.
const auto frameRate = [&] {
+ if (overrideChildren && parentFrameRate.isValid()) {
+ return parentFrameRate;
+ }
+
if (mDrawingState.frameRate.isValid()) {
return mDrawingState.frameRate;
}
@@ -1267,7 +1273,10 @@
bool childrenHaveFrameRate = false;
for (const sp<Layer>& child : mCurrentChildren) {
childrenHaveFrameRate |=
- child->propagateFrameRateForLayerTree(frameRate, transactionNeeded);
+ child->propagateFrameRateForLayerTree(frameRate,
+ overrideChildren ||
+ shouldOverrideChildrenFrameRate(),
+ transactionNeeded);
}
// If we don't have a valid frame rate specification, but the children do, we set this
@@ -1300,7 +1309,7 @@
}();
bool transactionNeeded = false;
- root->propagateFrameRateForLayerTree({}, &transactionNeeded);
+ root->propagateFrameRateForLayerTree({}, false, &transactionNeeded);
// TODO(b/195668952): we probably don't need eTraversalNeeded here
if (transactionNeeded) {
@@ -1338,6 +1347,17 @@
return true;
}
+bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
+ if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
+ mDrawingState.frameRateSelectionStrategy = strategy;
+ mDrawingState.sequence++;
+ mDrawingState.modified = true;
+
+ updateTreeHasFrameRateVote();
+ setTransactionFlags(eTransactionNeeded);
+ return true;
+}
+
void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
nsecs_t postTime) {
mDrawingState.postTime = postTime;
@@ -3163,8 +3183,7 @@
} else {
// release sideband stream if it exists and a non null buffer is being set
if (mDrawingState.sidebandStream != nullptr) {
- mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
- mDrawingState.sidebandStream = nullptr;
+ setSidebandStream(nullptr, info, postTime);
}
}
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0b0cef5..40882f4 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -112,6 +112,7 @@
using FrameRate = scheduler::LayerInfo::FrameRate;
using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
+ using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
struct State {
int32_t z;
@@ -188,6 +189,8 @@
// The combined frame rate of parents / children of this layer
FrameRate frameRateForLayerTree;
+ FrameRateSelectionStrategy frameRateSelectionStrategy;
+
// Set by window manager indicating the layer and all its children are
// in a different orientation than the display. The hint suggests that
// the graphic producers should receive a transform hint as if the
@@ -782,6 +785,8 @@
bool setFrameRate(FrameRate::FrameRateVote);
bool setFrameRateCategory(FrameRateCategory);
+ bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
+
virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
@@ -1097,7 +1102,8 @@
const std::vector<Layer*>& layersInTree);
void updateTreeHasFrameRateVote();
- bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded);
+ bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+ bool* transactionNeeded);
void setZOrderRelativeOf(const wp<Layer>& relativeOf);
bool isTrustedOverlay() const;
gui::DropInputMode getDropInputMode() const;
@@ -1159,6 +1165,11 @@
void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
std::vector<JankData>& jankData);
+ bool shouldOverrideChildrenFrameRate() const {
+ return getDrawingState().frameRateSelectionStrategy ==
+ FrameRateSelectionStrategy::OverrideChildren;
+ }
+
// Cached properties computed from drawing state
// Effective transform taking into account parent transforms and any parent scaling, which is
// a transform from the current layer coordinate space to display(screen) coordinate space.
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 3270e4c..0aee7d4 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -4,6 +4,7 @@
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 e918dc9..be04c09 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 @@
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 c0fc79b..ae334e5 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -50,6 +50,7 @@
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 c70ed2c..edab7ec 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -111,6 +111,15 @@
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) {
@@ -162,10 +171,8 @@
} // namespace
EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
- ResyncCallback resyncCallback,
EventRegistrationFlags eventRegistration)
- : resyncCallback(std::move(resyncCallback)),
- mOwnerUid(callingUid),
+ : mOwnerUid(callingUid),
mEventRegistration(eventRegistration),
mEventThread(eventThread),
mChannel(gui::BitTube::DefaultSize) {}
@@ -241,9 +248,7 @@
EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule,
android::frametimeline::TokenManager* tokenManager,
- ThrottleVsyncCallback throttleVsyncCallback,
- GetVsyncPeriodFunction getVsyncPeriodFunction,
- std::chrono::nanoseconds workDuration,
+ IEventThreadCallback& callback, std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration)
: mThreadName(name),
mVsyncTracer(base::StringPrintf("VSYNC-%s", name), 0),
@@ -252,11 +257,7 @@
mVsyncSchedule(std::move(vsyncSchedule)),
mVsyncRegistration(mVsyncSchedule->getDispatch(), createDispatchCallback(), name),
mTokenManager(tokenManager),
- mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
- mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)) {
- LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
- "getVsyncPeriodFunction must not be null");
-
+ mCallback(callback) {
mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
std::unique_lock<std::mutex> lock(mMutex);
threadMain(lock);
@@ -298,10 +299,10 @@
}
sp<EventThreadConnection> EventThread::createEventConnection(
- ResyncCallback resyncCallback, EventRegistrationFlags eventRegistration) const {
+ EventRegistrationFlags eventRegistration) const {
return sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
IPCThreadState::self()->getCallingUid(),
- std::move(resyncCallback), eventRegistration);
+ eventRegistration);
}
status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -344,9 +345,7 @@
}
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
- if (connection->resyncCallback) {
- connection->resyncCallback();
- }
+ mCallback.resync();
std::lock_guard<std::mutex> lock(mMutex);
@@ -362,20 +361,18 @@
const sp<EventThreadConnection>& connection) const {
// Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
// way to get vsync data (instead of posting callbacks to Choreographer).
- if (connection->resyncCallback) {
- connection->resyncCallback();
- }
+ mCallback.resync();
VsyncEventData vsyncEventData;
- nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
- vsyncEventData.frameInterval = frameInterval;
+ const Period frameInterval = mCallback.getVsyncPeriod(connection->mOwnerUid);
+ vsyncEventData.frameInterval = frameInterval.ns();
const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
std::lock_guard<std::mutex> lock(mMutex);
const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
return {vsyncTime, vsyncTime - mReadyDuration.count()};
}();
- generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
+ generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
presentTime, deadline);
return vsyncEventData;
}
@@ -408,6 +405,13 @@
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 +443,15 @@
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.
}
}
}
@@ -529,9 +537,9 @@
connection->frameRate);
}
- return mThrottleVsyncCallback &&
- mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
- connection->mOwnerUid);
+ const auto expectedPresentTime =
+ TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
+ return mCallback.throttleVsync(expectedPresentTime, connection->mOwnerUid);
};
switch (event.header.type) {
@@ -651,9 +659,9 @@
for (const auto& consumer : consumers) {
DisplayEventReceiver::Event copy = event;
if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
- const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
- copy.vsync.vsyncData.frameInterval = frameInterval;
- generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp,
+ const Period frameInterval = mCallback.getVsyncPeriod(consumer->mOwnerUid);
+ copy.vsync.vsyncData.frameInterval = frameInterval.ns();
+ generateFrameTimeline(copy.vsync.vsyncData, frameInterval.ns(), copy.header.timestamp,
event.vsync.vsyncData.preferredExpectedPresentationTime(),
event.vsync.vsyncData.preferredDeadlineTimestamp());
}
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7023445..a7c8b74 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -54,7 +54,6 @@
// ---------------------------------------------------------------------------
-using ResyncCallback = std::function<void()>;
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
enum class VSyncRequest {
@@ -69,7 +68,7 @@
class EventThreadConnection : public gui::BnDisplayEventConnection {
public:
- EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
+ EventThreadConnection(EventThread*, uid_t callingUid,
EventRegistrationFlags eventRegistration = {});
virtual ~EventThreadConnection();
@@ -80,9 +79,6 @@
binder::Status requestNextVsync() override; // asynchronous
binder::Status getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) override;
- // Called in response to requestNextVsync.
- const ResyncCallback resyncCallback;
-
VSyncRequest vsyncRequest = VSyncRequest::None;
const uid_t mOwnerUid;
const EventRegistrationFlags mEventRegistration;
@@ -104,13 +100,15 @@
virtual ~EventThread();
virtual sp<EventThreadConnection> createEventConnection(
- ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0;
+ EventRegistrationFlags eventRegistration = {}) const = 0;
// Feed clients with fake VSYNC, e.g. while the display is off.
virtual void enableSyntheticVsync(bool) = 0;
virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
+ 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;
@@ -134,20 +132,25 @@
virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
};
+struct IEventThreadCallback {
+ virtual ~IEventThreadCallback() = default;
+
+ virtual bool throttleVsync(TimePoint, uid_t) = 0;
+ virtual Period getVsyncPeriod(uid_t) = 0;
+ virtual void resync() = 0;
+};
+
namespace impl {
class EventThread : public android::EventThread {
public:
- using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
- using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
-
EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>,
- frametimeline::TokenManager*, ThrottleVsyncCallback, GetVsyncPeriodFunction,
+ frametimeline::TokenManager*, IEventThreadCallback& callback,
std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
~EventThread();
sp<EventThreadConnection> createEventConnection(
- ResyncCallback, EventRegistrationFlags eventRegistration = {}) const override;
+ EventRegistrationFlags eventRegistration = {}) const override;
status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
@@ -159,6 +162,8 @@
void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
+ void onHotplugConnectionError(int32_t connectionError) override;
+
void onModeChanged(const scheduler::FrameRateMode&) override;
void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
@@ -210,8 +215,7 @@
scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
frametimeline::TokenManager* const mTokenManager;
- const ThrottleVsyncCallback mThrottleVsyncCallback;
- const GetVsyncPeriodFunction mGetVsyncPeriodFunction;
+ IEventThreadCallback& mCallback;
std::thread mThread;
mutable std::mutex mMutex;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 0784251..875bdc8 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -116,12 +116,24 @@
}
}
+ // 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 @@
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 @@
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());
@@ -490,6 +503,19 @@
}
}
+LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
+ int8_t strategy) {
+ switch (strategy) {
+ case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+ return FrameRateSelectionStrategy::Self;
+ case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
+ return FrameRateSelectionStrategy::OverrideChildren;
+ default:
+ LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
+ return FrameRateSelectionStrategy::Self;
+ }
+}
+
bool LayerInfo::FrameRate::isNoVote() const {
return vote.type == FrameRateCompatibility::NoVote;
}
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 7fe407f..129b4c4 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -96,6 +96,13 @@
ftl_last = NoVote
};
+ enum class FrameRateSelectionStrategy {
+ Self,
+ OverrideChildren,
+
+ ftl_last = OverrideChildren
+ };
+
// Encapsulates the frame rate specifications of the layer. This information will be used
// when the display refresh rate is determined.
struct FrameRate {
@@ -139,11 +146,11 @@
static FrameRateCompatibility convertCompatibility(int8_t compatibility);
// Convert an ANATIVEWINDOW_CHANGE_FRAME_RATE_* value to a scheduler::Seamlessness.
- // Logs fatal if the compatibility value is invalid.
+ // Logs fatal if the strategy value is invalid.
static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
// Convert an ANATIVEWINDOW_FRAME_RATE_CATEGORY_* value to a FrameRateCategory.
- // Logs fatal if the compatibility value is invalid.
+ // Logs fatal if the category value is invalid.
static FrameRateCategory convertCategory(int8_t category);
// True if the FrameRate has explicit frame rate specifications.
@@ -164,6 +171,10 @@
}
};
+ // Convert an ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_* value to FrameRateSelectionStrategy.
+ // Logs fatal if the strategy value is invalid.
+ static FrameRateSelectionStrategy convertFrameRateSelectionStrategy(int8_t strategy);
+
static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
LayerInfo(const std::string& name, uid_t ownerUid, LayerHistory::LayerVoteType defaultVote);
@@ -346,6 +357,10 @@
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 e378946..b06723d 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 @@
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 595550b..68e2ce9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -211,6 +211,17 @@
targeters.try_emplace(id, &targeter);
}
+ if (flagutils::vrrConfigEnabled() &&
+ CC_UNLIKELY(mPacesetterFrameDurationFractionToSkip > 0.f)) {
+ const auto period = pacesetterTargeter.target().expectedFrameDuration();
+ const auto skipDuration = Duration::fromNs(
+ static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
+ ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+ mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+ std::this_thread::sleep_for(skipDuration);
+ mPacesetterFrameDurationFractionToSkip = 0.f;
+ }
+
const auto resultsPerDisplay = compositor.composite(pacesetterId, targeters);
compositor.sample();
@@ -242,36 +253,35 @@
return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate);
}
-impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
- return [this](nsecs_t expectedVsyncTime, uid_t uid) {
- return !isVsyncValid(TimePoint::fromNs(expectedVsyncTime), uid);
- };
+bool Scheduler::throttleVsync(android::TimePoint expectedPresentTime, uid_t uid) {
+ return !isVsyncValid(expectedPresentTime, uid);
}
-impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
- return [this](uid_t uid) {
- const auto [refreshRate, period] = [this] {
- std::scoped_lock lock(mDisplayLock);
- const auto pacesetterOpt = pacesetterDisplayLocked();
- LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
- const Display& pacesetter = *pacesetterOpt;
- return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
- pacesetter.schedulePtr->period());
- }();
+Period Scheduler::getVsyncPeriod(uid_t uid) {
+ const auto [refreshRate, period] = [this] {
+ std::scoped_lock lock(mDisplayLock);
+ const auto pacesetterOpt = pacesetterDisplayLocked();
+ LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
+ const Display& pacesetter = *pacesetterOpt;
+ return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
+ pacesetter.schedulePtr->period());
+ }();
- const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
+ const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
- const auto frameRate = getFrameRateOverride(uid);
- if (!frameRate.has_value()) {
- return currentPeriod.ns();
- }
+ const auto frameRate = getFrameRateOverride(uid);
+ if (!frameRate.has_value()) {
+ return currentPeriod;
+ }
- const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
- if (divisor <= 1) {
- return currentPeriod.ns();
- }
- return currentPeriod.ns() * divisor;
- };
+ const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
+ if (divisor <= 1) {
+ return currentPeriod;
+ }
+
+ // TODO(b/299378819): the casting is not needed, but we need a flag as it might change
+ // behaviour.
+ return Period::fromNs(currentPeriod.ns() * divisor);
}
ConnectionHandle Scheduler::createEventThread(Cycle cycle,
@@ -279,9 +289,7 @@
std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration) {
auto eventThread = std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
- getVsyncSchedule(), tokenManager,
- makeThrottleVsyncCallback(),
- makeGetVsyncPeriodFunction(),
+ getVsyncSchedule(), tokenManager, *this,
workDuration, readyDuration);
auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
@@ -293,7 +301,7 @@
const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
- auto connection = eventThread->createEventConnection([&] { resync(); });
+ auto connection = eventThread->createEventConnection();
std::lock_guard<std::mutex> lock(mConnectionsLock);
mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
@@ -307,8 +315,7 @@
std::scoped_lock lock(mConnectionsLock);
RETURN_IF_INVALID_HANDLE(handle, nullptr);
- return mConnections[handle].thread->createEventConnection([&] { resync(); },
- eventRegistration);
+ return mConnections[handle].thread->createEventConnection(eventRegistration);
}();
const auto layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
@@ -347,6 +354,17 @@
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 d65df2a..f652bb2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -98,7 +98,7 @@
class VsyncSchedule;
-class Scheduler : android::impl::MessageQueue {
+class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
using Impl = android::impl::MessageQueue;
public:
@@ -161,6 +161,8 @@
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&);
@@ -215,7 +217,6 @@
ftl::FakeGuard guard(kMainThreadContext);
resyncToHardwareVsyncLocked(id, allowToEnable, refreshRate);
}
- void resync() EXCLUDES(mDisplayLock);
void forceNextResync() { mLastResyncTime = 0; }
// Passes a vsync sample to VsyncController. Returns true if
@@ -322,6 +323,11 @@
return mFeatures.test(Feature::kSmallDirtyContentDetection);
}
+ // Injects a delay that is a fraction of the predicted frame duration for the next frame.
+ void injectPacesetterDelay(float frameDurationFraction) REQUIRES(kMainThreadContext) {
+ mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
+ }
+
private:
friend class TestableScheduler;
@@ -418,8 +424,10 @@
void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
- android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const;
- android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
+ // IEventThreadCallback overrides
+ bool throttleVsync(TimePoint, uid_t) override;
+ Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
+ void resync() override EXCLUDES(mDisplayLock);
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
struct Connection {
@@ -449,6 +457,9 @@
// Timer used to monitor display power mode.
ftl::Optional<OneShotTimer> mDisplayPowerTimer;
+ // Injected delay prior to compositing, for simulating jank.
+ float mPacesetterFrameDurationFractionToSkip GUARDED_BY(kMainThreadContext) = 0.f;
+
ISchedulerCallback& mSchedulerCallback;
// mDisplayLock may be locked while under mPolicyLock.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f1a45cb..12aacad 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 @@
mMiscFlagValue = flags::misc1();
mConnectedDisplayFlagValue = flags::connected_display();
mMisc2FlagEarlyBootValue = flags::late_boot_misc2();
- mVrrConfigFlagValue = flags::vrr_config();
}
LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
@@ -1355,8 +1355,7 @@
continue;
}
- if (!display->isPoweredOn()) {
- // Display is no longer powered on, so abort the mode change.
+ if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
clearDesiredActiveModeState(display);
continue;
}
@@ -2084,6 +2083,17 @@
void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
+ if (mConnectedDisplayFlagValue) {
+ // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
+ if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
+ vsyncPeriod.value() == ~0) {
+ int hotplugErrorCode = static_cast<int32_t>(-timestamp);
+ ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+ mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
+ return;
+ }
+ }
+
ATRACE_NAME(vsyncPeriod
? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
: ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
@@ -3953,8 +3963,9 @@
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;
}
@@ -4050,6 +4061,9 @@
sp<RegionSamplingThread>::make(*this,
RegionSamplingThread::EnvironmentTimingTunables());
mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+
+ mIsHotplugErrViaNegVsync =
+ base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
}
void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
@@ -5206,6 +5220,14 @@
flags |= eTraversalNeeded;
}
}
+ if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
+ const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
+ scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+ s.frameRateSelectionStrategy);
+ if (layer->setFrameRateSelectionStrategy(strategy)) {
+ flags |= eTraversalNeeded;
+ }
+ }
if (what & layer_state_t::eFixedTransformHintChanged) {
if (layer->setFixedTransformHint(s.fixedTransformHint)) {
flags |= eTraversalNeeded | eTransformHintUpdateNeeded;
@@ -6410,7 +6432,8 @@
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);
@@ -6637,9 +6660,9 @@
code == IBinder::SYSPROPS_TRANSACTION) {
return OK;
}
- // Numbers from 1000 to 1044 are currently used for backdoors. The code
+ // Numbers from 1000 to 1045 are currently used for backdoors. The code
// in onTransact verifies that the user is root, and has access to use SF.
- if (code >= 1000 && code <= 1044) {
+ if (code >= 1000 && code <= 1045) {
ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
return OK;
}
@@ -7019,7 +7042,10 @@
}
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(
@@ -7125,6 +7151,39 @@
}
return NO_ERROR;
}
+ // Inject jank
+ // First argument is a float that describes the fraction of frame duration to jank by.
+ // Second argument is a delay in ms for triggering the jank. This is useful for working
+ // with tools that steal the adb connection. This argument is optional.
+ case 1045: {
+ if (flagutils::vrrConfigEnabled()) {
+ float jankAmount = data.readFloat();
+ int32_t jankDelayMs = 0;
+ if (data.readInt32(&jankDelayMs) != NO_ERROR) {
+ jankDelayMs = 0;
+ }
+
+ const auto jankDelayDuration = Duration(std::chrono::milliseconds(jankDelayMs));
+
+ const bool jankAmountValid = jankAmount > 0.0 && jankAmount < 100.0;
+
+ if (!jankAmountValid) {
+ ALOGD("Ignoring invalid jank amount: %f", jankAmount);
+ reply->writeInt32(BAD_VALUE);
+ return BAD_VALUE;
+ }
+
+ (void)mScheduler->scheduleDelayed(
+ [&, jankAmount]() FTL_FAKE_GUARD(kMainThreadContext) {
+ mScheduler->injectPacesetterDelay(jankAmount);
+ scheduleComposite(FrameHint::kActive);
+ },
+ jankDelayDuration.ns());
+ reply->writeInt32(NO_ERROR);
+ return NO_ERROR;
+ }
+ return err;
+ }
}
}
return err;
@@ -8038,17 +8097,33 @@
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 ef6b815..42825f7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -705,6 +705,9 @@
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&,
@@ -1233,6 +1236,8 @@
hal::Connection connection = hal::Connection::INVALID;
};
+ bool mIsHotplugErrViaNegVsync = false;
+
std::mutex mHotplugMutex;
std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
@@ -1457,7 +1462,6 @@
bool mConnectedDisplayFlagValue;
bool mMisc2FlagEarlyBootValue;
bool mMisc2FlagLateBootValue;
- bool mVrrConfigFlagValue;
};
class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/TEST_MAPPING b/services/surfaceflinger/TEST_MAPPING
index 5512734..6f53d62 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -2,6 +2,9 @@
"imports": [
{
"path": "frameworks/native/libs/gui"
+ },
+ {
+ "path": "frameworks/native/services/inputflinger"
}
],
"presubmit": [
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
index 474fef8..25e768e 100644
--- a/services/surfaceflinger/Tracing/LayerDataSource.cpp
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -52,7 +52,7 @@
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::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 e55b4c2..403e105 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -36,6 +36,10 @@
LayerDataSource::Initialize(*this);
}
+LayerTracing::LayerTracing(std::ostream& outStream) : LayerTracing() {
+ mOutStream = std::ref(outStream);
+}
+
LayerTracing::~LayerTracing() {
LayerDataSource::UnregisterLayerTracing();
}
@@ -49,10 +53,6 @@
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 @@
// 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 @@
}
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 349cc40..fe7f06d 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -97,11 +97,11 @@
};
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 05b89b8..6c9ed30 100644
--- a/services/surfaceflinger/Tracing/TransactionDataSource.cpp
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
@@ -49,26 +49,26 @@
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 0517984..9d6d87e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -70,7 +70,7 @@
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 @@
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 62c362e..23fe8fa 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -41,8 +41,7 @@
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 @@
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 2bb6f51..e4d02ca 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -30,7 +30,6 @@
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 a8ac36a..698ef06 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -50,7 +50,9 @@
const auto* outputLayersTracePath =
(argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
- auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::app};
+ auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::out};
+
+ auto layerTracing = LayerTracing{outStream};
const bool generateLastEntryOnly =
argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
@@ -60,7 +62,7 @@
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 0000000..8435f04
--- /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/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 7062a4e..effbfdb 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -56,31 +56,35 @@
ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
sp<IBinder> asBinder = IInterface::asBinder(listener);
asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
- mWindowInfosListeners.erase(asBinder);
+ eraseListenerAndAckMessages(asBinder);
}});
}
void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
- auto it = mWindowInfosListeners.find(who);
- int64_t listenerId = it->second.first;
- mWindowInfosListeners.erase(who);
-
- std::vector<int64_t> vsyncIds;
- for (auto& [vsyncId, state] : mUnackedState) {
- if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
- listenerId) != state.unackedListenerIds.end()) {
- vsyncIds.push_back(vsyncId);
- }
- }
-
- for (int64_t vsyncId : vsyncIds) {
- ackWindowInfosReceived(vsyncId, listenerId);
- }
+ eraseListenerAndAckMessages(who);
}});
}
+void WindowInfosListenerInvoker::eraseListenerAndAckMessages(const wp<IBinder>& binder) {
+ auto it = mWindowInfosListeners.find(binder);
+ int64_t listenerId = it->second.first;
+ mWindowInfosListeners.erase(binder);
+
+ std::vector<int64_t> vsyncIds;
+ for (auto& [vsyncId, state] : mUnackedState) {
+ if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
+ listenerId) != state.unackedListenerIds.end()) {
+ vsyncIds.push_back(vsyncId);
+ }
+ }
+
+ for (int64_t vsyncId : vsyncIds) {
+ ackWindowInfosReceived(vsyncId, listenerId);
+ }
+}
+
void WindowInfosListenerInvoker::windowInfosChanged(
gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners,
bool forceImmediateCall) {
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index f36b0ed..261fd0f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -67,6 +67,7 @@
std::optional<gui::WindowInfosUpdate> mDelayedUpdate;
WindowInfosReportedListenerSet mReportedListeners;
+ void eraseListenerAndAckMessages(const wp<IBinder>&);
struct UnackedState {
ftl::SmallVector<int64_t, kStaticCapacity> unackedListenerIds;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index cbbfa16..2f5a360 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -97,21 +97,26 @@
return displayId;
}
+struct EventThreadCallback : public IEventThreadCallback {
+ bool throttleVsync(TimePoint, uid_t) override { return false; }
+ Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
+ void resync() override {}
+};
+
void SchedulerFuzzer::fuzzEventThread() {
mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
new scheduler::VsyncSchedule(getPhysicalDisplayId(),
std::make_shared<mock::VSyncTracker>(),
std::make_shared<mock::VSyncDispatch>(), nullptr));
- const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
+ EventThreadCallback callback;
std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
- android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, nullptr, getVsyncPeriod,
+ android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, callback,
(std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
(std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
sp<EventThreadConnection> connection =
- sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>(),
- nullptr);
+ sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>());
thread->requestNextVsync(connection);
thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index bfc03aa..d4ab786 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -11,8 +11,8 @@
flag {
name: "connected_display"
namespace: "core_graphics"
- description: "Controls SurfaceFlinger support for Connected Displays"
- bug: "278199093"
+ description: "Controls SurfaceFlinger support for Connected Displays in 24Q1"
+ bug: "299486625"
is_fixed_read_only: true
}
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index b6cd7b8..aeceadb 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -29,9 +29,6 @@
"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 7a07634..2fcb9e0 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 @@
{
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/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 8deff85..f4516c7 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -87,6 +87,7 @@
"FramebufferSurfaceTest.cpp",
"FrameRateOverrideMappingsTest.cpp",
"FrameRateSelectionPriorityTest.cpp",
+ "FrameRateSelectionStrategyTest.cpp",
"FrameTimelineTest.cpp",
"GameModeTest.cpp",
"HWComposerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index e32cf88..fa31643 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -67,15 +67,13 @@
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*mEventThread, createEventConnection(_, _))
- .WillOnce(Return(sp<EventThreadConnection>::make(mEventThread,
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ .WillOnce(Return(
+ sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
.WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ mock::EventThread::kCallingUid)));
mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
std::make_shared<mock::VSyncTracker>(),
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 9b46009..8891c06 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -57,7 +57,7 @@
} // namespace
-class EventThreadTest : public testing::Test {
+class EventThreadTest : public testing::Test, public IEventThreadCallback {
protected:
static constexpr std::chrono::nanoseconds kWorkDuration = 0ms;
static constexpr std::chrono::nanoseconds kReadyDuration = 3ms;
@@ -65,10 +65,8 @@
class MockEventThreadConnection : public EventThreadConnection {
public:
MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
- ResyncCallback&& resyncCallback,
EventRegistrationFlags eventRegistration)
- : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
- eventRegistration) {}
+ : EventThreadConnection(eventThread, callingUid, eventRegistration) {}
MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
};
@@ -78,7 +76,14 @@
EventThreadTest();
~EventThreadTest() override;
- void setupEventThread(std::chrono::nanoseconds vsyncPeriod);
+ void SetUp() override { mVsyncPeriod = VSYNC_PERIOD; }
+
+ // IEventThreadCallback overrides
+ bool throttleVsync(TimePoint, uid_t) override;
+ Period getVsyncPeriod(uid_t) override;
+ void resync() override;
+
+ void setupEventThread();
sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
EventRegistrationFlags eventRegistration = {},
uid_t ownerUid = mConnectionUid);
@@ -92,8 +97,7 @@
void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
void expectVsyncEventFrameTimelinesCorrect(
nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData);
- void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData,
- std::chrono::nanoseconds vsyncPeriod);
+ void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData);
void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
bool expectedConnected);
void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -133,6 +137,8 @@
sp<MockEventThreadConnection> mThrottledConnection;
std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
+ std::chrono::nanoseconds mVsyncPeriod;
+
static constexpr uid_t mConnectionUid = 443;
static constexpr uid_t mThrottledConnectionUid = 177;
};
@@ -168,17 +174,24 @@
EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
}
-void EventThreadTest::setupEventThread(std::chrono::nanoseconds vsyncPeriod) {
- const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
- mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
- return (uid == mThrottledConnectionUid);
- };
- const auto getVsyncPeriod = [vsyncPeriod](uid_t uid) { return vsyncPeriod.count(); };
+bool EventThreadTest::throttleVsync(android::TimePoint expectedVsyncTimestamp, uid_t uid) {
+ mThrottleVsyncCallRecorder.recordCall(expectedVsyncTimestamp.ns(), uid);
+ return (uid == mThrottledConnectionUid);
+}
+Period EventThreadTest::getVsyncPeriod(uid_t) {
+ return mVsyncPeriod;
+}
+
+void EventThreadTest::resync() {
+ mResyncCallRecorder.recordCall();
+}
+
+void EventThreadTest::setupEventThread() {
mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
- mTokenManager.get(), throttleVsync,
- getVsyncPeriod, kWorkDuration, kReadyDuration);
+ mTokenManager.get(), *this, kWorkDuration,
+ kReadyDuration);
// EventThread should register itself as VSyncSource callback.
EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
@@ -200,9 +213,7 @@
ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration,
uid_t ownerUid) {
sp<MockEventThreadConnection> connection =
- sp<MockEventThreadConnection>::make(mThread.get(), ownerUid,
- mResyncCallRecorder.getInvocable(),
- eventRegistration);
+ sp<MockEventThreadConnection>::make(mThread.get(), ownerUid, eventRegistration);
EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
return connection;
}
@@ -292,10 +303,9 @@
}
}
-void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(
- VsyncEventData vsyncEventData, std::chrono::nanoseconds vsyncPeriod) {
+void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData) {
float nonPreferredTimelinesAmount =
- scheduler::VsyncConfig::kEarlyLatchMaxThreshold / vsyncPeriod;
+ scheduler::VsyncConfig::kEarlyLatchMaxThreshold / mVsyncPeriod;
EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1)
<< "Amount of non-preferred frame timelines too many;"
<< " expected presentation time will be over threshold";
@@ -357,7 +367,7 @@
*/
TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
@@ -368,7 +378,7 @@
}
TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
@@ -381,7 +391,7 @@
}
TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
@@ -414,7 +424,7 @@
}
TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
@@ -428,12 +438,12 @@
}
TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) {
+ setupEventThread();
// The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines,
// due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal
// truncation (e.g. 60Hz has 16.6... ms vsync period).
- std::chrono::nanoseconds vsyncPeriod(scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
- (VsyncEventData::kFrameTimelinesCapacity - 2));
- setupEventThread(vsyncPeriod);
+ mVsyncPeriod = (scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
+ (VsyncEventData::kFrameTimelinesCapacity - 2));
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
@@ -449,11 +459,11 @@
ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
<< expectedTimestamp;
const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData;
- expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, vsyncPeriod);
+ expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
}
TEST_F(EventThreadTest, getLatestVsyncEventData) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const nsecs_t now = systemTime();
const nsecs_t preferredExpectedPresentationTime = now + 20000000;
@@ -469,7 +479,7 @@
// Check EventThread immediately requested a resync.
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
- expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData, VSYNC_PERIOD);
+ expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
<< "Deadline timestamp should be greater than frame time";
for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
@@ -508,7 +518,7 @@
}
TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
// Create a first connection, register it, and request a vsync rate of zero.
ConnectionEventRecorder firstConnectionEventRecorder{0};
@@ -537,7 +547,7 @@
}
TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->setVsyncRate(1, mConnection);
@@ -562,7 +572,7 @@
}
TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->setVsyncRate(2, mConnection);
@@ -590,7 +600,7 @@
}
TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->setVsyncRate(1, mConnection);
@@ -609,7 +619,7 @@
}
TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -635,7 +645,7 @@
}
TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
@@ -661,42 +671,42 @@
}
TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->setDuration(321ns, 456ns);
expectVSyncSetDurationCallReceived(321ns, 456ns);
}
TEST_F(EventThreadTest, postHotplugInternalDisconnect) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
}
TEST_F(EventThreadTest, postHotplugInternalConnect) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
}
TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false);
}
TEST_F(EventThreadTest, postHotplugExternalConnect) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true);
expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true);
}
TEST_F(EventThreadTest, postConfigChangedPrimary) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(INTERNAL_DISPLAY_ID)
@@ -710,7 +720,7 @@
}
TEST_F(EventThreadTest, postConfigChangedExternal) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(EXTERNAL_DISPLAY_ID)
@@ -724,7 +734,7 @@
}
TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(DISPLAY_ID_64BIT)
@@ -737,7 +747,7 @@
}
TEST_F(EventThreadTest, suppressConfigChanged) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
ConnectionEventRecorder suppressConnectionEventRecorder{0};
sp<MockEventThreadConnection> suppressConnection =
@@ -758,7 +768,7 @@
}
TEST_F(EventThreadTest, postUidFrameRateMapping) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const std::vector<FrameRateOverride> overrides = {
{.uid = 1, .frameRateHz = 20},
@@ -771,7 +781,7 @@
}
TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
const std::vector<FrameRateOverride> overrides = {
{.uid = 1, .frameRateHz = 20},
@@ -791,7 +801,7 @@
}
TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
- setupEventThread(VSYNC_PERIOD);
+ setupEventThread();
// Signal that we want the next vsync event to be posted to the throttled connection
mThread->requestNextVsync(mThrottledConnection);
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
new file mode 100644
index 0000000..20ea0c0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "Layer.h"
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+
+namespace android {
+
+using testing::DoAll;
+using testing::Mock;
+using testing::SetArgPointee;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using scheduler::LayerHistory;
+
+using FrameRate = Layer::FrameRate;
+using FrameRateCompatibility = Layer::FrameRateCompatibility;
+using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
+
+/**
+ * This class tests the behaviour of Layer::setFrameRateSelectionStrategy.
+ */
+class FrameRateSelectionStrategyTest : public BaseLayerTest {
+protected:
+ const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
+
+ FrameRateSelectionStrategyTest();
+
+ void addChild(sp<Layer> layer, sp<Layer> child);
+ void removeChild(sp<Layer> layer, sp<Layer> child);
+ void commitTransaction();
+
+ std::vector<sp<Layer>> mLayers;
+};
+
+FrameRateSelectionStrategyTest::FrameRateSelectionStrategyTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+ mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+}
+
+void FrameRateSelectionStrategyTest::addChild(sp<Layer> layer, sp<Layer> child) {
+ layer->addChild(child);
+}
+
+void FrameRateSelectionStrategyTest::removeChild(sp<Layer> layer, sp<Layer> child) {
+ layer->removeChild(child);
+}
+
+void FrameRateSelectionStrategyTest::commitTransaction() {
+ for (auto layer : mLayers) {
+ layer->commitTransaction();
+ }
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, FrameRateSelectionStrategyTest,
+ testing::Values(std::make_shared<BufferStateLayerFactory>(),
+ std::make_shared<EffectLayerFactory>()),
+ PrintToStringParamName);
+
+TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ layer->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ commitTransaction();
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetChildAndGetParent) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ addChild(parent, child1);
+ addChild(child1, child2);
+
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+ child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ commitTransaction();
+ EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ parent->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ child1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ child2->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetParentAndGet) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ addChild(layer1, layer2);
+ addChild(layer2, layer3);
+
+ layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+ layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ layer3->setFrameRate(FRAME_RATE_VOTE3.vote);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+
+ layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+} // namespace
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index acfde71..d7ac038 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -346,6 +346,18 @@
mLifecycleManager.applyTransactions(transactions);
}
+ void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what =
+ layer_state_t::eFrameRateSelectionStrategyChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
void setRoundedCorners(uint32_t id, float radius) {
std::vector<TransactionState> transactions;
transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index c432ad0..7e3e61f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1124,8 +1124,8 @@
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 767dd4f..1a9233d 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -743,6 +743,50 @@
EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
}
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12 (frame rate set to 244.f with strategy OverrideChildren)
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRate(12, 244.f, 0, 0);
+ setFrameRate(122, 123.f, 0, 0);
+ setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::LayerInfo::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
setRoundedCorners(1, 42.f);
setRoundedCorners(2, 42.f);
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 3200003..173f941 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -55,8 +55,7 @@
class MockEventThreadConnection : public android::EventThreadConnection {
public:
explicit MockEventThreadConnection(EventThread* eventThread)
- : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0),
- ResyncCallback()) {}
+ : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0)) {}
~MockEventThreadConnection() = default;
MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel));
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 24eb318..aeac80d 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -124,14 +124,12 @@
EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*eventThread, createEventConnection(_, _))
.WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ mock::EventThread::kCallingUid)));
EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
.WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ mock::EventThread::kCallingUid)));
auto vsyncController = std::make_unique<mock::VsyncController>();
auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
@@ -476,6 +474,37 @@
}
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 +545,7 @@
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 +553,28 @@
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/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 908c9ab..dd998ba 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -281,14 +281,12 @@
EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*eventThread, createEventConnection(_, _))
.WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ mock::EventThread::kCallingUid)));
EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
.WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
- mock::EventThread::kCallingUid,
- ResyncCallback())));
+ mock::EventThread::kCallingUid)));
auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
index c7b845e..cfb047c 100644
--- a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
@@ -245,4 +245,42 @@
EXPECT_EQ(callCount, 1);
}
+// Test that WindowInfosListenerInvoker#removeWindowInfosListener acks any unacked messages for
+// the removed listener.
+TEST_F(WindowInfosListenerInvokerTest, removeListenerAcks) {
+ // Don't ack in this listener to ensure there's an unacked message when the listener is later
+ // removed.
+ gui::WindowInfosListenerInfo listenerToBeRemovedInfo;
+ auto listenerToBeRemoved = sp<Listener>::make([](const gui::WindowInfosUpdate&) {});
+ mInvoker->addWindowInfosListener(listenerToBeRemoved, &listenerToBeRemovedInfo);
+
+ std::mutex mutex;
+ std::condition_variable cv;
+ int callCount = 0;
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ listenerInfo.windowInfosPublisher
+ ->ackWindowInfosReceived(update.vsyncId,
+ listenerInfo.listenerId);
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+ mInvoker->removeWindowInfosListener(listenerToBeRemoved);
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ // Verify that the second listener is called twice. If unacked messages aren't removed when the
+ // first listener is removed, this will fail.
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 2; });
+ }
+ EXPECT_EQ(callCount, 2);
+}
+
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 9a1a16d..866af3b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -29,10 +29,19 @@
EventThread();
~EventThread() override;
- MOCK_METHOD(sp<EventThreadConnection>, createEventConnection,
- (ResyncCallback, EventRegistrationFlags), (const, override));
+ // TODO(b/302035909): workaround otherwise gtest complains about
+ // error: no viable conversion from
+ // 'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
+ // tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
+ sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
+ return createEventConnection(false, flags);
+ }
+ MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
+ (const));
+
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/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index c5870d4..dcef54d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -944,6 +944,11 @@
return VK_ERROR_SURFACE_LOST_KHR;
}
+ // Additional buffer count over min_undequeued_buffers in vulkan came from 2 total
+ // being technically enough for fifo (although a poor experience) vs 3 being the
+ // absolute minimum for mailbox to be useful. So min_undequeued_buffers + 2 is sensible
+ static constexpr int default_additional_buffers = 2;
+
if(pPresentMode != nullptr) {
switch (pPresentMode->presentMode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR:
@@ -951,8 +956,8 @@
break;
case VK_PRESENT_MODE_MAILBOX_KHR:
case VK_PRESENT_MODE_FIFO_KHR:
- capabilities->minImageCount =
- std::min(max_buffer_count, min_undequeued_buffers + 2);
+ capabilities->minImageCount = std::min(max_buffer_count,
+ min_undequeued_buffers + default_additional_buffers);
capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
break;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
@@ -971,7 +976,8 @@
break;
}
} else {
- capabilities->minImageCount = std::min(max_buffer_count, min_undequeued_buffers + 2);
+ capabilities->minImageCount = std::min(max_buffer_count,
+ min_undequeued_buffers + default_additional_buffers);
capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
}
}