Merge "Revert^2 "Added update device info playback configuration""
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 6459805..72c03bf 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -68,15 +68,15 @@
apex::kApexPackageVendorDir};
for (const auto& dir : apex_dirs) {
// Cast call to void to suppress warn_unused_result.
- static_cast<void>(apex::scanPackagesDirAndActivate(dir));
+ static_cast<void>(apex::ScanPackagesDirAndActivate(dir));
}
- return apex::getActivePackages();
+ return apex::GetActivePackages();
}
static void DeactivateApexPackages(const std::vector<apex::ApexFile>& active_packages) {
for (const apex::ApexFile& apex_file : active_packages) {
const std::string& package_path = apex_file.GetPath();
- base::Result<void> status = apex::deactivatePackage(package_path);
+ base::Result<void> status = apex::DeactivatePackage(package_path);
if (!status.ok()) {
LOG(ERROR) << "Failed to deactivate " << package_path << ": "
<< status.error();
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 1994e56..0cbb80f 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -16,6 +16,7 @@
name: "liblshal",
shared_libs: [
"libbase",
+ "libbinderdebug",
"libcutils",
"libutils",
"libhidlbase",
@@ -47,6 +48,7 @@
name: "lshal_defaults",
shared_libs: [
"libbase",
+ "libbinderdebug",
"libcutils",
"libutils",
"libhidlbase",
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 92958d9..22268ac 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -29,7 +29,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
-#include <android-base/parseint.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <hidl-hash/Hash.h>
#include <hidl-util/FQName.h>
@@ -203,97 +202,14 @@
lshal::getVintfInfo(getFrameworkMatrix(), fqInstance, ta, FRAMEWORK_MATRIX);
}
-static bool scanBinderContext(pid_t pid,
- const std::string &contextName,
- std::function<void(const std::string&)> eachLine) {
- std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid));
- if (!ifs.is_open()) {
- ifs.open("/d/binder/proc/" + std::to_string(pid));
- if (!ifs.is_open()) {
- return false;
- }
- }
-
- static const std::regex kContextLine("^context (\\w+)$");
-
- bool isDesiredContext = false;
- std::string line;
- std::smatch match;
- while(getline(ifs, line)) {
- if (std::regex_search(line, match, kContextLine)) {
- isDesiredContext = match.str(1) == contextName;
- continue;
- }
-
- if (!isDesiredContext) {
- continue;
- }
-
- eachLine(line);
- }
- return true;
-}
-
bool ListCommand::getPidInfo(
- pid_t serverPid, PidInfo *pidInfo) const {
- static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
- static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
-
- std::smatch match;
- return scanBinderContext(serverPid, "hwbinder", [&](const std::string& line) {
- if (std::regex_search(line, match, kReferencePrefix)) {
- const std::string &ptrString = "0x" + match.str(2); // use number after c
- uint64_t ptr;
- if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
- // Should not reach here, but just be tolerant.
- err() << "Could not parse number " << ptrString << std::endl;
- return;
- }
- const std::string proc = " proc ";
- auto pos = line.rfind(proc);
- if (pos != std::string::npos) {
- for (const std::string &pidStr : split(line.substr(pos + proc.size()), ' ')) {
- int32_t pid;
- if (!::android::base::ParseInt(pidStr, &pid)) {
- err() << "Could not parse number " << pidStr << std::endl;
- return;
- }
- pidInfo->refPids[ptr].push_back(pid);
- }
- }
-
- return;
- }
-
- if (std::regex_search(line, match, kThreadPrefix)) {
- // "1" is waiting in binder driver
- // "2" is poll. It's impossible to tell if these are in use.
- // and HIDL default code doesn't use it.
- bool isInUse = match.str(1) != "1";
- // "0" is a thread that has called into binder
- // "1" is looper thread
- // "2" is main looper thread
- bool isHwbinderThread = match.str(2) != "0";
-
- if (!isHwbinderThread) {
- return;
- }
-
- if (isInUse) {
- pidInfo->threadUsage++;
- }
-
- pidInfo->threadCount++;
- return;
- }
-
- // not reference or thread line
- return;
- });
+ pid_t serverPid, BinderPidInfo *pidInfo) const {
+ const auto& status = getBinderPidInfo(BinderDebugContext::HWBINDER, serverPid, pidInfo);
+ return status == OK;
}
-const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
- auto pair = mCachedPidInfos.insert({serverPid, PidInfo{}});
+const BinderPidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
+ auto pair = mCachedPidInfos.insert({serverPid, BinderPidInfo{}});
if (pair.second /* did insertion take place? */) {
if (!getPidInfo(serverPid, &pair.first->second)) {
return nullptr;
@@ -727,7 +643,7 @@
entry->arch = fromBaseArchitecture(debugInfo.arch);
if (debugInfo.pid != NO_PID) {
- const PidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+ const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
if (pidInfo == nullptr) {
handleError(IO_ERROR,
"no information for PID " + std::to_string(debugInfo.pid) +
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index 412aadd..561f9cb 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -25,6 +25,7 @@
#include <android-base/macros.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <binderdebug/BinderDebug.h>
#include <hidl-util/FqInstance.h>
#include <vintf/HalManifest.h>
#include <vintf/VintfObject.h>
@@ -40,12 +41,6 @@
class Lshal;
-struct PidInfo {
- std::map<uint64_t, Pids> refPids; // pids that are referenced
- uint32_t threadUsage; // number of threads in use
- uint32_t threadCount; // number of threads total
-};
-
enum class HalType {
BINDERIZED_SERVICES = 0,
PASSTHROUGH_CLIENTS,
@@ -110,9 +105,9 @@
// Get relevant information for a PID by parsing files under
// /dev/binderfs/binder_logs or /d/binder.
// It is a virtual member function so that it can be mocked.
- virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const;
+ virtual bool getPidInfo(pid_t serverPid, BinderPidInfo *info) const;
// Retrieve from mCachedPidInfos and call getPidInfo if necessary.
- const PidInfo* getPidInfoCached(pid_t serverPid);
+ const BinderPidInfo* getPidInfoCached(pid_t serverPid);
void dumpTable(const NullableOStream<std::ostream>& out) const;
void dumpVintf(const NullableOStream<std::ostream>& out) const;
@@ -191,7 +186,7 @@
std::map<pid_t, std::string> mCmdlines;
// Cache for getPidInfo.
- std::map<pid_t, PidInfo> mCachedPidInfos;
+ std::map<pid_t, BinderPidInfo> mCachedPidInfos;
// Cache for getPartition.
std::map<pid_t, Partition> mPartitions;
diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h
index 3c36813..476aa04 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -32,7 +32,7 @@
namespace lshal {
using android::procpartition::Partition;
-using Pids = std::vector<int32_t>;
+using Pids = std::vector<pid_t>;
enum class TableColumnType : unsigned int {
INTERFACE_NAME = 0,
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index ba6cdf1..b6ff28d 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -233,12 +233,12 @@
return ListCommand::dumpVintf(out);
}
void internalPostprocess() { ListCommand::postprocess(); }
- const PidInfo* getPidInfoCached(pid_t serverPid) {
+ const BinderPidInfo* getPidInfoCached(pid_t serverPid) {
return ListCommand::getPidInfoCached(serverPid);
}
MOCK_METHOD0(postprocess, void());
- MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*));
+ MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, BinderPidInfo*));
MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t));
MOCK_METHOD1(getPartition, Partition(pid_t));
@@ -299,8 +299,8 @@
static std::vector<pid_t> getClients(pid_t serverId) {
return {serverId + 1, serverId + 3};
}
-static PidInfo getPidInfoFromId(pid_t serverId) {
- PidInfo info;
+static BinderPidInfo getPidInfoFromId(pid_t serverId) {
+ BinderPidInfo info;
info.refPids[getPtr(serverId)] = getClients(serverId);
info.threadUsage = 10 + serverId;
info.threadCount = 20 + serverId;
@@ -363,7 +363,7 @@
void initMockList() {
mockList = std::make_unique<NiceMock<MockListCommand>>(lshal.get());
ON_CALL(*mockList, getPidInfo(_,_)).WillByDefault(Invoke(
- [](pid_t serverPid, PidInfo* info) {
+ [](pid_t serverPid, BinderPidInfo* info) {
*info = getPidInfoFromId(serverPid);
return true;
}));
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 7aac7da..b21010d 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -494,7 +494,7 @@
sp<BpBinder> bpBinder = binder->remoteBinder();
if (bpBinder == nullptr) return -1;
- return ProcessState::self()->getStrongRefCountForNodeByHandle(bpBinder->handle());
+ return ProcessState::self()->getStrongRefCountForNode(bpBinder);
}
void ServiceManager::handleClientCallbacks() {
diff --git a/data/etc/pc_core_hardware.xml b/data/etc/pc_core_hardware.xml
new file mode 100644
index 0000000..c62da0a
--- /dev/null
+++ b/data/etc/pc_core_hardware.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- These are the hardware components that all handheld devices
+ must include. Devices with optional hardware must also include extra
+ hardware files, per the comments below.
+
+ Handheld devices include phones, mobile Internet devices (MIDs),
+ Personal Media Players (PMPs), small tablets (7" or less), and similar
+ devices.
+-->
+<permissions>
+ <!-- This is Android and fully CTS compatible. Basically this is for CTS tests to use. -->
+ <feature name="android.software.cts" />
+
+ <feature name="android.hardware.audio.output" />
+ <feature name="android.hardware.bluetooth" />
+ <feature name="android.hardware.microphone" />
+ <feature name="android.hardware.screen.portrait" />
+ <feature name="android.hardware.screen.landscape" />
+ <feature name="android.hardware.location" />
+ <feature name="android.hardware.location.network" />
+
+ <!-- basic system services -->
+ <feature name="android.software.app_widgets" />
+ <feature name="android.software.voice_recognizers" />
+ <feature name="android.software.backup" />
+ <feature name="android.software.home_screen" />
+ <feature name="android.software.input_methods" />
+ <feature name="android.software.picture_in_picture" />
+ <feature name="android.software.activities_on_secondary_displays" />
+ <feature name="android.software.print" />
+ <feature name="android.software.companion_device_setup" />
+ <feature name="android.software.autofill" />
+ <feature name="android.software.cant_save_state" />
+ <feature name="android.software.secure_lock_screen" />
+
+ <!-- Feature to specify if the device supports adding device admins. -->
+ <feature name="android.software.device_admin" />
+
+ <!-- Feature to specify if the device support managed users. -->
+ <feature name="android.software.managed_users" />
+
+ <!-- Feature to specify if the device supports controls. -->
+ <feature name="android.software.controls" />
+
+ <!-- Feature to specify if the device supports freeform. -->
+ <feature name="android.software.freeform_window_management" />
+ <feature name="android.hardware.type.pc" />
+</permissions>
\ No newline at end of file
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index 3dd1534..49a616f 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -57,13 +57,15 @@
struct AAsset;
-#if __ANDROID_API__ >= 30
-
/**
- * {@link AImageDecoder} functions result code. Many functions will return one of these
- * to indicate success ({@link ANDROID_IMAGE_DECODER_SUCCESS}) or the reason
- * for the failure. On failure, any out-parameters should be considered
- * uninitialized, except where specified.
+ * {@link AImageDecoder} functions result code.
+ *
+ * Introduced in API 30.
+ *
+ * Many functions will return this to indicate success
+ * ({@link ANDROID_IMAGE_DECODER_SUCCESS}) or the reason for the failure. On
+ * failure, any out-parameters should be considered uninitialized, except where
+ * specified.
*/
enum {
/**
@@ -115,6 +117,8 @@
/**
* Opaque handle for decoding images.
*
+ * Introduced in API 30
+ *
* Create using one of the following:
* - {@link AImageDecoder_createFromAAsset}
* - {@link AImageDecoder_createFromFd}
@@ -130,6 +134,8 @@
*/
typedef struct AImageDecoder AImageDecoder;
+#if __ANDROID_API__ >= 30
+
/**
* Create a new {@link AImageDecoder} from an {@link AAsset}.
*
@@ -331,7 +337,6 @@
int AImageDecoder_setTargetSize(AImageDecoder* _Nonnull decoder, int32_t width,
int32_t height) __INTRODUCED_IN(30);
-
/**
* Compute the dimensions to use for a given sampleSize.
*
@@ -361,6 +366,7 @@
int AImageDecoder_computeSampledSize(const AImageDecoder* _Nonnull decoder, int sampleSize,
int32_t* _Nonnull width, int32_t* _Nonnull height)
__INTRODUCED_IN(30);
+
/**
* Specify how to crop the output after scaling (if any).
*
@@ -388,15 +394,22 @@
*/
int AImageDecoder_setCrop(AImageDecoder* _Nonnull decoder, ARect crop) __INTRODUCED_IN(30);
+#endif // __ANDROID_API__ >= 30
+
struct AImageDecoderHeaderInfo;
/**
- * Opaque handle for representing information about the encoded image. Retrieved
- * using {@link AImageDecoder_getHeaderInfo} and passed to methods like
- * {@link AImageDecoderHeaderInfo_getWidth} and
+ * Opaque handle for representing information about the encoded image.
+ *
+ * Introduced in API 30
+ *
+ * Retrieved using {@link AImageDecoder_getHeaderInfo} and passed to methods
+ * like {@link AImageDecoderHeaderInfo_getWidth} and
* {@link AImageDecoderHeaderInfo_getHeight}.
*/
typedef struct AImageDecoderHeaderInfo AImageDecoderHeaderInfo;
+#if __ANDROID_API__ >= 30
+
/**
* Return an opaque handle for reading header info.
*
@@ -557,14 +570,20 @@
bool AImageDecoder_isAnimated(AImageDecoder* _Nonnull decoder)
__INTRODUCED_IN(31);
+#endif // __ANDROID_API__ >= 31
+
enum {
/*
* Reported by {@link AImageDecoder_getRepeatCount} if the
* animation should repeat forever.
+ *
+ * Introduced in API 31
*/
ANDROID_IMAGE_DECODER_INFINITE = INT32_MAX,
};
+#if __ANDROID_API__ >= 31
+
/**
* Report how many times the animation should repeat.
*
diff --git a/include/android/input.h b/include/android/input.h
index 38af89a..b5d399e 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -856,6 +856,8 @@
AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
/** joystick */
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
+ /** sensor */
+ AINPUT_SOURCE_SENSOR = 0x02000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
diff --git a/include/ftl/.clang-format b/include/ftl/.clang-format
new file mode 120000
index 0000000..86b1593
--- /dev/null
+++ b/include/ftl/.clang-format
@@ -0,0 +1 @@
+../../../../build/soong/scripts/system-clang-format-2
\ No newline at end of file
diff --git a/include/ftl/ArrayTraits.h b/include/ftl/ArrayTraits.h
deleted file mode 100644
index ff685c5..0000000
--- a/include/ftl/ArrayTraits.h
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <algorithm>
-#include <iterator>
-#include <new>
-#include <type_traits>
-
-#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
-
-namespace android::ftl {
-
-template <typename T>
-struct ArrayTraits {
- using value_type = T;
- using size_type = size_t;
- using difference_type = ptrdiff_t;
-
- using pointer = value_type*;
- using reference = value_type&;
- using iterator = pointer;
- using reverse_iterator = std::reverse_iterator<iterator>;
-
- using const_pointer = const value_type*;
- using const_reference = const value_type&;
- using const_iterator = const_pointer;
- using const_reverse_iterator = std::reverse_iterator<const_iterator>;
-
- template <typename... Args>
- static pointer construct_at(const_iterator it, Args&&... args) {
- void* const ptr = const_cast<void*>(static_cast<const void*>(it));
- if constexpr (std::is_constructible_v<value_type, Args...>) {
- // TODO: Replace with std::construct_at in C++20.
- return new (ptr) value_type(std::forward<Args>(args)...);
- } else {
- // Fall back to list initialization.
- return new (ptr) value_type{std::forward<Args>(args)...};
- }
- }
-};
-
-// CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
-template <typename Self, typename T>
-class ArrayIterators {
- FTL_ARRAY_TRAIT(T, size_type);
-
- FTL_ARRAY_TRAIT(T, reference);
- FTL_ARRAY_TRAIT(T, iterator);
- FTL_ARRAY_TRAIT(T, reverse_iterator);
-
- FTL_ARRAY_TRAIT(T, const_reference);
- FTL_ARRAY_TRAIT(T, const_iterator);
- FTL_ARRAY_TRAIT(T, const_reverse_iterator);
-
- Self& self() const { return *const_cast<Self*>(static_cast<const Self*>(this)); }
-
-public:
- const_iterator begin() const { return cbegin(); }
- const_iterator cbegin() const { return self().begin(); }
-
- const_iterator end() const { return cend(); }
- const_iterator cend() const { return self().end(); }
-
- reverse_iterator rbegin() { return std::make_reverse_iterator(self().end()); }
- const_reverse_iterator rbegin() const { return crbegin(); }
- const_reverse_iterator crbegin() const { return self().rbegin(); }
-
- reverse_iterator rend() { return std::make_reverse_iterator(self().begin()); }
- const_reverse_iterator rend() const { return crend(); }
- const_reverse_iterator crend() const { return self().rend(); }
-
- iterator last() { return self().end() - 1; }
- const_iterator last() const { return self().last(); }
-
- reference front() { return *self().begin(); }
- const_reference front() const { return self().front(); }
-
- reference back() { return *last(); }
- const_reference back() const { return self().back(); }
-
- reference operator[](size_type i) { return *(self().begin() + i); }
- const_reference operator[](size_type i) const { return self()[i]; }
-};
-
-// Mixin to define comparison operators for an array-like template.
-// TODO: Replace with operator<=> in C++20.
-template <template <typename, size_t> class Array>
-struct ArrayComparators {
- template <typename T, size_t N, size_t M>
- friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
- }
-
- template <typename T, size_t N, size_t M>
- friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
- }
-
- template <typename T, size_t N, size_t M>
- friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return rhs < lhs;
- }
-
- template <typename T, size_t N, size_t M>
- friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return !(lhs == rhs);
- }
-
- template <typename T, size_t N, size_t M>
- friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return !(lhs < rhs);
- }
-
- template <typename T, size_t N, size_t M>
- friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
- return !(lhs > rhs);
- }
-};
-
-} // namespace android::ftl
diff --git a/include/ftl/InitializerList.h b/include/ftl/InitializerList.h
deleted file mode 100644
index bb99280..0000000
--- a/include/ftl/InitializerList.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <tuple>
-#include <utility>
-
-namespace android::ftl {
-
-// Compile-time counterpart of std::initializer_list<T> that stores per-element constructor
-// arguments with heterogeneous types. For a container with elements of type T, given Sizes
-// (S0, S1, ..., SN), N elements are initialized: the first element is initialized with the
-// first S0 arguments, the second element is initialized with the next S1 arguments, and so
-// on. The list of Types (T0, ..., TM) is flattened, so M is equal to the sum of the Sizes.
-//
-// The InitializerList is created using ftl::init::list, and is consumed by constructors of
-// containers. The function call operator is overloaded such that arguments are accumulated
-// in a tuple with each successive call. For instance, the following calls initialize three
-// strings using different constructors, i.e. string literal, default, and count/character:
-//
-// ... = ftl::init::list<std::string>("abc")()(3u, '?');
-//
-// The following syntax is a shorthand for key-value pairs, where the first argument is the
-// key, and the rest construct the value. The types of the key and value are deduced if the
-// first pair contains exactly two arguments:
-//
-// ... = ftl::init::map<int, std::string>(-1, "abc")(-2)(-3, 3u, '?');
-//
-// ... = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
-//
-// WARNING: The InitializerList returned by an ftl::init::list expression must be consumed
-// immediately, since temporary arguments are destroyed after the full expression. Storing
-// an InitializerList results in dangling references.
-//
-template <typename T, typename Sizes = std::index_sequence<>, typename... Types>
-struct InitializerList;
-
-template <typename T, size_t... Sizes, typename... Types>
-struct InitializerList<T, std::index_sequence<Sizes...>, Types...> {
- // Creates a superset InitializerList by appending the number of arguments to Sizes, and
- // expanding Types with forwarding references for each argument.
- template <typename... Args>
- [[nodiscard]] constexpr auto operator()(Args&&... args) && -> InitializerList<
- T, std::index_sequence<Sizes..., sizeof...(Args)>, Types..., Args&&...> {
- return {std::tuple_cat(std::move(tuple),
- std::forward_as_tuple(std::forward<Args>(args)...))};
- }
-
- // The temporary InitializerList returned by operator() is bound to an rvalue reference in
- // container constructors, which extends the lifetime of any temporary arguments that this
- // tuple refers to until the completion of the full expression containing the construction.
- std::tuple<Types...> tuple;
-};
-
-template <typename K, typename V>
-struct KeyValue {};
-
-// Shorthand for key-value pairs that assigns the first argument to the key, and the rest to the
-// value. The specialization is on KeyValue rather than std::pair, so that ftl::init::list works
-// with the latter.
-template <typename K, typename V, size_t... Sizes, typename... Types>
-struct InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...> {
- // Accumulate the three arguments to std::pair's piecewise constructor.
- template <typename... Args>
- [[nodiscard]] constexpr auto operator()(K&& k, Args&&... args) && -> InitializerList<
- KeyValue<K, V>, std::index_sequence<Sizes..., 3>, Types..., std::piecewise_construct_t,
- std::tuple<K&&>, std::tuple<Args&&...>> {
- return {std::tuple_cat(std::move(tuple),
- std::forward_as_tuple(std::piecewise_construct,
- std::forward_as_tuple(std::forward<K>(k)),
- std::forward_as_tuple(
- std::forward<Args>(args)...)))};
- }
-
- std::tuple<Types...> tuple;
-};
-
-namespace init {
-
-template <typename T, typename... Args>
-[[nodiscard]] constexpr auto list(Args&&... args) {
- return InitializerList<T>{}(std::forward<Args>(args)...);
-}
-
-template <typename K, typename V, typename... Args>
-[[nodiscard]] constexpr auto map(Args&&... args) {
- return list<KeyValue<K, V>>(std::forward<Args>(args)...);
-}
-
-template <typename K, typename V>
-[[nodiscard]] constexpr auto map(K&& k, V&& v) {
- return list<KeyValue<K, V>>(std::forward<K>(k), std::forward<V>(v));
-}
-
-} // namespace init
-} // namespace android::ftl
diff --git a/include/ftl/SmallMap.h b/include/ftl/SmallMap.h
deleted file mode 100644
index 87ae99c..0000000
--- a/include/ftl/SmallMap.h
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <ftl/InitializerList.h>
-#include <ftl/SmallVector.h>
-
-#include <functional>
-#include <optional>
-#include <type_traits>
-#include <utility>
-
-namespace android::ftl {
-
-// Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
-// stored in contiguous storage for cache efficiency. The map is allocated statically until its size
-// exceeds N, at which point mappings are relocated to dynamic memory.
-//
-// SmallMap<K, V, 0> unconditionally allocates on the heap.
-//
-// Example usage:
-//
-// ftl::SmallMap<int, std::string, 3> map;
-// assert(map.empty());
-// assert(!map.dynamic());
-//
-// map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
-// assert(map.size() == 3u);
-// assert(!map.dynamic());
-//
-// assert(map.contains(123));
-// assert(map.find(42, [](const std::string& s) { return s.size(); }) == 3u);
-//
-// const auto opt = map.find(-1);
-// assert(opt);
-//
-// std::string& ref = *opt;
-// assert(ref.empty());
-// ref = "xyz";
-//
-// assert(map == SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
-//
-template <typename K, typename V, size_t N>
-class SmallMap final {
- using Map = SmallVector<std::pair<const K, V>, N>;
-
-public:
- using key_type = K;
- using mapped_type = V;
-
- using value_type = typename Map::value_type;
- using size_type = typename Map::size_type;
- using difference_type = typename Map::difference_type;
-
- using reference = typename Map::reference;
- using iterator = typename Map::iterator;
-
- using const_reference = typename Map::const_reference;
- using const_iterator = typename Map::const_iterator;
-
- // Creates an empty map.
- SmallMap() = default;
-
- // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments.
- // The template arguments K, V, and N are inferred using the deduction guide defined below.
- // The syntax for listing pairs is as follows:
- //
- // ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
- //
- // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
- // assert(map.size() == 3u);
- // assert(map.contains(-1) && map.find(-1)->get().empty());
- // assert(map.contains(42) && map.find(42)->get() == "???");
- // assert(map.contains(123) && map.find(123)->get() == "abc");
- //
- // The types of the key and value are deduced if the first pair contains exactly two arguments:
- //
- // ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
- // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>);
- //
- template <typename U, size_t... Sizes, typename... Types>
- SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& init)
- : mMap(std::move(init)) {
- // TODO: Enforce unique keys.
- }
-
- size_type max_size() const { return mMap.max_size(); }
- size_type size() const { return mMap.size(); }
- bool empty() const { return mMap.empty(); }
-
- // Returns whether the map is backed by static or dynamic storage.
- bool dynamic() const { return mMap.dynamic(); }
-
- iterator begin() { return mMap.begin(); }
- const_iterator begin() const { return cbegin(); }
- const_iterator cbegin() const { return mMap.cbegin(); }
-
- iterator end() { return mMap.end(); }
- const_iterator end() const { return cend(); }
- const_iterator cend() const { return mMap.cend(); }
-
- // Returns whether a mapping exists for the given key.
- bool contains(const key_type& key) const {
- return find(key, [](const mapped_type&) {});
- }
-
- // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
- //
- // ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
- //
- // const auto opt = map.find('c');
- // assert(opt == 'C');
- //
- // char d = 'd';
- // const auto ref = map.find('d').value_or(std::ref(d));
- // ref.get() = 'D';
- // assert(d == 'D');
- //
- auto find(const key_type& key) const
- -> std::optional<std::reference_wrapper<const mapped_type>> {
- return find(key, [](const mapped_type& v) { return std::cref(v); });
- }
-
- auto find(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
- return find(key, [](mapped_type& v) { return std::ref(v); });
- }
-
- // Returns the result R of a unary operation F on (a constant or mutable reference to) the value
- // for the given key, or std::nullopt if the key was not found. If F has a return type of void,
- // then the Boolean result indicates whether the key was found.
- //
- // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
- //
- // assert(map.find('c', [](char c) { return std::toupper(c); }) == 'Z');
- // assert(map.find('c', [](char& c) { c = std::toupper(c); }));
- //
- template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
- auto find(const key_type& key, F f) const
- -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
- for (auto& [k, v] : *this) {
- if (k == key) {
- if constexpr (std::is_void_v<R>) {
- f(v);
- return true;
- } else {
- return f(v);
- }
- }
- }
-
- return {};
- }
-
- template <typename F>
- auto find(const key_type& key, F f) {
- return std::as_const(*this).find(key, [&f](const mapped_type& v) {
- return f(const_cast<mapped_type&>(v));
- });
- }
-
-private:
- Map mMap;
-};
-
-// Deduction guide for in-place constructor.
-template <typename K, typename V, size_t... Sizes, typename... Types>
-SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&)
- -> SmallMap<K, V, sizeof...(Sizes)>;
-
-// Returns whether the key-value pairs of two maps are equal.
-template <typename K, typename V, size_t N, typename Q, typename W, size_t M>
-bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
- if (lhs.size() != rhs.size()) return false;
-
- for (const auto& [k, v] : lhs) {
- const auto& lv = v;
- if (!rhs.find(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
- return false;
- }
- }
-
- return true;
-}
-
-// TODO: Remove in C++20.
-template <typename K, typename V, size_t N, typename Q, typename W, size_t M>
-inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
- return !(lhs == rhs);
-}
-
-} // namespace android::ftl
diff --git a/include/ftl/SmallVector.h b/include/ftl/SmallVector.h
deleted file mode 100644
index 2f05a9b..0000000
--- a/include/ftl/SmallVector.h
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <ftl/ArrayTraits.h>
-#include <ftl/StaticVector.h>
-
-#include <algorithm>
-#include <iterator>
-#include <type_traits>
-#include <utility>
-#include <variant>
-#include <vector>
-
-namespace android::ftl {
-
-template <typename>
-struct IsSmallVector;
-
-// ftl::StaticVector that promotes to std::vector when full. SmallVector is a drop-in replacement
-// for std::vector with statically allocated storage for N elements, whose goal is to improve run
-// time by avoiding heap allocation and increasing probability of cache hits. The standard API is
-// augmented by an unstable_erase operation that does not preserve order, and a replace operation
-// that destructively emplaces.
-//
-// SmallVector<T, 0> is a specialization that thinly wraps std::vector.
-//
-// Example usage:
-//
-// ftl::SmallVector<char, 3> vector;
-// assert(vector.empty());
-// assert(!vector.dynamic());
-//
-// vector = {'a', 'b', 'c'};
-// assert(vector.size() == 3u);
-// assert(!vector.dynamic());
-//
-// vector.push_back('d');
-// assert(vector.dynamic());
-//
-// vector.unstable_erase(vector.begin());
-// assert(vector == (ftl::SmallVector{'d', 'b', 'c'}));
-//
-// vector.pop_back();
-// assert(vector.back() == 'b');
-// assert(vector.dynamic());
-//
-// const char array[] = "hi";
-// vector = ftl::SmallVector(array);
-// assert(vector == (ftl::SmallVector{'h', 'i', '\0'}));
-// assert(!vector.dynamic());
-//
-// ftl::SmallVector strings = ftl::init::list<std::string>("abc")
-// ("123456", 3u)
-// (3u, '?');
-// assert(strings.size() == 3u);
-// assert(!strings.dynamic());
-//
-// assert(strings[0] == "abc");
-// assert(strings[1] == "123");
-// assert(strings[2] == "???");
-//
-template <typename T, size_t N>
-class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
- using Static = StaticVector<T, N>;
- using Dynamic = SmallVector<T, 0>;
-
- // TODO: Replace with std::remove_cvref_t in C++20.
- template <typename U>
- using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
-
-public:
- FTL_ARRAY_TRAIT(T, value_type);
- FTL_ARRAY_TRAIT(T, size_type);
- FTL_ARRAY_TRAIT(T, difference_type);
-
- FTL_ARRAY_TRAIT(T, pointer);
- FTL_ARRAY_TRAIT(T, reference);
- FTL_ARRAY_TRAIT(T, iterator);
- FTL_ARRAY_TRAIT(T, reverse_iterator);
-
- FTL_ARRAY_TRAIT(T, const_pointer);
- FTL_ARRAY_TRAIT(T, const_reference);
- FTL_ARRAY_TRAIT(T, const_iterator);
- FTL_ARRAY_TRAIT(T, const_reverse_iterator);
-
- // Creates an empty vector.
- SmallVector() = default;
-
- // Constructs at most N elements. See StaticVector for underlying constructors.
- template <typename Arg, typename... Args,
- typename = std::enable_if_t<!IsSmallVector<remove_cvref_t<Arg>>{}>>
- SmallVector(Arg&& arg, Args&&... args)
- : mVector(std::in_place_type<Static>, std::forward<Arg>(arg),
- std::forward<Args>(args)...) {}
-
- // Copies at most N elements from a smaller convertible vector.
- template <typename U, size_t M, typename = std::enable_if_t<M <= N>>
- SmallVector(const SmallVector<U, M>& other)
- : SmallVector(IteratorRange, other.begin(), other.end()) {}
-
- void swap(SmallVector& other) { mVector.swap(other.mVector); }
-
- // Returns whether the vector is backed by static or dynamic storage.
- bool dynamic() const { return std::holds_alternative<Dynamic>(mVector); }
-
- // Avoid std::visit as it generates a dispatch table.
-#define DISPATCH(T, F, ...) \
- T F() __VA_ARGS__ { \
- return dynamic() ? std::get<Dynamic>(mVector).F() : std::get<Static>(mVector).F(); \
- }
-
- DISPATCH(size_type, max_size, const)
- DISPATCH(size_type, size, const)
- DISPATCH(bool, empty, const)
-
- // noexcept to suppress warning about zero variadic macro arguments.
- DISPATCH(iterator, begin, noexcept)
- DISPATCH(const_iterator, begin, const)
- DISPATCH(const_iterator, cbegin, const)
-
- DISPATCH(iterator, end, noexcept)
- DISPATCH(const_iterator, end, const)
- DISPATCH(const_iterator, cend, const)
-
- DISPATCH(reverse_iterator, rbegin, noexcept)
- DISPATCH(const_reverse_iterator, rbegin, const)
- DISPATCH(const_reverse_iterator, crbegin, const)
-
- DISPATCH(reverse_iterator, rend, noexcept)
- DISPATCH(const_reverse_iterator, rend, const)
- DISPATCH(const_reverse_iterator, crend, const)
-
- DISPATCH(iterator, last, noexcept)
- DISPATCH(const_iterator, last, const)
-
- DISPATCH(reference, front, noexcept)
- DISPATCH(const_reference, front, const)
-
- DISPATCH(reference, back, noexcept)
- DISPATCH(const_reference, back, const)
-
-#undef DISPATCH
-
- reference operator[](size_type i) {
- return dynamic() ? std::get<Dynamic>(mVector)[i] : std::get<Static>(mVector)[i];
- }
-
- const_reference operator[](size_type i) const { return const_cast<SmallVector&>(*this)[i]; }
-
- // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
- // replacing at end() is erroneous.
- //
- // The element is emplaced via move constructor, so type T does not need to define copy/move
- // assignment, e.g. its data members may be const.
- //
- // The arguments may directly or indirectly refer to the element being replaced.
- //
- // Iterators to the replaced element point to its replacement, and others remain valid.
- //
- template <typename... Args>
- reference replace(const_iterator it, Args&&... args) {
- if (dynamic()) {
- return std::get<Dynamic>(mVector).replace(it, std::forward<Args>(args)...);
- } else {
- return std::get<Static>(mVector).replace(it, std::forward<Args>(args)...);
- }
- }
-
- // Appends an element, and returns a reference to it.
- //
- // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
- // Otherwise, only the end() iterator is invalidated.
- //
- template <typename... Args>
- reference emplace_back(Args&&... args) {
- constexpr auto insertStatic = &Static::template emplace_back<Args...>;
- constexpr auto insertDynamic = &Dynamic::template emplace_back<Args...>;
- return *insert<insertStatic, insertDynamic>(std::forward<Args>(args)...);
- }
-
- // Appends an element.
- //
- // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
- // Otherwise, only the end() iterator is invalidated.
- //
- void push_back(const value_type& v) {
- constexpr auto insertStatic =
- static_cast<bool (Static::*)(const value_type&)>(&Static::push_back);
- constexpr auto insertDynamic =
- static_cast<bool (Dynamic::*)(const value_type&)>(&Dynamic::push_back);
- insert<insertStatic, insertDynamic>(v);
- }
-
- void push_back(value_type&& v) {
- constexpr auto insertStatic =
- static_cast<bool (Static::*)(value_type&&)>(&Static::push_back);
- constexpr auto insertDynamic =
- static_cast<bool (Dynamic::*)(value_type&&)>(&Dynamic::push_back);
- insert<insertStatic, insertDynamic>(std::move(v));
- }
-
- // Removes the last element. The vector must not be empty, or the call is erroneous.
- //
- // The last() and end() iterators are invalidated.
- //
- void pop_back() {
- if (dynamic()) {
- std::get<Dynamic>(mVector).pop_back();
- } else {
- std::get<Static>(mVector).pop_back();
- }
- }
-
- // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
- // this moves the last element to the slot of the erased element.
- //
- // The last() and end() iterators, as well as those to the erased element, are invalidated.
- //
- void unstable_erase(iterator it) {
- if (dynamic()) {
- std::get<Dynamic>(mVector).unstable_erase(it);
- } else {
- std::get<Static>(mVector).unstable_erase(it);
- }
- }
-
-private:
- template <auto insertStatic, auto insertDynamic, typename... Args>
- auto insert(Args&&... args) {
- if (Dynamic* const vector = std::get_if<Dynamic>(&mVector)) {
- return (vector->*insertDynamic)(std::forward<Args>(args)...);
- }
-
- auto& vector = std::get<Static>(mVector);
- if (vector.full()) {
- return (promote(vector).*insertDynamic)(std::forward<Args>(args)...);
- } else {
- return (vector.*insertStatic)(std::forward<Args>(args)...);
- }
- }
-
- Dynamic& promote(Static& staticVector) {
- assert(staticVector.full());
-
- // Allocate double capacity to reduce probability of reallocation.
- Dynamic vector;
- vector.reserve(Static::max_size() * 2);
- std::move(staticVector.begin(), staticVector.end(), std::back_inserter(vector));
-
- return mVector.template emplace<Dynamic>(std::move(vector));
- }
-
- std::variant<Static, Dynamic> mVector;
-};
-
-// Partial specialization without static storage.
-template <typename T>
-class SmallVector<T, 0> final : ArrayTraits<T>,
- ArrayIterators<SmallVector<T, 0>, T>,
- std::vector<T> {
- using ArrayTraits<T>::construct_at;
-
- using Iter = ArrayIterators<SmallVector, T>;
- using Impl = std::vector<T>;
-
- friend Iter;
-
-public:
- FTL_ARRAY_TRAIT(T, value_type);
- FTL_ARRAY_TRAIT(T, size_type);
- FTL_ARRAY_TRAIT(T, difference_type);
-
- FTL_ARRAY_TRAIT(T, pointer);
- FTL_ARRAY_TRAIT(T, reference);
- FTL_ARRAY_TRAIT(T, iterator);
- FTL_ARRAY_TRAIT(T, reverse_iterator);
-
- FTL_ARRAY_TRAIT(T, const_pointer);
- FTL_ARRAY_TRAIT(T, const_reference);
- FTL_ARRAY_TRAIT(T, const_iterator);
- FTL_ARRAY_TRAIT(T, const_reverse_iterator);
-
- using Impl::Impl;
-
- using Impl::empty;
- using Impl::max_size;
- using Impl::size;
-
- using Impl::reserve;
-
- // std::vector iterators are not necessarily raw pointers.
- iterator begin() { return Impl::data(); }
- iterator end() { return Impl::data() + size(); }
-
- using Iter::begin;
- using Iter::end;
-
- using Iter::cbegin;
- using Iter::cend;
-
- using Iter::rbegin;
- using Iter::rend;
-
- using Iter::crbegin;
- using Iter::crend;
-
- using Iter::last;
-
- using Iter::back;
- using Iter::front;
-
- using Iter::operator[];
-
- template <typename... Args>
- reference replace(const_iterator it, Args&&... args) {
- value_type element{std::forward<Args>(args)...};
- std::destroy_at(it);
- // This is only safe because exceptions are disabled.
- return *construct_at(it, std::move(element));
- }
-
- template <typename... Args>
- iterator emplace_back(Args&&... args) {
- return &Impl::emplace_back(std::forward<Args>(args)...);
- }
-
- bool push_back(const value_type& v) {
- Impl::push_back(v);
- return true;
- }
-
- bool push_back(value_type&& v) {
- Impl::push_back(std::move(v));
- return true;
- }
-
- using Impl::pop_back;
-
- void unstable_erase(iterator it) {
- if (it != last()) std::iter_swap(it, last());
- pop_back();
- }
-
- void swap(SmallVector& other) { Impl::swap(other); }
-};
-
-template <typename>
-struct IsSmallVector : std::false_type {};
-
-template <typename T, size_t N>
-struct IsSmallVector<SmallVector<T, N>> : std::true_type {};
-
-// Deduction guide for array constructor.
-template <typename T, size_t N>
-SmallVector(T (&)[N]) -> SmallVector<std::remove_cv_t<T>, N>;
-
-// Deduction guide for variadic constructor.
-template <typename T, typename... Us, typename V = std::decay_t<T>,
- typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
-SmallVector(T&&, Us&&...) -> SmallVector<V, 1 + sizeof...(Us)>;
-
-// Deduction guide for in-place constructor.
-template <typename T, size_t... Sizes, typename... Types>
-SmallVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
- -> SmallVector<T, sizeof...(Sizes)>;
-
-// Deduction guide for StaticVector conversion.
-template <typename T, size_t N>
-SmallVector(StaticVector<T, N>&&) -> SmallVector<T, N>;
-
-template <typename T, size_t N>
-inline void swap(SmallVector<T, N>& lhs, SmallVector<T, N>& rhs) {
- lhs.swap(rhs);
-}
-
-} // namespace android::ftl
diff --git a/include/ftl/StaticVector.h b/include/ftl/StaticVector.h
deleted file mode 100644
index c132556..0000000
--- a/include/ftl/StaticVector.h
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <ftl/ArrayTraits.h>
-#include <ftl/InitializerList.h>
-
-#include <algorithm>
-#include <cassert>
-#include <iterator>
-#include <memory>
-#include <type_traits>
-#include <utility>
-
-namespace android::ftl {
-
-constexpr struct IteratorRangeTag {} IteratorRange;
-
-// Fixed-capacity, statically allocated counterpart of std::vector. Akin to std::array, StaticVector
-// allocates contiguous storage for N elements of type T at compile time, but stores at most (rather
-// than exactly) N elements. Unlike std::array, its default constructor does not require T to have a
-// default constructor, since elements are constructed in place as the vector grows. Operations that
-// insert an element (emplace_back, push_back, etc.) fail when the vector is full. The API otherwise
-// adheres to standard containers, except the unstable_erase operation that does not preserve order,
-// and the replace operation that destructively emplaces.
-//
-// StaticVector<T, 1> is analogous to an iterable std::optional, but StaticVector<T, 0> is an error.
-//
-// Example usage:
-//
-// ftl::StaticVector<char, 3> vector;
-// assert(vector.empty());
-//
-// vector = {'a', 'b'};
-// assert(vector.size() == 2u);
-//
-// vector.push_back('c');
-// assert(vector.full());
-//
-// assert(!vector.push_back('d'));
-// assert(vector.size() == 3u);
-//
-// vector.unstable_erase(vector.begin());
-// assert(vector == (ftl::StaticVector{'c', 'b'}));
-//
-// vector.pop_back();
-// assert(vector.back() == 'c');
-//
-// const char array[] = "hi";
-// vector = ftl::StaticVector(array);
-// assert(vector == (ftl::StaticVector{'h', 'i', '\0'}));
-//
-// ftl::StaticVector strings = ftl::init::list<std::string>("abc")
-// ("123456", 3u)
-// (3u, '?');
-// assert(strings.size() == 3u);
-// assert(strings[0] == "abc");
-// assert(strings[1] == "123");
-// assert(strings[2] == "???");
-//
-template <typename T, size_t N>
-class StaticVector final : ArrayTraits<T>,
- ArrayIterators<StaticVector<T, N>, T>,
- ArrayComparators<StaticVector> {
- static_assert(N > 0);
-
- using ArrayTraits<T>::construct_at;
-
- using Iter = ArrayIterators<StaticVector, T>;
- friend Iter;
-
- // There is ambiguity when constructing from two iterator-like elements like pointers:
- // they could be an iterator range, or arguments for in-place construction. Assume the
- // latter unless they are input iterators and cannot be used to construct elements. If
- // the former is intended, the caller can pass an IteratorRangeTag to disambiguate.
- template <typename I, typename Traits = std::iterator_traits<I>>
- using IsInputIterator = std::conjunction<
- std::is_base_of<std::input_iterator_tag, typename Traits::iterator_category>,
- std::negation<std::is_constructible<T, I>>>;
-
-public:
- FTL_ARRAY_TRAIT(T, value_type);
- FTL_ARRAY_TRAIT(T, size_type);
- FTL_ARRAY_TRAIT(T, difference_type);
-
- FTL_ARRAY_TRAIT(T, pointer);
- FTL_ARRAY_TRAIT(T, reference);
- FTL_ARRAY_TRAIT(T, iterator);
- FTL_ARRAY_TRAIT(T, reverse_iterator);
-
- FTL_ARRAY_TRAIT(T, const_pointer);
- FTL_ARRAY_TRAIT(T, const_reference);
- FTL_ARRAY_TRAIT(T, const_iterator);
- FTL_ARRAY_TRAIT(T, const_reverse_iterator);
-
- // Creates an empty vector.
- StaticVector() = default;
-
- // Copies and moves a vector, respectively.
- StaticVector(const StaticVector& other)
- : StaticVector(IteratorRange, other.begin(), other.end()) {}
- StaticVector(StaticVector&& other) { swap<Empty>(other); }
-
- // Copies at most N elements from a smaller convertible vector.
- template <typename U, size_t M, typename = std::enable_if_t<M <= N>>
- StaticVector(const StaticVector<U, M>& other)
- : StaticVector(IteratorRange, other.begin(), other.end()) {}
-
- // Copies at most N elements from an array.
- template <typename U, size_t M>
- explicit StaticVector(U (&array)[M])
- : StaticVector(IteratorRange, std::begin(array), std::end(array)) {}
-
- // Copies at most N elements from the range [first, last).
- //
- // IteratorRangeTag disambiguates with initialization from two iterator-like elements.
- //
- template <typename Iterator, typename = std::enable_if_t<IsInputIterator<Iterator>{}>>
- StaticVector(Iterator first, Iterator last) : StaticVector(IteratorRange, first, last) {
- using V = typename std::iterator_traits<Iterator>::value_type;
- static_assert(std::is_constructible_v<value_type, V>, "Incompatible iterator range");
- }
-
- template <typename Iterator>
- StaticVector(IteratorRangeTag, Iterator first, Iterator last)
- : mSize(std::min(max_size(), static_cast<size_type>(std::distance(first, last)))) {
- std::uninitialized_copy(first, first + mSize, begin());
- }
-
- // Constructs at most N elements. The template arguments T and N are inferred using the
- // deduction guide defined below. Note that T is determined from the first element, and
- // subsequent elements must have convertible types:
- //
- // ftl::StaticVector vector = {1, 2, 3};
- // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<int, 3>>);
- //
- // const auto copy = "quince"s;
- // auto move = "tart"s;
- // ftl::StaticVector vector = {copy, std::move(move)};
- //
- // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<std::string, 2>>);
- //
- template <typename E, typename... Es,
- typename = std::enable_if_t<std::is_constructible_v<value_type, E>>>
- StaticVector(E&& element, Es&&... elements)
- : StaticVector(std::index_sequence<0>{}, std::forward<E>(element),
- std::forward<Es>(elements)...) {
- static_assert(sizeof...(elements) < N, "Too many elements");
- }
-
- // Constructs at most N elements in place by forwarding per-element constructor arguments. The
- // template arguments T and N are inferred using the deduction guide defined below. The syntax
- // for listing arguments is as follows:
- //
- // ftl::StaticVector vector = ftl::init::list<std::string>("abc")()(3u, '?');
- //
- // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<std::string, 3>>);
- // assert(vector.full());
- // assert(vector[0] == "abc");
- // assert(vector[1].empty());
- // assert(vector[2] == "???");
- //
- template <typename U, size_t Size, size_t... Sizes, typename... Types>
- StaticVector(InitializerList<U, std::index_sequence<Size, Sizes...>, Types...>&& init)
- : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence<Size>{},
- std::index_sequence<Sizes...>{}, init.tuple) {}
-
- ~StaticVector() { std::destroy(begin(), end()); }
-
- StaticVector& operator=(const StaticVector& other) {
- StaticVector copy(other);
- swap(copy);
- return *this;
- }
-
- StaticVector& operator=(StaticVector&& other) {
- std::destroy(begin(), end());
- mSize = 0;
- swap<Empty>(other);
- return *this;
- }
-
- template <typename = void>
- void swap(StaticVector&);
-
- static constexpr size_type max_size() { return N; }
- size_type size() const { return mSize; }
-
- bool empty() const { return size() == 0; }
- bool full() const { return size() == max_size(); }
-
- iterator begin() { return std::launder(reinterpret_cast<pointer>(mData)); }
- iterator end() { return begin() + size(); }
-
- using Iter::begin;
- using Iter::end;
-
- using Iter::cbegin;
- using Iter::cend;
-
- using Iter::rbegin;
- using Iter::rend;
-
- using Iter::crbegin;
- using Iter::crend;
-
- using Iter::last;
-
- using Iter::back;
- using Iter::front;
-
- using Iter::operator[];
-
- // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
- // replacing at end() is erroneous.
- //
- // The element is emplaced via move constructor, so type T does not need to define copy/move
- // assignment, e.g. its data members may be const.
- //
- // The arguments may directly or indirectly refer to the element being replaced.
- //
- // Iterators to the replaced element point to its replacement, and others remain valid.
- //
- template <typename... Args>
- reference replace(const_iterator it, Args&&... args) {
- value_type element{std::forward<Args>(args)...};
- std::destroy_at(it);
- // This is only safe because exceptions are disabled.
- return *construct_at(it, std::move(element));
- }
-
- // Appends an element, and returns an iterator to it. If the vector is full, the element is not
- // inserted, and the end() iterator is returned.
- //
- // On success, the end() iterator is invalidated.
- //
- template <typename... Args>
- iterator emplace_back(Args&&... args) {
- if (full()) return end();
- const iterator it = construct_at(end(), std::forward<Args>(args)...);
- ++mSize;
- return it;
- }
-
- // Appends an element unless the vector is full, and returns whether the element was inserted.
- //
- // On success, the end() iterator is invalidated.
- //
- bool push_back(const value_type& v) {
- // Two statements for sequence point.
- const iterator it = emplace_back(v);
- return it != end();
- }
-
- bool push_back(value_type&& v) {
- // Two statements for sequence point.
- const iterator it = emplace_back(std::move(v));
- return it != end();
- }
-
- // Removes the last element. The vector must not be empty, or the call is erroneous.
- //
- // The last() and end() iterators are invalidated.
- //
- void pop_back() { unstable_erase(last()); }
-
- // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
- // this moves the last element to the slot of the erased element.
- //
- // The last() and end() iterators, as well as those to the erased element, are invalidated.
- //
- void unstable_erase(const_iterator it) {
- std::destroy_at(it);
- if (it != last()) {
- // Move last element and destroy its source for destructor side effects. This is only
- // safe because exceptions are disabled.
- construct_at(it, std::move(back()));
- std::destroy_at(last());
- }
- --mSize;
- }
-
-private:
- struct Empty {};
-
- // Recursion for variadic constructor.
- template <size_t I, typename E, typename... Es>
- StaticVector(std::index_sequence<I>, E&& element, Es&&... elements)
- : StaticVector(std::index_sequence<I + 1>{}, std::forward<Es>(elements)...) {
- construct_at(begin() + I, std::forward<E>(element));
- }
-
- // Base case for variadic constructor.
- template <size_t I>
- explicit StaticVector(std::index_sequence<I>) : mSize(I) {}
-
- // Recursion for in-place constructor.
- //
- // Construct element I by extracting its arguments from the InitializerList tuple. ArgIndex
- // is the position of its first argument in Args, and ArgCount is the number of arguments.
- // The Indices sequence corresponds to [0, ArgCount).
- //
- // The Sizes sequence lists the argument counts for elements after I, so Size is the ArgCount
- // for the next element. The recursion stops when Sizes is empty for the last element.
- //
- template <size_t I, size_t ArgIndex, size_t ArgCount, size_t... Indices, size_t Size,
- size_t... Sizes, typename... Args>
- StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
- std::index_sequence<Size, Sizes...>, std::tuple<Args...>& tuple)
- : StaticVector(std::index_sequence<I + 1, ArgIndex + ArgCount, Size>{},
- std::make_index_sequence<Size>{}, std::index_sequence<Sizes...>{}, tuple) {
- construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
- }
-
- // Base case for in-place constructor.
- template <size_t I, size_t ArgIndex, size_t ArgCount, size_t... Indices, typename... Args>
- StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
- std::index_sequence<>, std::tuple<Args...>& tuple)
- : mSize(I + 1) {
- construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
- }
-
- size_type mSize = 0;
- std::aligned_storage_t<sizeof(value_type), alignof(value_type)> mData[N];
-};
-
-// Deduction guide for array constructor.
-template <typename T, size_t N>
-StaticVector(T (&)[N]) -> StaticVector<std::remove_cv_t<T>, N>;
-
-// Deduction guide for variadic constructor.
-template <typename T, typename... Us, typename V = std::decay_t<T>,
- typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
-StaticVector(T&&, Us&&...) -> StaticVector<V, 1 + sizeof...(Us)>;
-
-// Deduction guide for in-place constructor.
-template <typename T, size_t... Sizes, typename... Types>
-StaticVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
- -> StaticVector<T, sizeof...(Sizes)>;
-
-template <typename T, size_t N>
-template <typename E>
-void StaticVector<T, N>::swap(StaticVector& other) {
- auto [to, from] = std::make_pair(this, &other);
- if (from == this) return;
-
- // Assume this vector has fewer elements, so the excess of the other vector will be moved to it.
- auto [min, max] = std::make_pair(size(), other.size());
-
- // No elements to swap if moving into an empty vector.
- if constexpr (std::is_same_v<E, Empty>) {
- assert(min == 0);
- } else {
- if (min > max) {
- std::swap(from, to);
- std::swap(min, max);
- }
-
- // Swap elements [0, min).
- std::swap_ranges(begin(), begin() + min, other.begin());
-
- // No elements to move if sizes are equal.
- if (min == max) return;
- }
-
- // Move elements [min, max) and destroy their source for destructor side effects.
- const auto [first, last] = std::make_pair(from->begin() + min, from->begin() + max);
- std::uninitialized_move(first, last, to->begin() + min);
- std::destroy(first, last);
-
- std::swap(mSize, other.mSize);
-}
-
-template <typename T, size_t N>
-inline void swap(StaticVector<T, N>& lhs, StaticVector<T, N>& rhs) {
- lhs.swap(rhs);
-}
-
-} // namespace android::ftl
diff --git a/include/ftl/array_traits.h b/include/ftl/array_traits.h
new file mode 100644
index 0000000..1265fa1
--- /dev/null
+++ b/include/ftl/array_traits.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <new>
+#include <type_traits>
+
+#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
+
+namespace android::ftl {
+
+template <typename T>
+struct ArrayTraits {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+
+ using pointer = value_type*;
+ using reference = value_type&;
+ using iterator = pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+
+ using const_pointer = const value_type*;
+ using const_reference = const value_type&;
+ using const_iterator = const_pointer;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ template <typename... Args>
+ static pointer construct_at(const_iterator it, Args&&... args) {
+ void* const ptr = const_cast<void*>(static_cast<const void*>(it));
+ if constexpr (std::is_constructible_v<value_type, Args...>) {
+ // TODO: Replace with std::construct_at in C++20.
+ return new (ptr) value_type(std::forward<Args>(args)...);
+ } else {
+ // Fall back to list initialization.
+ return new (ptr) value_type{std::forward<Args>(args)...};
+ }
+ }
+};
+
+// CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
+template <typename Self, typename T>
+class ArrayIterators {
+ FTL_ARRAY_TRAIT(T, size_type);
+
+ FTL_ARRAY_TRAIT(T, reference);
+ FTL_ARRAY_TRAIT(T, iterator);
+ FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+ FTL_ARRAY_TRAIT(T, const_reference);
+ FTL_ARRAY_TRAIT(T, const_iterator);
+ FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+ Self& self() const { return *const_cast<Self*>(static_cast<const Self*>(this)); }
+
+ public:
+ const_iterator begin() const { return cbegin(); }
+ const_iterator cbegin() const { return self().begin(); }
+
+ const_iterator end() const { return cend(); }
+ const_iterator cend() const { return self().end(); }
+
+ reverse_iterator rbegin() { return std::make_reverse_iterator(self().end()); }
+ const_reverse_iterator rbegin() const { return crbegin(); }
+ const_reverse_iterator crbegin() const { return self().rbegin(); }
+
+ reverse_iterator rend() { return std::make_reverse_iterator(self().begin()); }
+ const_reverse_iterator rend() const { return crend(); }
+ const_reverse_iterator crend() const { return self().rend(); }
+
+ iterator last() { return self().end() - 1; }
+ const_iterator last() const { return self().last(); }
+
+ reference front() { return *self().begin(); }
+ const_reference front() const { return self().front(); }
+
+ reference back() { return *last(); }
+ const_reference back() const { return self().back(); }
+
+ reference operator[](size_type i) { return *(self().begin() + i); }
+ const_reference operator[](size_type i) const { return self()[i]; }
+};
+
+// Mixin to define comparison operators for an array-like template.
+// TODO: Replace with operator<=> in C++20.
+template <template <typename, std::size_t> class Array>
+struct ArrayComparators {
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
+ }
+
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+ }
+
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return rhs < lhs;
+ }
+
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return !(lhs == rhs);
+ }
+
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return !(lhs < rhs);
+ }
+
+ template <typename T, std::size_t N, std::size_t M>
+ friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+ return !(lhs > rhs);
+ }
+};
+
+} // namespace android::ftl
diff --git a/include/ftl/future.h b/include/ftl/future.h
new file mode 100644
index 0000000..dd6358f
--- /dev/null
+++ b/include/ftl/future.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <future>
+#include <type_traits>
+#include <utility>
+
+namespace android::ftl {
+
+// Creates a future that defers a function call until its result is queried.
+//
+// auto future = ftl::defer([](int x) { return x + 1; }, 99);
+// assert(future.get() == 100);
+//
+template <typename F, typename... Args>
+inline auto defer(F&& f, Args&&... args) {
+ return std::async(std::launch::deferred, std::forward<F>(f), std::forward<Args>(args)...);
+}
+
+// Creates a future that wraps a value.
+//
+// auto future = ftl::yield(42);
+// assert(future.get() == 42);
+//
+// auto ptr = std::make_unique<char>('!');
+// auto future = ftl::yield(std::move(ptr));
+// assert(*future.get() == '!');
+//
+template <typename T>
+inline std::future<T> yield(T&& v) {
+ return defer([](T&& v) { return std::forward<T>(v); }, std::forward<T>(v));
+}
+
+namespace details {
+
+template <typename T>
+struct future_result {
+ using type = T;
+};
+
+template <typename T>
+struct future_result<std::future<T>> {
+ using type = T;
+};
+
+template <typename T>
+using future_result_t = typename future_result<T>::type;
+
+// Attaches a continuation to a future. The continuation is a function that maps T to either R or
+// std::future<R>. In the former case, the chain wraps the result in a future as if by ftl::yield.
+//
+// auto future = ftl::yield(123);
+// std::future<char> futures[] = {ftl::yield('a'), ftl::yield('b')};
+//
+// std::future<char> chain =
+// ftl::chain(std::move(future))
+// .then([](int x) { return static_cast<std::size_t>(x % 2); })
+// .then([&futures](std::size_t i) { return std::move(futures[i]); });
+//
+// assert(chain.get() == 'b');
+//
+template <typename T>
+struct Chain {
+ // Implicit conversion.
+ Chain(std::future<T>&& f) : future(std::move(f)) {}
+ operator std::future<T>&&() && { return std::move(future); }
+
+ T get() && { return future.get(); }
+
+ template <typename F, typename R = std::invoke_result_t<F, T>>
+ auto then(F&& op) && -> Chain<future_result_t<R>> {
+ return defer(
+ [](auto&& f, F&& op) {
+ R r = op(f.get());
+ if constexpr (std::is_same_v<R, future_result_t<R>>) {
+ return r;
+ } else {
+ return r.get();
+ }
+ },
+ std::move(future), std::forward<F>(op));
+ }
+
+ std::future<T> future;
+};
+
+} // namespace details
+
+template <typename T>
+inline auto chain(std::future<T>&& f) -> details::Chain<T> {
+ return std::move(f);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/initializer_list.h b/include/ftl/initializer_list.h
new file mode 100644
index 0000000..769c09f
--- /dev/null
+++ b/include/ftl/initializer_list.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <tuple>
+#include <utility>
+
+namespace android::ftl {
+
+// Compile-time counterpart of std::initializer_list<T> that stores per-element constructor
+// arguments with heterogeneous types. For a container with elements of type T, given Sizes
+// (S0, S1, ..., SN), N elements are initialized: the first element is initialized with the
+// first S0 arguments, the second element is initialized with the next S1 arguments, and so
+// on. The list of Types (T0, ..., TM) is flattened, so M is equal to the sum of the Sizes.
+//
+// An InitializerList is created using ftl::init::list, and is consumed by constructors of
+// containers. The function call operator is overloaded such that arguments are accumulated
+// in a tuple with each successive call. For instance, the following calls initialize three
+// strings using different constructors, i.e. string literal, default, and count/character:
+//
+// ... = ftl::init::list<std::string>("abc")()(3u, '?');
+//
+// The following syntax is a shorthand for key-value pairs, where the first argument is the
+// key, and the rest construct the value. The types of the key and value are deduced if the
+// first pair contains exactly two arguments:
+//
+// ... = ftl::init::map<int, std::string>(-1, "abc")(-2)(-3, 3u, '?');
+//
+// ... = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
+//
+// WARNING: The InitializerList returned by an ftl::init::list expression must be consumed
+// immediately, since temporary arguments are destroyed after the full expression. Storing
+// an InitializerList results in dangling references.
+//
+template <typename T, typename Sizes = std::index_sequence<>, typename... Types>
+struct InitializerList;
+
+template <typename T, std::size_t... Sizes, typename... Types>
+struct InitializerList<T, std::index_sequence<Sizes...>, Types...> {
+ // Creates a superset InitializerList by appending the number of arguments to Sizes, and
+ // expanding Types with forwarding references for each argument.
+ template <typename... Args>
+ [[nodiscard]] constexpr auto operator()(Args&&... args) && -> InitializerList<
+ T, std::index_sequence<Sizes..., sizeof...(Args)>, Types..., Args&&...> {
+ return {std::tuple_cat(std::move(tuple), std::forward_as_tuple(std::forward<Args>(args)...))};
+ }
+
+ // The temporary InitializerList returned by operator() is bound to an rvalue reference in
+ // container constructors, which extends the lifetime of any temporary arguments that this
+ // tuple refers to until the completion of the full expression containing the construction.
+ std::tuple<Types...> tuple;
+};
+
+template <typename K, typename V>
+struct KeyValue {};
+
+// Shorthand for key-value pairs that assigns the first argument to the key, and the rest to the
+// value. The specialization is on KeyValue rather than std::pair, so that ftl::init::list works
+// with the latter.
+template <typename K, typename V, std::size_t... Sizes, typename... Types>
+struct InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...> {
+ // Accumulate the three arguments to std::pair's piecewise constructor.
+ template <typename... Args>
+ [[nodiscard]] constexpr auto operator()(K&& k, Args&&... args) && -> InitializerList<
+ KeyValue<K, V>, std::index_sequence<Sizes..., 3>, Types..., std::piecewise_construct_t,
+ std::tuple<K&&>, std::tuple<Args&&...>> {
+ return {std::tuple_cat(
+ std::move(tuple),
+ std::forward_as_tuple(std::piecewise_construct, std::forward_as_tuple(std::forward<K>(k)),
+ std::forward_as_tuple(std::forward<Args>(args)...)))};
+ }
+
+ std::tuple<Types...> tuple;
+};
+
+namespace init {
+
+template <typename T, typename... Args>
+[[nodiscard]] constexpr auto list(Args&&... args) {
+ return InitializerList<T>{}(std::forward<Args>(args)...);
+}
+
+template <typename K, typename V, typename... Args>
+[[nodiscard]] constexpr auto map(Args&&... args) {
+ return list<KeyValue<K, V>>(std::forward<Args>(args)...);
+}
+
+template <typename K, typename V>
+[[nodiscard]] constexpr auto map(K&& k, V&& v) {
+ return list<KeyValue<K, V>>(std::forward<K>(k), std::forward<V>(v));
+}
+
+} // namespace init
+} // namespace android::ftl
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
new file mode 100644
index 0000000..84c15eb
--- /dev/null
+++ b/include/ftl/small_map.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/initializer_list.h>
+#include <ftl/small_vector.h>
+
+#include <functional>
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+namespace android::ftl {
+
+// Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
+// stored in contiguous storage for cache efficiency. The map is allocated statically until its size
+// exceeds N, at which point mappings are relocated to dynamic memory.
+//
+// SmallMap<K, V, 0> unconditionally allocates on the heap.
+//
+// Example usage:
+//
+// ftl::SmallMap<int, std::string, 3> map;
+// assert(map.empty());
+// assert(!map.dynamic());
+//
+// map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
+// assert(map.size() == 3u);
+// assert(!map.dynamic());
+//
+// assert(map.contains(123));
+// assert(map.find(42, [](const std::string& s) { return s.size(); }) == 3u);
+//
+// const auto opt = map.find(-1);
+// assert(opt);
+//
+// std::string& ref = *opt;
+// assert(ref.empty());
+// ref = "xyz";
+//
+// assert(map == SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
+//
+template <typename K, typename V, std::size_t N>
+class SmallMap final {
+ using Map = SmallVector<std::pair<const K, V>, N>;
+
+ public:
+ using key_type = K;
+ using mapped_type = V;
+
+ using value_type = typename Map::value_type;
+ using size_type = typename Map::size_type;
+ using difference_type = typename Map::difference_type;
+
+ using reference = typename Map::reference;
+ using iterator = typename Map::iterator;
+
+ using const_reference = typename Map::const_reference;
+ using const_iterator = typename Map::const_iterator;
+
+ // Creates an empty map.
+ SmallMap() = default;
+
+ // Constructs at most N key-value pairs in place by forwarding per-pair constructor arguments.
+ // The template arguments K, V, and N are inferred using the deduction guide defined below.
+ // The syntax for listing pairs is as follows:
+ //
+ // ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
+ //
+ // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
+ // assert(map.size() == 3u);
+ // assert(map.contains(-1) && map.find(-1)->get().empty());
+ // assert(map.contains(42) && map.find(42)->get() == "???");
+ // assert(map.contains(123) && map.find(123)->get() == "abc");
+ //
+ // The types of the key and value are deduced if the first pair contains exactly two arguments:
+ //
+ // ftl::SmallMap map = ftl::init::map(0, 'a')(1, 'b')(2, 'c');
+ // static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, char, 3>>);
+ //
+ template <typename U, std::size_t... Sizes, typename... Types>
+ SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
+ : map_(std::move(list)) {
+ // TODO: Enforce unique keys.
+ }
+
+ size_type max_size() const { return map_.max_size(); }
+ size_type size() const { return map_.size(); }
+ bool empty() const { return map_.empty(); }
+
+ // Returns whether the map is backed by static or dynamic storage.
+ bool dynamic() const { return map_.dynamic(); }
+
+ iterator begin() { return map_.begin(); }
+ const_iterator begin() const { return cbegin(); }
+ const_iterator cbegin() const { return map_.cbegin(); }
+
+ iterator end() { return map_.end(); }
+ const_iterator end() const { return cend(); }
+ const_iterator cend() const { return map_.cend(); }
+
+ // Returns whether a mapping exists for the given key.
+ bool contains(const key_type& key) const {
+ return find(key, [](const mapped_type&) {});
+ }
+
+ // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
+ //
+ // ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
+ //
+ // const auto opt = map.find('c');
+ // assert(opt == 'C');
+ //
+ // char d = 'd';
+ // const auto ref = map.find('d').value_or(std::ref(d));
+ // ref.get() = 'D';
+ // assert(d == 'D');
+ //
+ auto find(const key_type& key) const -> std::optional<std::reference_wrapper<const mapped_type>> {
+ return find(key, [](const mapped_type& v) { return std::cref(v); });
+ }
+
+ auto find(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
+ return find(key, [](mapped_type& v) { return std::ref(v); });
+ }
+
+ // Returns the result R of a unary operation F on (a constant or mutable reference to) the value
+ // for the given key, or std::nullopt if the key was not found. If F has a return type of void,
+ // then the Boolean result indicates whether the key was found.
+ //
+ // ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
+ //
+ // assert(map.find('c', [](char c) { return std::toupper(c); }) == 'Z');
+ // assert(map.find('c', [](char& c) { c = std::toupper(c); }));
+ //
+ template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
+ auto find(const key_type& key, F f) const
+ -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
+ for (auto& [k, v] : *this) {
+ if (k == key) {
+ if constexpr (std::is_void_v<R>) {
+ f(v);
+ return true;
+ } else {
+ return f(v);
+ }
+ }
+ }
+
+ return {};
+ }
+
+ template <typename F>
+ auto find(const key_type& key, F f) {
+ return std::as_const(*this).find(
+ key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(v)); });
+ }
+
+ private:
+ Map map_;
+};
+
+// Deduction guide for in-place constructor.
+template <typename K, typename V, std::size_t... Sizes, typename... Types>
+SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&)
+ -> SmallMap<K, V, sizeof...(Sizes)>;
+
+// Returns whether the key-value pairs of two maps are equal.
+template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
+bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
+ if (lhs.size() != rhs.size()) return false;
+
+ for (const auto& [k, v] : lhs) {
+ const auto& lv = v;
+ if (!rhs.find(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// TODO: Remove in C++20.
+template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
+inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
new file mode 100644
index 0000000..cb0ae35
--- /dev/null
+++ b/include/ftl/small_vector.h
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/array_traits.h>
+#include <ftl/static_vector.h>
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace android::ftl {
+
+template <typename>
+struct is_small_vector;
+
+// ftl::StaticVector that promotes to std::vector when full. SmallVector is a drop-in replacement
+// for std::vector with statically allocated storage for N elements, whose goal is to improve run
+// time by avoiding heap allocation and increasing probability of cache hits. The standard API is
+// augmented by an unstable_erase operation that does not preserve order, and a replace operation
+// that destructively emplaces.
+//
+// SmallVector<T, 0> is a specialization that thinly wraps std::vector.
+//
+// Example usage:
+//
+// ftl::SmallVector<char, 3> vector;
+// assert(vector.empty());
+// assert(!vector.dynamic());
+//
+// vector = {'a', 'b', 'c'};
+// assert(vector.size() == 3u);
+// assert(!vector.dynamic());
+//
+// vector.push_back('d');
+// assert(vector.dynamic());
+//
+// vector.unstable_erase(vector.begin());
+// assert(vector == (ftl::SmallVector{'d', 'b', 'c'}));
+//
+// vector.pop_back();
+// assert(vector.back() == 'b');
+// assert(vector.dynamic());
+//
+// const char array[] = "hi";
+// vector = ftl::SmallVector(array);
+// assert(vector == (ftl::SmallVector{'h', 'i', '\0'}));
+// assert(!vector.dynamic());
+//
+// ftl::SmallVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
+// assert(strings.size() == 3u);
+// assert(!strings.dynamic());
+//
+// assert(strings[0] == "abc");
+// assert(strings[1] == "123");
+// assert(strings[2] == "???");
+//
+template <typename T, std::size_t N>
+class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
+ using Static = StaticVector<T, N>;
+ using Dynamic = SmallVector<T, 0>;
+
+ // TODO: Replace with std::remove_cvref_t in C++20.
+ template <typename U>
+ using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
+
+ public:
+ FTL_ARRAY_TRAIT(T, value_type);
+ FTL_ARRAY_TRAIT(T, size_type);
+ FTL_ARRAY_TRAIT(T, difference_type);
+
+ FTL_ARRAY_TRAIT(T, pointer);
+ FTL_ARRAY_TRAIT(T, reference);
+ FTL_ARRAY_TRAIT(T, iterator);
+ FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+ FTL_ARRAY_TRAIT(T, const_pointer);
+ FTL_ARRAY_TRAIT(T, const_reference);
+ FTL_ARRAY_TRAIT(T, const_iterator);
+ FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+ // Creates an empty vector.
+ SmallVector() = default;
+
+ // Constructs at most N elements. See StaticVector for underlying constructors.
+ template <typename Arg, typename... Args,
+ typename = std::enable_if_t<!is_small_vector<remove_cvref_t<Arg>>{}>>
+ SmallVector(Arg&& arg, Args&&... args)
+ : vector_(std::in_place_type<Static>, std::forward<Arg>(arg), std::forward<Args>(args)...) {}
+
+ // Copies at most N elements from a smaller convertible vector.
+ template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
+ SmallVector(const SmallVector<U, M>& other)
+ : SmallVector(kIteratorRange, other.begin(), other.end()) {}
+
+ void swap(SmallVector& other) { vector_.swap(other.vector_); }
+
+ // Returns whether the vector is backed by static or dynamic storage.
+ bool dynamic() const { return std::holds_alternative<Dynamic>(vector_); }
+
+ // Avoid std::visit as it generates a dispatch table.
+#define DISPATCH(T, F, ...) \
+ T F() __VA_ARGS__ { \
+ return dynamic() ? std::get<Dynamic>(vector_).F() : std::get<Static>(vector_).F(); \
+ }
+
+ DISPATCH(size_type, max_size, const)
+ DISPATCH(size_type, size, const)
+ DISPATCH(bool, empty, const)
+
+ // noexcept to suppress warning about zero variadic macro arguments.
+ DISPATCH(iterator, begin, noexcept)
+ DISPATCH(const_iterator, begin, const)
+ DISPATCH(const_iterator, cbegin, const)
+
+ DISPATCH(iterator, end, noexcept)
+ DISPATCH(const_iterator, end, const)
+ DISPATCH(const_iterator, cend, const)
+
+ DISPATCH(reverse_iterator, rbegin, noexcept)
+ DISPATCH(const_reverse_iterator, rbegin, const)
+ DISPATCH(const_reverse_iterator, crbegin, const)
+
+ DISPATCH(reverse_iterator, rend, noexcept)
+ DISPATCH(const_reverse_iterator, rend, const)
+ DISPATCH(const_reverse_iterator, crend, const)
+
+ DISPATCH(iterator, last, noexcept)
+ DISPATCH(const_iterator, last, const)
+
+ DISPATCH(reference, front, noexcept)
+ DISPATCH(const_reference, front, const)
+
+ DISPATCH(reference, back, noexcept)
+ DISPATCH(const_reference, back, const)
+
+#undef DISPATCH
+
+ reference operator[](size_type i) {
+ return dynamic() ? std::get<Dynamic>(vector_)[i] : std::get<Static>(vector_)[i];
+ }
+
+ const_reference operator[](size_type i) const { return const_cast<SmallVector&>(*this)[i]; }
+
+ // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
+ // replacing at end() is erroneous.
+ //
+ // The element is emplaced via move constructor, so type T does not need to define copy/move
+ // assignment, e.g. its data members may be const.
+ //
+ // The arguments may directly or indirectly refer to the element being replaced.
+ //
+ // Iterators to the replaced element point to its replacement, and others remain valid.
+ //
+ template <typename... Args>
+ reference replace(const_iterator it, Args&&... args) {
+ if (dynamic()) {
+ return std::get<Dynamic>(vector_).replace(it, std::forward<Args>(args)...);
+ } else {
+ return std::get<Static>(vector_).replace(it, std::forward<Args>(args)...);
+ }
+ }
+
+ // Appends an element, and returns a reference to it.
+ //
+ // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+ // Otherwise, only the end() iterator is invalidated.
+ //
+ template <typename... Args>
+ reference emplace_back(Args&&... args) {
+ constexpr auto kInsertStatic = &Static::template emplace_back<Args...>;
+ constexpr auto kInsertDynamic = &Dynamic::template emplace_back<Args...>;
+ return *insert<kInsertStatic, kInsertDynamic>(std::forward<Args>(args)...);
+ }
+
+ // Appends an element.
+ //
+ // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+ // Otherwise, only the end() iterator is invalidated.
+ //
+ void push_back(const value_type& v) {
+ constexpr auto kInsertStatic =
+ static_cast<bool (Static::*)(const value_type&)>(&Static::push_back);
+ constexpr auto kInsertDynamic =
+ static_cast<bool (Dynamic::*)(const value_type&)>(&Dynamic::push_back);
+ insert<kInsertStatic, kInsertDynamic>(v);
+ }
+
+ void push_back(value_type&& v) {
+ constexpr auto kInsertStatic = static_cast<bool (Static::*)(value_type &&)>(&Static::push_back);
+ constexpr auto kInsertDynamic =
+ static_cast<bool (Dynamic::*)(value_type &&)>(&Dynamic::push_back);
+ insert<kInsertStatic, kInsertDynamic>(std::move(v));
+ }
+
+ // Removes the last element. The vector must not be empty, or the call is erroneous.
+ //
+ // The last() and end() iterators are invalidated.
+ //
+ void pop_back() {
+ if (dynamic()) {
+ std::get<Dynamic>(vector_).pop_back();
+ } else {
+ std::get<Static>(vector_).pop_back();
+ }
+ }
+
+ // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
+ // this moves the last element to the slot of the erased element.
+ //
+ // The last() and end() iterators, as well as those to the erased element, are invalidated.
+ //
+ void unstable_erase(iterator it) {
+ if (dynamic()) {
+ std::get<Dynamic>(vector_).unstable_erase(it);
+ } else {
+ std::get<Static>(vector_).unstable_erase(it);
+ }
+ }
+
+ private:
+ template <auto InsertStatic, auto InsertDynamic, typename... Args>
+ auto insert(Args&&... args) {
+ if (Dynamic* const vector = std::get_if<Dynamic>(&vector_)) {
+ return (vector->*InsertDynamic)(std::forward<Args>(args)...);
+ }
+
+ auto& vector = std::get<Static>(vector_);
+ if (vector.full()) {
+ return (promote(vector).*InsertDynamic)(std::forward<Args>(args)...);
+ } else {
+ return (vector.*InsertStatic)(std::forward<Args>(args)...);
+ }
+ }
+
+ Dynamic& promote(Static& static_vector) {
+ assert(static_vector.full());
+
+ // Allocate double capacity to reduce probability of reallocation.
+ Dynamic vector;
+ vector.reserve(Static::max_size() * 2);
+ std::move(static_vector.begin(), static_vector.end(), std::back_inserter(vector));
+
+ return vector_.template emplace<Dynamic>(std::move(vector));
+ }
+
+ std::variant<Static, Dynamic> vector_;
+};
+
+// Partial specialization without static storage.
+template <typename T>
+class SmallVector<T, 0> final : ArrayTraits<T>,
+ ArrayIterators<SmallVector<T, 0>, T>,
+ std::vector<T> {
+ using ArrayTraits<T>::construct_at;
+
+ using Iter = ArrayIterators<SmallVector, T>;
+ using Impl = std::vector<T>;
+
+ friend Iter;
+
+ public:
+ FTL_ARRAY_TRAIT(T, value_type);
+ FTL_ARRAY_TRAIT(T, size_type);
+ FTL_ARRAY_TRAIT(T, difference_type);
+
+ FTL_ARRAY_TRAIT(T, pointer);
+ FTL_ARRAY_TRAIT(T, reference);
+ FTL_ARRAY_TRAIT(T, iterator);
+ FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+ FTL_ARRAY_TRAIT(T, const_pointer);
+ FTL_ARRAY_TRAIT(T, const_reference);
+ FTL_ARRAY_TRAIT(T, const_iterator);
+ FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+ using Impl::Impl;
+
+ using Impl::empty;
+ using Impl::max_size;
+ using Impl::size;
+
+ using Impl::reserve;
+
+ // std::vector iterators are not necessarily raw pointers.
+ iterator begin() { return Impl::data(); }
+ iterator end() { return Impl::data() + size(); }
+
+ using Iter::begin;
+ using Iter::end;
+
+ using Iter::cbegin;
+ using Iter::cend;
+
+ using Iter::rbegin;
+ using Iter::rend;
+
+ using Iter::crbegin;
+ using Iter::crend;
+
+ using Iter::last;
+
+ using Iter::back;
+ using Iter::front;
+
+ using Iter::operator[];
+
+ template <typename... Args>
+ reference replace(const_iterator it, Args&&... args) {
+ value_type element{std::forward<Args>(args)...};
+ std::destroy_at(it);
+ // This is only safe because exceptions are disabled.
+ return *construct_at(it, std::move(element));
+ }
+
+ template <typename... Args>
+ iterator emplace_back(Args&&... args) {
+ return &Impl::emplace_back(std::forward<Args>(args)...);
+ }
+
+ bool push_back(const value_type& v) {
+ Impl::push_back(v);
+ return true;
+ }
+
+ bool push_back(value_type&& v) {
+ Impl::push_back(std::move(v));
+ return true;
+ }
+
+ using Impl::pop_back;
+
+ void unstable_erase(iterator it) {
+ if (it != last()) std::iter_swap(it, last());
+ pop_back();
+ }
+
+ void swap(SmallVector& other) { Impl::swap(other); }
+};
+
+template <typename>
+struct is_small_vector : std::false_type {};
+
+template <typename T, std::size_t N>
+struct is_small_vector<SmallVector<T, N>> : std::true_type {};
+
+// Deduction guide for array constructor.
+template <typename T, std::size_t N>
+SmallVector(T (&)[N]) -> SmallVector<std::remove_cv_t<T>, N>;
+
+// Deduction guide for variadic constructor.
+template <typename T, typename... Us, typename V = std::decay_t<T>,
+ typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
+SmallVector(T&&, Us&&...) -> SmallVector<V, 1 + sizeof...(Us)>;
+
+// Deduction guide for in-place constructor.
+template <typename T, std::size_t... Sizes, typename... Types>
+SmallVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
+ -> SmallVector<T, sizeof...(Sizes)>;
+
+// Deduction guide for StaticVector conversion.
+template <typename T, std::size_t N>
+SmallVector(StaticVector<T, N>&&) -> SmallVector<T, N>;
+
+template <typename T, std::size_t N>
+inline void swap(SmallVector<T, N>& lhs, SmallVector<T, N>& rhs) {
+ lhs.swap(rhs);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/static_vector.h b/include/ftl/static_vector.h
new file mode 100644
index 0000000..96a1ae8
--- /dev/null
+++ b/include/ftl/static_vector.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/array_traits.h>
+#include <ftl/initializer_list.h>
+
+#include <algorithm>
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace android::ftl {
+
+constexpr struct IteratorRangeTag {
+} kIteratorRange;
+
+// Fixed-capacity, statically allocated counterpart of std::vector. Like std::array, StaticVector
+// allocates contiguous storage for N elements of type T at compile time, but stores at most (rather
+// than exactly) N elements. Unlike std::array, its default constructor does not require T to have a
+// default constructor, since elements are constructed in place as the vector grows. Operations that
+// insert an element (emplace_back, push_back, etc.) fail when the vector is full. The API otherwise
+// adheres to standard containers, except the unstable_erase operation that does not preserve order,
+// and the replace operation that destructively emplaces.
+//
+// StaticVector<T, 1> is analogous to an iterable std::optional.
+// StaticVector<T, 0> is an error.
+//
+// Example usage:
+//
+// ftl::StaticVector<char, 3> vector;
+// assert(vector.empty());
+//
+// vector = {'a', 'b'};
+// assert(vector.size() == 2u);
+//
+// vector.push_back('c');
+// assert(vector.full());
+//
+// assert(!vector.push_back('d'));
+// assert(vector.size() == 3u);
+//
+// vector.unstable_erase(vector.begin());
+// assert(vector == (ftl::StaticVector{'c', 'b'}));
+//
+// vector.pop_back();
+// assert(vector.back() == 'c');
+//
+// const char array[] = "hi";
+// vector = ftl::StaticVector(array);
+// assert(vector == (ftl::StaticVector{'h', 'i', '\0'}));
+//
+// ftl::StaticVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
+// assert(strings.size() == 3u);
+// assert(strings[0] == "abc");
+// assert(strings[1] == "123");
+// assert(strings[2] == "???");
+//
+template <typename T, std::size_t N>
+class StaticVector final : ArrayTraits<T>,
+ ArrayIterators<StaticVector<T, N>, T>,
+ ArrayComparators<StaticVector> {
+ static_assert(N > 0);
+
+ using ArrayTraits<T>::construct_at;
+
+ using Iter = ArrayIterators<StaticVector, T>;
+ friend Iter;
+
+ // There is ambiguity when constructing from two iterator-like elements like pointers:
+ // they could be an iterator range, or arguments for in-place construction. Assume the
+ // latter unless they are input iterators and cannot be used to construct elements. If
+ // the former is intended, the caller can pass an IteratorRangeTag to disambiguate.
+ template <typename I, typename Traits = std::iterator_traits<I>>
+ using is_input_iterator =
+ std::conjunction<std::is_base_of<std::input_iterator_tag, typename Traits::iterator_category>,
+ std::negation<std::is_constructible<T, I>>>;
+
+ public:
+ FTL_ARRAY_TRAIT(T, value_type);
+ FTL_ARRAY_TRAIT(T, size_type);
+ FTL_ARRAY_TRAIT(T, difference_type);
+
+ FTL_ARRAY_TRAIT(T, pointer);
+ FTL_ARRAY_TRAIT(T, reference);
+ FTL_ARRAY_TRAIT(T, iterator);
+ FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+ FTL_ARRAY_TRAIT(T, const_pointer);
+ FTL_ARRAY_TRAIT(T, const_reference);
+ FTL_ARRAY_TRAIT(T, const_iterator);
+ FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+ // Creates an empty vector.
+ StaticVector() = default;
+
+ // Copies and moves a vector, respectively.
+ StaticVector(const StaticVector& other)
+ : StaticVector(kIteratorRange, other.begin(), other.end()) {}
+
+ StaticVector(StaticVector&& other) { swap<true>(other); }
+
+ // Copies at most N elements from a smaller convertible vector.
+ template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
+ StaticVector(const StaticVector<U, M>& other)
+ : StaticVector(kIteratorRange, other.begin(), other.end()) {}
+
+ // Copies at most N elements from an array.
+ template <typename U, std::size_t M>
+ explicit StaticVector(U (&array)[M])
+ : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {}
+
+ // Copies at most N elements from the range [first, last).
+ //
+ // IteratorRangeTag disambiguates with initialization from two iterator-like elements.
+ //
+ template <typename Iterator, typename = std::enable_if_t<is_input_iterator<Iterator>{}>>
+ StaticVector(Iterator first, Iterator last) : StaticVector(kIteratorRange, first, last) {
+ using V = typename std::iterator_traits<Iterator>::value_type;
+ static_assert(std::is_constructible_v<value_type, V>, "Incompatible iterator range");
+ }
+
+ template <typename Iterator>
+ StaticVector(IteratorRangeTag, Iterator first, Iterator last)
+ : size_(std::min(max_size(), static_cast<size_type>(std::distance(first, last)))) {
+ std::uninitialized_copy(first, first + size_, begin());
+ }
+
+ // Constructs at most N elements. The template arguments T and N are inferred using the
+ // deduction guide defined below. Note that T is determined from the first element, and
+ // subsequent elements must have convertible types:
+ //
+ // ftl::StaticVector vector = {1, 2, 3};
+ // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<int, 3>>);
+ //
+ // const auto copy = "quince"s;
+ // auto move = "tart"s;
+ // ftl::StaticVector vector = {copy, std::move(move)};
+ //
+ // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<std::string, 2>>);
+ //
+ template <typename E, typename... Es,
+ typename = std::enable_if_t<std::is_constructible_v<value_type, E>>>
+ StaticVector(E&& element, Es&&... elements)
+ : StaticVector(std::index_sequence<0>{}, std::forward<E>(element),
+ std::forward<Es>(elements)...) {
+ static_assert(sizeof...(elements) < N, "Too many elements");
+ }
+
+ // Constructs at most N elements in place by forwarding per-element constructor arguments. The
+ // template arguments T and N are inferred using the deduction guide defined below. The syntax
+ // for listing arguments is as follows:
+ //
+ // ftl::StaticVector vector = ftl::init::list<std::string>("abc")()(3u, '?');
+ //
+ // static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<std::string, 3>>);
+ // assert(vector.full());
+ // assert(vector[0] == "abc");
+ // assert(vector[1].empty());
+ // assert(vector[2] == "???");
+ //
+ template <typename U, std::size_t Size, std::size_t... Sizes, typename... Types>
+ StaticVector(InitializerList<U, std::index_sequence<Size, Sizes...>, Types...>&& list)
+ : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence<Size>{},
+ std::index_sequence<Sizes...>{}, list.tuple) {}
+
+ ~StaticVector() { std::destroy(begin(), end()); }
+
+ StaticVector& operator=(const StaticVector& other) {
+ StaticVector copy(other);
+ swap(copy);
+ return *this;
+ }
+
+ StaticVector& operator=(StaticVector&& other) {
+ std::destroy(begin(), end());
+ size_ = 0;
+ swap<true>(other);
+ return *this;
+ }
+
+ // IsEmpty enables a fast path when the vector is known to be empty at compile time.
+ template <bool IsEmpty = false>
+ void swap(StaticVector&);
+
+ static constexpr size_type max_size() { return N; }
+ size_type size() const { return size_; }
+
+ bool empty() const { return size() == 0; }
+ bool full() const { return size() == max_size(); }
+
+ iterator begin() { return std::launder(reinterpret_cast<pointer>(data_)); }
+ iterator end() { return begin() + size(); }
+
+ using Iter::begin;
+ using Iter::end;
+
+ using Iter::cbegin;
+ using Iter::cend;
+
+ using Iter::rbegin;
+ using Iter::rend;
+
+ using Iter::crbegin;
+ using Iter::crend;
+
+ using Iter::last;
+
+ using Iter::back;
+ using Iter::front;
+
+ using Iter::operator[];
+
+ // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so
+ // replacing at end() is erroneous.
+ //
+ // The element is emplaced via move constructor, so type T does not need to define copy/move
+ // assignment, e.g. its data members may be const.
+ //
+ // The arguments may directly or indirectly refer to the element being replaced.
+ //
+ // Iterators to the replaced element point to its replacement, and others remain valid.
+ //
+ template <typename... Args>
+ reference replace(const_iterator it, Args&&... args) {
+ value_type element{std::forward<Args>(args)...};
+ std::destroy_at(it);
+ // This is only safe because exceptions are disabled.
+ return *construct_at(it, std::move(element));
+ }
+
+ // Appends an element, and returns an iterator to it. If the vector is full, the element is not
+ // inserted, and the end() iterator is returned.
+ //
+ // On success, the end() iterator is invalidated.
+ //
+ template <typename... Args>
+ iterator emplace_back(Args&&... args) {
+ if (full()) return end();
+ const iterator it = construct_at(end(), std::forward<Args>(args)...);
+ ++size_;
+ return it;
+ }
+
+ // Appends an element unless the vector is full, and returns whether the element was inserted.
+ //
+ // On success, the end() iterator is invalidated.
+ //
+ bool push_back(const value_type& v) {
+ // Two statements for sequence point.
+ const iterator it = emplace_back(v);
+ return it != end();
+ }
+
+ bool push_back(value_type&& v) {
+ // Two statements for sequence point.
+ const iterator it = emplace_back(std::move(v));
+ return it != end();
+ }
+
+ // Removes the last element. The vector must not be empty, or the call is erroneous.
+ //
+ // The last() and end() iterators are invalidated.
+ //
+ void pop_back() { unstable_erase(last()); }
+
+ // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
+ // this moves the last element to the slot of the erased element.
+ //
+ // The last() and end() iterators, as well as those to the erased element, are invalidated.
+ //
+ void unstable_erase(const_iterator it) {
+ std::destroy_at(it);
+ if (it != last()) {
+ // Move last element and destroy its source for destructor side effects. This is only
+ // safe because exceptions are disabled.
+ construct_at(it, std::move(back()));
+ std::destroy_at(last());
+ }
+ --size_;
+ }
+
+ private:
+ // Recursion for variadic constructor.
+ template <std::size_t I, typename E, typename... Es>
+ StaticVector(std::index_sequence<I>, E&& element, Es&&... elements)
+ : StaticVector(std::index_sequence<I + 1>{}, std::forward<Es>(elements)...) {
+ construct_at(begin() + I, std::forward<E>(element));
+ }
+
+ // Base case for variadic constructor.
+ template <std::size_t I>
+ explicit StaticVector(std::index_sequence<I>) : size_(I) {}
+
+ // Recursion for in-place constructor.
+ //
+ // Construct element I by extracting its arguments from the InitializerList tuple. ArgIndex
+ // is the position of its first argument in Args, and ArgCount is the number of arguments.
+ // The Indices sequence corresponds to [0, ArgCount).
+ //
+ // The Sizes sequence lists the argument counts for elements after I, so Size is the ArgCount
+ // for the next element. The recursion stops when Sizes is empty for the last element.
+ //
+ template <std::size_t I, std::size_t ArgIndex, std::size_t ArgCount, std::size_t... Indices,
+ std::size_t Size, std::size_t... Sizes, typename... Args>
+ StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
+ std::index_sequence<Size, Sizes...>, std::tuple<Args...>& tuple)
+ : StaticVector(std::index_sequence<I + 1, ArgIndex + ArgCount, Size>{},
+ std::make_index_sequence<Size>{}, std::index_sequence<Sizes...>{}, tuple) {
+ construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
+ }
+
+ // Base case for in-place constructor.
+ template <std::size_t I, std::size_t ArgIndex, std::size_t ArgCount, std::size_t... Indices,
+ typename... Args>
+ StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
+ std::index_sequence<>, std::tuple<Args...>& tuple)
+ : size_(I + 1) {
+ construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
+ }
+
+ size_type size_ = 0;
+ std::aligned_storage_t<sizeof(value_type), alignof(value_type)> data_[N];
+};
+
+// Deduction guide for array constructor.
+template <typename T, std::size_t N>
+StaticVector(T (&)[N]) -> StaticVector<std::remove_cv_t<T>, N>;
+
+// Deduction guide for variadic constructor.
+template <typename T, typename... Us, typename V = std::decay_t<T>,
+ typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
+StaticVector(T&&, Us&&...) -> StaticVector<V, 1 + sizeof...(Us)>;
+
+// Deduction guide for in-place constructor.
+template <typename T, std::size_t... Sizes, typename... Types>
+StaticVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
+ -> StaticVector<T, sizeof...(Sizes)>;
+
+template <typename T, std::size_t N>
+template <bool IsEmpty>
+void StaticVector<T, N>::swap(StaticVector& other) {
+ auto [to, from] = std::make_pair(this, &other);
+ if (from == this) return;
+
+ // Assume this vector has fewer elements, so the excess of the other vector will be moved to it.
+ auto [min, max] = std::make_pair(size(), other.size());
+
+ // No elements to swap if moving into an empty vector.
+ if constexpr (IsEmpty) {
+ assert(min == 0);
+ } else {
+ if (min > max) {
+ std::swap(from, to);
+ std::swap(min, max);
+ }
+
+ // Swap elements [0, min).
+ std::swap_ranges(begin(), begin() + min, other.begin());
+
+ // No elements to move if sizes are equal.
+ if (min == max) return;
+ }
+
+ // Move elements [min, max) and destroy their source for destructor side effects.
+ const auto [first, last] = std::make_pair(from->begin() + min, from->begin() + max);
+ std::uninitialized_move(first, last, to->begin() + min);
+ std::destroy(first, last);
+
+ std::swap(size_, other.size_);
+}
+
+template <typename T, std::size_t N>
+inline void swap(StaticVector<T, N>& lhs, StaticVector<T, N>& rhs) {
+ lhs.swap(rhs);
+}
+
+} // namespace android::ftl
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 60638ca..23692e9 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -17,8 +17,10 @@
#ifndef _LIBINPUT_INPUT_DEVICE_H
#define _LIBINPUT_INPUT_DEVICE_H
+#include <android/sensor.h>
#include <input/Input.h>
#include <input/KeyCharacterMap.h>
+#include <unordered_map>
#include <vector>
namespace android {
@@ -63,6 +65,97 @@
std::string getCanonicalName() const;
};
+/* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
+enum class InputDeviceSensorType : int32_t {
+ ACCELEROMETER = ASENSOR_TYPE_ACCELEROMETER,
+ MAGNETIC_FIELD = ASENSOR_TYPE_MAGNETIC_FIELD,
+ ORIENTATION = 3,
+ GYROSCOPE = ASENSOR_TYPE_GYROSCOPE,
+ LIGHT = ASENSOR_TYPE_LIGHT,
+ PRESSURE = ASENSOR_TYPE_PRESSURE,
+ TEMPERATURE = 7,
+ PROXIMITY = ASENSOR_TYPE_PROXIMITY,
+ GRAVITY = ASENSOR_TYPE_GRAVITY,
+ LINEAR_ACCELERATION = ASENSOR_TYPE_LINEAR_ACCELERATION,
+ ROTATION_VECTOR = ASENSOR_TYPE_ROTATION_VECTOR,
+ RELATIVE_HUMIDITY = ASENSOR_TYPE_RELATIVE_HUMIDITY,
+ AMBIENT_TEMPERATURE = ASENSOR_TYPE_AMBIENT_TEMPERATURE,
+ MAGNETIC_FIELD_UNCALIBRATED = ASENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED,
+ GAME_ROTATION_VECTOR = ASENSOR_TYPE_GAME_ROTATION_VECTOR,
+ GYROSCOPE_UNCALIBRATED = ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED,
+ SIGNIFICANT_MOTION = ASENSOR_TYPE_SIGNIFICANT_MOTION,
+};
+
+enum class InputDeviceSensorAccuracy : int32_t {
+ ACCURACY_NONE = 0,
+ ACCURACY_LOW = 1,
+ ACCURACY_MEDIUM = 2,
+ ACCURACY_HIGH = 3,
+};
+
+enum class InputDeviceSensorReportingMode : int32_t {
+ CONTINUOUS = 0,
+ ON_CHANGE = 1,
+ ONE_SHOT = 2,
+ SPECIAL_TRIGGER = 3,
+};
+
+struct InputDeviceSensorInfo {
+ explicit InputDeviceSensorInfo(std::string name, std::string vendor, int32_t version,
+ InputDeviceSensorType type, InputDeviceSensorAccuracy accuracy,
+ float maxRange, float resolution, float power, int32_t minDelay,
+ int32_t fifoReservedEventCount, int32_t fifoMaxEventCount,
+ std::string stringType, int32_t maxDelay, int32_t flags,
+ int32_t id)
+ : name(name),
+ vendor(vendor),
+ version(version),
+ type(type),
+ accuracy(accuracy),
+ maxRange(maxRange),
+ resolution(resolution),
+ power(power),
+ minDelay(minDelay),
+ fifoReservedEventCount(fifoReservedEventCount),
+ fifoMaxEventCount(fifoMaxEventCount),
+ stringType(stringType),
+ maxDelay(maxDelay),
+ flags(flags),
+ id(id) {}
+ // Name string of the sensor.
+ std::string name;
+ // Vendor string of this sensor.
+ std::string vendor;
+ // Version of the sensor's module.
+ int32_t version;
+ // Generic type of this sensor.
+ InputDeviceSensorType type;
+ // The current accuracy of sensor event.
+ InputDeviceSensorAccuracy accuracy;
+ // Maximum range of the sensor in the sensor's unit.
+ float maxRange;
+ // Resolution of the sensor in the sensor's unit.
+ float resolution;
+ // The power in mA used by this sensor while in use.
+ float power;
+ // The minimum delay allowed between two events in microsecond or zero if this sensor only
+ // returns a value when the data it's measuring changes.
+ int32_t minDelay;
+ // Number of events reserved for this sensor in the batch mode FIFO.
+ int32_t fifoReservedEventCount;
+ // Maximum number of events of this sensor that could be batched.
+ int32_t fifoMaxEventCount;
+ // The type of this sensor as a string.
+ std::string stringType;
+ // The delay between two sensor events corresponding to the lowest frequency that this sensor
+ // supports.
+ int32_t maxDelay;
+ // Sensor flags
+ int32_t flags;
+ // Sensor id, same as the input device ID it belongs to.
+ int32_t id;
+};
+
/*
* Describes the characteristics and capabilities of an input device.
*/
@@ -104,6 +197,7 @@
void addMotionRange(int32_t axis, uint32_t source,
float min, float max, float flat, float fuzz, float resolution);
void addMotionRange(const MotionRange& range);
+ void addSensorInfo(const InputDeviceSensorInfo& info);
inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
inline int32_t getKeyboardType() const { return mKeyboardType; }
@@ -122,10 +216,17 @@
inline void setButtonUnderPad(bool hasButton) { mHasButtonUnderPad = hasButton; }
inline bool hasButtonUnderPad() const { return mHasButtonUnderPad; }
+ inline void setHasSensor(bool hasSensor) { mHasSensor = hasSensor; }
+ inline bool hasSensor() const { return mHasSensor; }
+
inline const std::vector<MotionRange>& getMotionRanges() const {
return mMotionRanges;
}
+ const InputDeviceSensorInfo* getSensorInfo(InputDeviceSensorType type);
+
+ const std::vector<InputDeviceSensorType> getSensorTypes();
+
private:
int32_t mId;
int32_t mGeneration;
@@ -139,8 +240,10 @@
std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
bool mHasVibrator;
bool mHasButtonUnderPad;
+ bool mHasSensor;
std::vector<MotionRange> mMotionRanges;
+ std::unordered_map<InputDeviceSensorType, InputDeviceSensorInfo> mSensors;
};
/* Types of input device configuration files. */
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 23f8ddf..451ca3c 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -142,6 +142,8 @@
void writeToParcel(Parcel* parcel) const;
#endif
+ bool operator==(const KeyCharacterMap& other) const;
+
KeyCharacterMap(const KeyCharacterMap& other);
virtual ~KeyCharacterMap();
diff --git a/include/input/KeyLayoutMap.h b/include/input/KeyLayoutMap.h
index 872dd45..b2bd535 100644
--- a/include/input/KeyLayoutMap.h
+++ b/include/input/KeyLayoutMap.h
@@ -24,6 +24,8 @@
#include <utils/RefBase.h>
#include <utils/Tokenizer.h>
+#include <input/InputDevice.h>
+
namespace android {
struct AxisInfo {
@@ -76,6 +78,8 @@
status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
const std::string getLoadFileName() const;
+ // Return pair of sensor type and sensor data index, for the input device abs code
+ base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t absCode);
virtual ~KeyLayoutMap();
@@ -89,12 +93,17 @@
int32_t ledCode;
};
+ struct Sensor {
+ InputDeviceSensorType sensorType;
+ int32_t sensorDataIndex;
+ };
KeyedVector<int32_t, Key> mKeysByScanCode;
KeyedVector<int32_t, Key> mKeysByUsageCode;
KeyedVector<int32_t, AxisInfo> mAxes;
KeyedVector<int32_t, Led> mLedsByScanCode;
KeyedVector<int32_t, Led> mLedsByUsageCode;
+ std::unordered_map<int32_t, Sensor> mSensorsByAbsCode;
std::string mLoadFileName;
KeyLayoutMap();
@@ -114,6 +123,7 @@
status_t parseKey();
status_t parseAxis();
status_t parseLed();
+ status_t parseSensor();
};
};
diff --git a/libs/adbd_auth/adbd_auth.cpp b/libs/adbd_auth/adbd_auth.cpp
index dae6eeb..15bd5c3 100644
--- a/libs/adbd_auth/adbd_auth.cpp
+++ b/libs/adbd_auth/adbd_auth.cpp
@@ -282,9 +282,8 @@
LOG(FATAL) << "adbd_auth: unhandled packet type?";
}
- output_queue_.pop_front();
-
ssize_t rc = writev(framework_fd_.get(), iovs, iovcnt);
+ output_queue_.pop_front();
if (rc == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
PLOG(ERROR) << "adbd_auth: failed to write to framework fd";
ReplaceFrameworkFd(unique_fd());
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 90feedd..feaea63 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -83,9 +83,6 @@
// or dessert updates. Instead, apex users should use libbinder_ndk.
apex_available: [
"//apex_available:platform",
- // TODO(b/166468760) remove these three
- "com.android.media.swcodec",
- "test_com.android.media.swcodec",
],
srcs: [
@@ -183,6 +180,14 @@
],
tidy_checks_as_errors: [
"*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc,",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "-misc-no-recursion",
+ "-misc-redundant-expression",
+ "-misc-unused-using-decls",
],
}
diff --git a/libs/binder/LazyServiceRegistrar.cpp b/libs/binder/LazyServiceRegistrar.cpp
index 2e15e50..1173138 100644
--- a/libs/binder/LazyServiceRegistrar.cpp
+++ b/libs/binder/LazyServiceRegistrar.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "log/log_main.h"
#define LOG_TAG "AidlLazyServiceRegistrar"
#include <binder/LazyServiceRegistrar.h>
@@ -37,6 +38,13 @@
bool allowIsolated, int dumpFlags);
void forcePersist(bool persist);
+ void setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback);
+
+ bool tryUnregister();
+
+ void reRegister();
+
protected:
Status onClients(const sp<IBinder>& service, bool clients) override;
@@ -49,6 +57,7 @@
// whether, based on onClients calls, we know we have a client for this
// service or not
bool clients = false;
+ bool registered = true;
};
/**
@@ -62,6 +71,14 @@
*/
void tryShutdown();
+ /**
+ * Try to shutdown the process, unless:
+ * - 'forcePersist' is 'true', or
+ * - The active services count callback returns 'true', or
+ * - Some services have clients.
+ */
+ void maybeTryShutdown();
+
// count of services with clients
size_t mNumConnectedServices;
@@ -69,6 +86,9 @@
std::map<std::string, Service> mRegisteredServices;
bool mForcePersist;
+
+ // Callback used to report the number of services with clients
+ std::function<bool(int)> mActiveServicesCountCallback;
};
class ClientCounterCallback {
@@ -83,6 +103,13 @@
*/
void forcePersist(bool persist);
+ void setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback);
+
+ bool tryUnregister();
+
+ void reRegister();
+
private:
sp<ClientCounterCallbackImpl> mImpl;
};
@@ -131,8 +158,60 @@
void ClientCounterCallbackImpl::forcePersist(bool persist) {
mForcePersist = persist;
- if(!mForcePersist) {
+ if (!mForcePersist && mNumConnectedServices == 0) {
// Attempt a shutdown in case the number of clients hit 0 while the flag was on
+ maybeTryShutdown();
+ }
+}
+
+bool ClientCounterCallbackImpl::tryUnregister() {
+ auto manager = interface_cast<AidlServiceManager>(asBinder(defaultServiceManager()));
+
+ for (auto& [name, entry] : mRegisteredServices) {
+ bool success = manager->tryUnregisterService(name, entry.service).isOk();
+
+ if (!success) {
+ ALOGI("Failed to unregister service %s", name.c_str());
+ return false;
+ }
+ entry.registered = false;
+ }
+
+ return true;
+}
+
+void ClientCounterCallbackImpl::reRegister() {
+ for (auto& [name, entry] : mRegisteredServices) {
+ // re-register entry if not already registered
+ if (entry.registered) {
+ continue;
+ }
+
+ if (!registerService(entry.service, name, entry.allowIsolated,
+ entry.dumpFlags)) {
+ // Must restart. Otherwise, clients will never be able to get a hold of this service.
+ LOG_ALWAYS_FATAL("Bad state: could not re-register services");
+ }
+
+ entry.registered = true;
+ }
+}
+
+void ClientCounterCallbackImpl::maybeTryShutdown() {
+ if (mForcePersist) {
+ ALOGI("Shutdown prevented by forcePersist override flag.");
+ return;
+ }
+
+ bool handledInCallback = false;
+ if (mActiveServicesCountCallback != nullptr) {
+ handledInCallback = mActiveServicesCountCallback(mNumConnectedServices);
+ }
+
+ // If there is no callback defined or the callback did not handle this
+ // client count change event, try to shutdown the process if its services
+ // have no clients.
+ if (!handledInCallback && mNumConnectedServices == 0) {
tryShutdown();
}
}
@@ -162,53 +241,24 @@
ALOGI("Process has %zu (of %zu available) client(s) in use after notification %s has clients: %d",
mNumConnectedServices, mRegisteredServices.size(), name.c_str(), clients);
- tryShutdown();
+ maybeTryShutdown();
return Status::ok();
}
-void ClientCounterCallbackImpl::tryShutdown() {
- if(mNumConnectedServices > 0) {
- // Should only shut down if there are no clients
- return;
- }
+ void ClientCounterCallbackImpl::tryShutdown() {
+ ALOGI("Trying to shut down the service. No clients in use for any service in process.");
- if(mForcePersist) {
- ALOGI("Shutdown prevented by forcePersist override flag.");
- return;
- }
+ if (tryUnregister()) {
+ ALOGI("Unregistered all clients and exiting");
+ exit(EXIT_SUCCESS);
+ }
- ALOGI("Trying to shut down the service. No clients in use for any service in process.");
+ reRegister();
+}
- auto manager = interface_cast<AidlServiceManager>(asBinder(defaultServiceManager()));
-
- auto unRegisterIt = mRegisteredServices.begin();
- for (; unRegisterIt != mRegisteredServices.end(); ++unRegisterIt) {
- auto& entry = (*unRegisterIt);
-
- bool success = manager->tryUnregisterService(entry.first, entry.second.service).isOk();
-
- if (!success) {
- ALOGI("Failed to unregister service %s", entry.first.c_str());
- break;
- }
- }
-
- if (unRegisterIt == mRegisteredServices.end()) {
- ALOGI("Unregistered all clients and exiting");
- exit(EXIT_SUCCESS);
- }
-
- for (auto reRegisterIt = mRegisteredServices.begin(); reRegisterIt != unRegisterIt;
- reRegisterIt++) {
- auto& entry = (*reRegisterIt);
-
- // re-register entry
- if (!registerService(entry.second.service, entry.first, entry.second.allowIsolated,
- entry.second.dumpFlags)) {
- // Must restart. Otherwise, clients will never be able to get a hold of this service.
- ALOGE("Bad state: could not re-register services");
- }
- }
+void ClientCounterCallbackImpl::setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback) {
+ mActiveServicesCountCallback = activeServicesCountCallback;
}
ClientCounterCallback::ClientCounterCallback() {
@@ -224,6 +274,19 @@
mImpl->forcePersist(persist);
}
+void ClientCounterCallback::setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback) {
+ mImpl->setActiveServicesCountCallback(activeServicesCountCallback);
+}
+
+bool ClientCounterCallback::tryUnregister() {
+ return mImpl->tryUnregister();
+}
+
+void ClientCounterCallback::reRegister() {
+ mImpl->reRegister();
+}
+
} // namespace internal
LazyServiceRegistrar::LazyServiceRegistrar() {
@@ -247,5 +310,18 @@
mClientCC->forcePersist(persist);
}
+void LazyServiceRegistrar::setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback) {
+ mClientCC->setActiveServicesCountCallback(activeServicesCountCallback);
+}
+
+bool LazyServiceRegistrar::tryUnregister() {
+ return mClientCC->tryUnregister();
+}
+
+void LazyServiceRegistrar::reRegister() {
+ mClientCC->reRegister();
+}
+
} // namespace hardware
} // namespace android
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 91e465f..87eab52 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -206,7 +206,7 @@
if (proxy == nullptr) {
ALOGE("null proxy");
}
- const int32_t handle = proxy ? proxy->handle() : 0;
+ const int32_t handle = proxy ? proxy->getPrivateAccessorForHandle().handle() : 0;
obj.hdr.type = BINDER_TYPE_HANDLE;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = handle;
@@ -2509,19 +2509,15 @@
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
const binder_size_t* objects, size_t objectsCount, release_func relFunc)
{
- binder_size_t minOffset = 0;
- freeDataNoInit();
- mError = NO_ERROR;
+ freeData();
+
mData = const_cast<uint8_t*>(data);
mDataSize = mDataCapacity = dataSize;
- //ALOGI("setDataReference Setting data size of %p to %lu (pid=%d)", this, mDataSize, getpid());
- mDataPos = 0;
- ALOGV("setDataReference Setting data pos of %p to %zu", this, mDataPos);
mObjects = const_cast<binder_size_t*>(objects);
mObjectsSize = mObjectsCapacity = objectsCount;
- mNextObjectHint = 0;
- mObjectsSorted = false;
mOwner = relFunc;
+
+ binder_size_t minOffset = 0;
for (size_t i = 0; i < mObjectsSize; i++) {
binder_size_t offset = mObjects[i];
if (offset < minOffset) {
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 9aedf28..b5e4dfe 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -124,14 +124,14 @@
{
sp<IBinder> context = getStrongProxyForHandle(0);
- if (context == nullptr) {
- ALOGW("Not able to get context object on %s.", mDriverName.c_str());
+ if (context) {
+ // The root object is special since we get it directly from the driver, it is never
+ // written by Parcell::writeStrongBinder.
+ internal::Stability::markCompilationUnit(context.get());
+ } else {
+ ALOGW("Not able to get context object on %s.", mDriverName.c_str());
}
- // The root object is special since we get it directly from the driver, it is never
- // written by Parcell::writeStrongBinder.
- internal::Stability::markCompilationUnit(context.get());
-
return context;
}
@@ -204,11 +204,11 @@
// that the handle points to. Can only be used by the servicemanager.
//
// Returns -1 in case of failure, otherwise the strong reference count.
-ssize_t ProcessState::getStrongRefCountForNodeByHandle(int32_t handle) {
+ssize_t ProcessState::getStrongRefCountForNode(const sp<BpBinder>& binder) {
binder_node_info_for_ref info;
memset(&info, 0, sizeof(binder_node_info_for_ref));
- info.handle = handle;
+ info.handle = binder->getPrivateAccessorForHandle().handle();
status_t result = ioctl(mDriverFD, BINDER_GET_NODE_INFO_FOR_REF, &info);
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 2735315..22300ac 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -29,6 +29,7 @@
namespace internal {
class Stability;
}
+class ProcessState;
using binder_proxy_limit_callback = void(*)(int);
@@ -37,8 +38,6 @@
public:
static BpBinder* create(int32_t handle);
- int32_t handle() const;
-
virtual const String16& getInterfaceDescriptor() const;
virtual bool isBinderAlive() const;
virtual status_t pingBinder();
@@ -109,7 +108,23 @@
KeyedVector<const void*, entry_t> mObjects;
};
+ class PrivateAccessorForHandle {
+ private:
+ friend BpBinder;
+ friend ::android::Parcel;
+ friend ::android::ProcessState;
+ explicit PrivateAccessorForHandle(const BpBinder* binder) : mBinder(binder) {}
+ int32_t handle() const { return mBinder->handle(); }
+ const BpBinder* mBinder;
+ };
+ const PrivateAccessorForHandle getPrivateAccessorForHandle() const {
+ return PrivateAccessorForHandle(this);
+ }
+
private:
+ friend PrivateAccessorForHandle;
+
+ int32_t handle() const;
BpBinder(int32_t handle,int32_t trackedUid);
virtual ~BpBinder();
virtual void onFirstRef();
diff --git a/libs/binder/include/binder/LazyServiceRegistrar.h b/libs/binder/include/binder/LazyServiceRegistrar.h
index d18c88e..73893c7 100644
--- a/libs/binder/include/binder/LazyServiceRegistrar.h
+++ b/libs/binder/include/binder/LazyServiceRegistrar.h
@@ -16,6 +16,8 @@
#pragma once
+#include <functional>
+
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <utils/StrongPointer.h>
@@ -53,6 +55,40 @@
*/
void forcePersist(bool persist);
+ /**
+ * Set a callback that is executed when the total number of services with
+ * clients changes.
+ * The callback takes an argument, which is the number of registered
+ * lazy services for this process which have clients.
+ *
+ * Callback return value:
+ * - false: Default behavior for lazy services (shut down the process if there
+ * are no clients).
+ * - true: Don't shut down the process even if there are no clients.
+ *
+ * This callback gives a chance to:
+ * 1 - Perform some additional operations before exiting;
+ * 2 - Prevent the process from exiting by returning "true" from the
+ * callback.
+ *
+ * This method should be called before 'registerService' to avoid races.
+ */
+ void setActiveServicesCountCallback(const std::function<bool(int)>&
+ activeServicesCountCallback);
+
+ /**
+ * Try to unregister all services previously registered with 'registerService'.
+ * Returns 'true' if successful.
+ */
+ bool tryUnregister();
+
+ /**
+ * Re-register services that were unregistered by 'tryUnregister'.
+ * This method should be called in the case 'tryUnregister' fails
+ * (and should be called on the same thread).
+ */
+ void reRegister();
+
private:
std::shared_ptr<internal::ClientCounterCallback> mClientCC;
LazyServiceRegistrar();
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 9f5260a..b49951b 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -504,9 +504,6 @@
const binder_size_t* objects, size_t objectsCount,
release_func relFunc);
- Parcel(const Parcel& o);
- Parcel& operator=(const Parcel& o);
-
status_t finishWrite(size_t len);
void releaseObjects();
void acquireObjects();
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index ce5027e..ff0a686 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -52,80 +52,80 @@
}
template <typename T>
- bool setParcelable(T&& p) {
+ status_t setParcelable(T&& p) {
using Tt = typename std::decay<T>::type;
return setParcelable<Tt>(std::make_shared<Tt>(std::forward<T>(p)));
}
template <typename T>
- bool setParcelable(std::shared_ptr<T> p) {
+ status_t setParcelable(std::shared_ptr<T> p) {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
if (p && this->getStability() > p->getStability()) {
- return false;
+ return android::BAD_VALUE;
}
this->mParcelable = p;
this->mParcelableName = T::getParcelableDescriptor();
this->mParcelPtr = nullptr;
- return true;
+ return android::OK;
}
template <typename T>
- std::shared_ptr<T> getParcelable() const {
+ status_t getParcelable(std::shared_ptr<T>* ret) const {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
const std::string& parcelableDesc = T::getParcelableDescriptor();
if (!this->mParcelPtr) {
if (!this->mParcelable || !this->mParcelableName) {
ALOGD("empty ParcelableHolder");
- return nullptr;
+ *ret = nullptr;
+ return android::OK;
} else if (parcelableDesc != *mParcelableName) {
ALOGD("extension class name mismatch expected:%s actual:%s",
mParcelableName->c_str(), parcelableDesc.c_str());
- return nullptr;
+ *ret = nullptr;
+ return android::BAD_VALUE;
}
- return std::shared_ptr<T>(mParcelable, reinterpret_cast<T*>(mParcelable.get()));
+ *ret = std::shared_ptr<T>(mParcelable, reinterpret_cast<T*>(mParcelable.get()));
+ return android::OK;
}
this->mParcelPtr->setDataPosition(0);
status_t status = this->mParcelPtr->readUtf8FromUtf16(&this->mParcelableName);
if (status != android::OK || parcelableDesc != this->mParcelableName) {
this->mParcelableName = std::nullopt;
- return nullptr;
+ *ret = nullptr;
+ return status;
}
this->mParcelable = std::make_shared<T>();
status = mParcelable.get()->readFromParcel(this->mParcelPtr.get());
if (status != android::OK) {
this->mParcelableName = std::nullopt;
this->mParcelable = nullptr;
- return nullptr;
+ *ret = nullptr;
+ return status;
}
this->mParcelPtr = nullptr;
- return std::shared_ptr<T>(mParcelable, reinterpret_cast<T*>(mParcelable.get()));
+ *ret = std::shared_ptr<T>(mParcelable, reinterpret_cast<T*>(mParcelable.get()));
+ return android::OK;
}
Stability getStability() const override { return mStability; }
inline bool operator!=(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) !=
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this != &rhs;
}
inline bool operator<(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) <
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this < &rhs;
}
inline bool operator<=(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) <=
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this <= &rhs;
}
inline bool operator==(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) ==
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this == &rhs;
}
inline bool operator>(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) >
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this > &rhs;
}
inline bool operator>=(const ParcelableHolder& rhs) const {
- return std::tie(mParcelable, mParcelPtr, mStability) >=
- std::tie(rhs.mParcelable, rhs.mParcelPtr, rhs.mStability);
+ return this >= &rhs;
}
private:
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 46457cd..bab6469 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -70,7 +70,7 @@
// 2. Temporary strong references held by the kernel during a
// transaction on the node.
// It does NOT include local strong references to the node
- ssize_t getStrongRefCountForNodeByHandle(int32_t handle);
+ ssize_t getStrongRefCountForNode(const sp<BpBinder>& binder);
enum class CallRestriction {
// all calls okay
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index a57beee..bdb74dc 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -105,6 +105,14 @@
],
tidy_checks_as_errors: [
"*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc,",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "-misc-no-recursion",
+ "-misc-redundant-expression",
+ "-misc-unused-using-decls",
],
}
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index 04167f7..53871f2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -27,6 +27,7 @@
#pragma once
#include <android/binder_ibinder.h>
+#include <android/binder_internal_logging.h>
#include <android/binder_parcel.h>
#include <android/binder_status.h>
@@ -130,7 +131,7 @@
/**
* This baseclass owns a single object, used to make various classes RAII.
*/
-template <typename T, typename R, R (*Destroy)(T), T DEFAULT>
+template <typename T, void (*Destroy)(T), T DEFAULT>
class ScopedAResource {
public:
/**
@@ -198,7 +199,7 @@
/**
* Convenience wrapper. See AParcel.
*/
-class ScopedAParcel : public impl::ScopedAResource<AParcel*, void, AParcel_delete, nullptr> {
+class ScopedAParcel : public impl::ScopedAResource<AParcel*, AParcel_delete, nullptr> {
public:
/**
* Takes ownership of a.
@@ -219,7 +220,7 @@
/**
* Convenience wrapper. See AStatus.
*/
-class ScopedAStatus : public impl::ScopedAResource<AStatus*, void, AStatus_delete, nullptr> {
+class ScopedAStatus : public impl::ScopedAResource<AStatus*, AStatus_delete, nullptr> {
public:
/**
* Takes ownership of a.
@@ -291,7 +292,7 @@
* Convenience wrapper. See AIBinder_DeathRecipient.
*/
class ScopedAIBinder_DeathRecipient
- : public impl::ScopedAResource<AIBinder_DeathRecipient*, void, AIBinder_DeathRecipient_delete,
+ : public impl::ScopedAResource<AIBinder_DeathRecipient*, AIBinder_DeathRecipient_delete,
nullptr> {
public:
/**
@@ -308,7 +309,7 @@
* Convenience wrapper. See AIBinder_Weak.
*/
class ScopedAIBinder_Weak
- : public impl::ScopedAResource<AIBinder_Weak*, void, AIBinder_Weak_delete, nullptr> {
+ : public impl::ScopedAResource<AIBinder_Weak*, AIBinder_Weak_delete, nullptr> {
public:
/**
* Takes ownership of a.
@@ -324,10 +325,22 @@
SpAIBinder promote() { return SpAIBinder(AIBinder_Weak_promote(get())); }
};
+namespace internal {
+
+static void closeWithError(int fd) {
+ if (fd == -1) return;
+ int ret = close(fd);
+ if (ret != 0) {
+ syslog(LOG_ERR, "Could not close FD %d: %s", fd, strerror(errno));
+ }
+}
+
+} // namespace internal
+
/**
* Convenience wrapper for a file descriptor.
*/
-class ScopedFileDescriptor : public impl::ScopedAResource<int, int, close, -1> {
+class ScopedFileDescriptor : public impl::ScopedAResource<int, internal::closeWithError, -1> {
public:
/**
* Takes ownership of a.
diff --git a/libs/binder/ndk/include_cpp/android/binder_internal_logging.h b/libs/binder/ndk/include_cpp/android/binder_internal_logging.h
new file mode 100644
index 0000000..88c6443
--- /dev/null
+++ b/libs/binder/ndk/include_cpp/android/binder_internal_logging.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @addtogroup NdkBinder
+ * @{
+ */
+
+/**
+ * @file binder_internal_logging.h
+ * @brief This provides the ability to use syslog from binder headers, since
+ * other logging functionality might be inaccessable.
+ */
+
+#pragma once
+
+// defined differently by liblog
+#pragma push_macro("LOG_PRI")
+#ifdef LOG_PRI
+#undef LOG_PRI
+#endif
+#include <syslog.h>
+#pragma pop_macro("LOG_PRI")
+
+/** @} */
diff --git a/libs/binder/ndk/include_cpp/android/binder_parcel_utils.h b/libs/binder/ndk/include_cpp/android/binder_parcel_utils.h
index 054aebe..83190aa 100644
--- a/libs/binder/ndk/include_cpp/android/binder_parcel_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_parcel_utils.h
@@ -27,6 +27,7 @@
#pragma once
#include <android/binder_auto_utils.h>
+#include <android/binder_internal_logging.h>
#include <android/binder_parcel.h>
#include <optional>
@@ -179,6 +180,7 @@
static inline binder_status_t AParcel_writeRequiredStrongBinder(AParcel* parcel,
const SpAIBinder& binder) {
if (binder.get() == nullptr) {
+ syslog(LOG_ERR, "Passing null binder object as non-@nullable AIDL IBinder");
return STATUS_UNEXPECTED_NULL;
}
return AParcel_writeStrongBinder(parcel, binder.get());
@@ -228,6 +230,7 @@
static inline binder_status_t AParcel_writeRequiredParcelFileDescriptor(
AParcel* parcel, const ScopedFileDescriptor& fd) {
if (fd.get() < 0) {
+ syslog(LOG_ERR, "Passing -1 file descriptor as non-@nullable AIDL ParcelFileDescriptor");
return STATUS_UNEXPECTED_NULL;
}
return AParcel_writeParcelFileDescriptor(parcel, fd.get());
diff --git a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
index cf2b9e9..6636a41 100644
--- a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
@@ -82,56 +82,47 @@
}
template <typename T>
- bool setParcelable(const T& p) {
+ binder_status_t setParcelable(const T& p) {
if (this->mStability > T::_aidl_stability) {
- return false;
+ return STATUS_BAD_VALUE;
}
AParcel_reset(mParcel.get());
AParcel_writeString(mParcel.get(), T::descriptor, strlen(T::descriptor));
p.writeToParcel(mParcel.get());
- return true;
+ return STATUS_OK;
}
template <typename T>
- std::unique_ptr<T> getParcelable() const {
+ binder_status_t getParcelable(std::optional<T>* ret) const {
const std::string parcelableDesc(T::descriptor);
AParcel_setDataPosition(mParcel.get(), 0);
if (AParcel_getDataSize(mParcel.get()) == 0) {
- return nullptr;
+ *ret = std::nullopt;
+ return STATUS_OK;
}
std::string parcelableDescInParcel;
binder_status_t status = AParcel_readString(mParcel.get(), &parcelableDescInParcel);
if (status != STATUS_OK || parcelableDesc != parcelableDescInParcel) {
- return nullptr;
+ *ret = std::nullopt;
+ return status;
}
- std::unique_ptr<T> ret = std::make_unique<T>();
- status = ret->readFromParcel(this->mParcel.get());
+ *ret = std::make_optional<T>();
+ status = (*ret)->readFromParcel(this->mParcel.get());
if (status != STATUS_OK) {
- return nullptr;
+ *ret = std::nullopt;
+ return status;
}
- return std::move(ret);
+ return STATUS_OK;
}
void reset() { AParcel_reset(mParcel.get()); }
- inline bool operator!=(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) != std::tie(rhs.mParcel, rhs.mStability);
- }
- inline bool operator<(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) < std::tie(rhs.mParcel, rhs.mStability);
- }
- inline bool operator<=(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) <= std::tie(rhs.mParcel, rhs.mStability);
- }
- inline bool operator==(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) == std::tie(rhs.mParcel, rhs.mStability);
- }
- inline bool operator>(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) > std::tie(rhs.mParcel, rhs.mStability);
- }
- inline bool operator>=(const AParcelableHolder& rhs) const {
- return std::tie(mParcel, mStability) >= std::tie(rhs.mParcel, rhs.mStability);
- }
+ inline bool operator!=(const AParcelableHolder& rhs) const { return this != &rhs; }
+ inline bool operator<(const AParcelableHolder& rhs) const { return this < &rhs; }
+ inline bool operator<=(const AParcelableHolder& rhs) const { return this <= &rhs; }
+ inline bool operator==(const AParcelableHolder& rhs) const { return this == &rhs; }
+ inline bool operator>(const AParcelableHolder& rhs) const { return this > &rhs; }
+ inline bool operator>=(const AParcelableHolder& rhs) const { return this >= &rhs; }
private:
mutable ndk::ScopedAParcel mParcel;
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 8ee6a62..edfb56a 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -122,7 +122,7 @@
pub use super::parcel::ParcelFileDescriptor;
pub use super::{add_service, get_interface};
pub use super::{
- ExceptionCode, Interface, ProcessState, SpIBinder, Status, StatusCode,
+ ExceptionCode, Interface, ProcessState, SpIBinder, Status, StatusCode, WpIBinder,
};
/// Binder result containing a [`Status`] on error.
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 485bb42..17af099 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -102,6 +102,11 @@
class.as_ref().map(|p| InterfaceClass::from_ptr(p))
}
}
+
+ /// Creates a new weak reference to this binder object.
+ pub fn downgrade(&mut self) -> WpIBinder {
+ WpIBinder::new(self)
+ }
}
/// An object that can be associate with an [`InterfaceClass`].
@@ -370,15 +375,25 @@
/// A weak reference to a Binder remote object.
///
-/// This struct encapsulates the C++ `wp<IBinder>` class. However, this wrapper
-/// is untyped, so properly typed versions implementing a particular binder
-/// interface should be crated with [`declare_binder_interface!`].
+/// This struct encapsulates the generic C++ `wp<IBinder>` class. This wrapper
+/// is untyped; typed interface access is implemented by the AIDL compiler.
pub struct WpIBinder(*mut sys::AIBinder_Weak);
+impl fmt::Debug for WpIBinder {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad("WpIBinder")
+ }
+}
+
+/// # Safety
+///
+/// A `WpIBinder` is a handle to a C++ IBinder, which is thread-safe.
+unsafe impl Send for WpIBinder {}
+
impl WpIBinder {
/// Create a new weak reference from an object that can be converted into a
/// raw `AIBinder` pointer.
- pub fn new<B: AsNative<sys::AIBinder>>(binder: &mut B) -> WpIBinder {
+ fn new<B: AsNative<sys::AIBinder>>(binder: &mut B) -> WpIBinder {
let ptr = unsafe {
// Safety: `SpIBinder` guarantees that `binder` always contains a
// valid pointer to an `AIBinder`.
@@ -401,6 +416,16 @@
}
}
+impl Drop for WpIBinder {
+ fn drop(&mut self) {
+ unsafe {
+ // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so we
+ // know this pointer is safe to pass to `AIBinder_Weak_delete` here.
+ sys::AIBinder_Weak_delete(self.0);
+ }
+ }
+}
+
/// Rust wrapper around DeathRecipient objects.
#[repr(C)]
pub struct DeathRecipient {
diff --git a/libs/binder/tests/fuzzers/BpBinderFuzzFunctions.h b/libs/binder/tests/fuzzers/BpBinderFuzzFunctions.h
index c685b41..6ca0e2f 100644
--- a/libs/binder/tests/fuzzers/BpBinderFuzzFunctions.h
+++ b/libs/binder/tests/fuzzers/BpBinderFuzzFunctions.h
@@ -48,9 +48,7 @@
static const std::vector<std::function<void(FuzzedDataProvider*, const sp<BpBinder>&,
const sp<IBinder::DeathRecipient>&)>>
gBPBinderOperations =
- {[](FuzzedDataProvider*, const sp<BpBinder>& bpbinder,
- const sp<IBinder::DeathRecipient>&) -> void { bpbinder->handle(); },
- [](FuzzedDataProvider* fdp, const sp<BpBinder>& bpbinder,
+ {[](FuzzedDataProvider* fdp, const sp<BpBinder>& bpbinder,
const sp<IBinder::DeathRecipient>& s_recipient) -> void {
// Clean up possible leftover memory.
wp<IBinder::DeathRecipient> outRecipient(nullptr);
diff --git a/libs/binderdebug/Android.bp b/libs/binderdebug/Android.bp
new file mode 100644
index 0000000..343246a
--- /dev/null
+++ b/libs/binderdebug/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+ name: "libbinderdebug",
+ vendor_available: true,
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ ],
+ srcs: [
+ "BinderDebug.cpp",
+ ],
+ export_include_dirs: [
+ "include",
+ ],
+}
diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp
new file mode 100644
index 0000000..b435dba
--- /dev/null
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <binder/Binder.h>
+#include <sys/types.h>
+#include <fstream>
+#include <regex>
+
+#include <binderdebug/BinderDebug.h>
+
+namespace android {
+
+static std::string contextToString(BinderDebugContext context) {
+ switch (context) {
+ case BinderDebugContext::BINDER:
+ return "binder";
+ case BinderDebugContext::HWBINDER:
+ return "hwbinder";
+ case BinderDebugContext::VNDBINDER:
+ return "vndbinder";
+ default:
+ return std::string();
+ }
+}
+
+static status_t scanBinderContext(pid_t pid, const std::string& contextName,
+ std::function<void(const std::string&)> eachLine) {
+ std::ifstream ifs("/dev/binderfs/binder_logs/proc/" + std::to_string(pid));
+ if (!ifs.is_open()) {
+ ifs.open("/d/binder/proc/" + std::to_string(pid));
+ if (!ifs.is_open()) {
+ return -errno;
+ }
+ }
+ static const std::regex kContextLine("^context (\\w+)$");
+
+ bool isDesiredContext = false;
+ std::string line;
+ std::smatch match;
+ while (getline(ifs, line)) {
+ if (std::regex_search(line, match, kContextLine)) {
+ isDesiredContext = match.str(1) == contextName;
+ continue;
+ }
+ if (!isDesiredContext) {
+ continue;
+ }
+ eachLine(line);
+ }
+ return OK;
+}
+
+status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo) {
+ std::smatch match;
+ static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
+ static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
+ std::string contextStr = contextToString(context);
+ status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
+ if (std::regex_search(line, match, kReferencePrefix)) {
+ const std::string& ptrString = "0x" + match.str(2); // use number after c
+ uint64_t ptr;
+ if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
+ // Should not reach here, but just be tolerant.
+ return;
+ }
+ const std::string proc = " proc ";
+ auto pos = line.rfind(proc);
+ if (pos != std::string::npos) {
+ for (const std::string& pidStr : base::Split(line.substr(pos + proc.size()), " ")) {
+ int32_t pid;
+ if (!::android::base::ParseInt(pidStr, &pid)) {
+ return;
+ }
+ pidInfo->refPids[ptr].push_back(pid);
+ }
+ }
+
+ return;
+ }
+ if (std::regex_search(line, match, kThreadPrefix)) {
+ // "1" is waiting in binder driver
+ // "2" is poll. It's impossible to tell if these are in use.
+ // and HIDL default code doesn't use it.
+ bool isInUse = match.str(1) != "1";
+ // "0" is a thread that has called into binder
+ // "1" is looper thread
+ // "2" is main looper thread
+ bool isBinderThread = match.str(2) != "0";
+ if (!isBinderThread) {
+ return;
+ }
+ if (isInUse) {
+ pidInfo->threadUsage++;
+ }
+
+ pidInfo->threadCount++;
+ return;
+ }
+ return;
+ });
+ return ret;
+}
+
+} // namespace android
diff --git a/libs/binderdebug/TEST_MAPPING b/libs/binderdebug/TEST_MAPPING
new file mode 100644
index 0000000..2f3353e
--- /dev/null
+++ b/libs/binderdebug/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libbinderdebug_test"
+ }
+ ]
+}
diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h
new file mode 100644
index 0000000..14a0ef3
--- /dev/null
+++ b/libs/binderdebug/include/binderdebug/BinderDebug.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <map>
+#include <vector>
+
+namespace android {
+
+struct BinderPidInfo {
+ std::map<uint64_t, std::vector<pid_t>> refPids; // cookie -> processes which hold binder
+ uint32_t threadUsage; // number of threads in use
+ uint32_t threadCount; // number of threads total
+};
+
+enum class BinderDebugContext {
+ BINDER,
+ HWBINDER,
+ VNDBINDER,
+};
+
+status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo);
+
+} // namespace android
diff --git a/libs/binderdebug/tests/Android.bp b/libs/binderdebug/tests/Android.bp
new file mode 100644
index 0000000..4c06b1d
--- /dev/null
+++ b/libs/binderdebug/tests/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+ name: "libbinderdebug_test",
+ test_suites: ["general-tests"],
+ srcs: [
+ "binderdebug_test.cpp",
+ "android/binderdebug/test/IControl.aidl",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ ],
+ static_libs: ["libbinderdebug"],
+ cflags: ["-Wall", "-Werror"],
+ require_root: true,
+}
diff --git a/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl
new file mode 100644
index 0000000..8efeb63
--- /dev/null
+++ b/libs/binderdebug/tests/android/binderdebug/test/IControl.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.binderdebug.test;
+
+interface IControl {
+ // Notifies the service to continue execution
+ void Continue();
+}
diff --git a/libs/binderdebug/tests/binderdebug_test.cpp b/libs/binderdebug/tests/binderdebug_test.cpp
new file mode 100644
index 0000000..ea799c0
--- /dev/null
+++ b/libs/binderdebug/tests/binderdebug_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/IPCThreadState.h>
+#include <binderdebug/BinderDebug.h>
+#include <gtest/gtest.h>
+#include <semaphore.h>
+#include <thread>
+
+#include <android/binderdebug/test/BnControl.h>
+#include <android/binderdebug/test/IControl.h>
+
+namespace android {
+namespace binderdebug {
+namespace test {
+
+class Control : public BnControl {
+public:
+ Control() {sem_init(&s, 1, 0);};
+ ::android::binder::Status Continue() override;
+ sem_t s;
+};
+
+::android::binder::Status Control::Continue() {
+ IPCThreadState::self()->flushCommands();
+ sem_post(&s);
+ return binder::Status::ok();
+}
+
+TEST(BinderDebugTests, BinderPid) {
+ BinderPidInfo pidInfo;
+ const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo);
+ ASSERT_EQ(status, OK);
+ // There should be one referenced PID for servicemanager
+ EXPECT_TRUE(!pidInfo.refPids.empty());
+}
+
+TEST(BinderDebugTests, BinderThreads) {
+ BinderPidInfo pidInfo;
+ const auto& status = getBinderPidInfo(BinderDebugContext::BINDER, getpid(), &pidInfo);
+ ASSERT_EQ(status, OK);
+ EXPECT_TRUE(pidInfo.threadUsage <= pidInfo.threadCount);
+ // The second looper thread can sometimes take longer to spawn.
+ EXPECT_GE(pidInfo.threadCount, 1);
+}
+
+extern "C" {
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // Create a child/client process to call into the main process so we can ensure
+ // looper thread has been registered before attempting to get the BinderPidInfo
+ pid_t pid = fork();
+ if (pid == 0) {
+ sp<IBinder> binder = android::defaultServiceManager()->getService(String16("binderdebug"));
+ sp<IControl> service;
+ if (binder != nullptr) {
+ service = android::interface_cast<IControl>(binder);
+ }
+ service->Continue();
+ exit(0);
+ }
+ sp<Control> iface = new Control;
+ android::defaultServiceManager()->addService(String16("binderdebug"), iface);
+ android::ProcessState::self()->setThreadPoolMaxThreadCount(8);
+ ProcessState::self()->startThreadPool();
+ sem_wait(&iface->s);
+
+ return RUN_ALL_TESTS();
+}
+} // extern "C"
+} // namespace test
+} // namespace binderdebug
+} // namespace android
diff --git a/libs/ftl/.clang-format b/libs/ftl/.clang-format
new file mode 120000
index 0000000..86b1593
--- /dev/null
+++ b/libs/ftl/.clang-format
@@ -0,0 +1 @@
+../../../../build/soong/scripts/system-clang-format-2
\ No newline at end of file
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index eb8e57a..5bccaca 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -5,9 +5,10 @@
address: true,
},
srcs: [
- "SmallMap_test.cpp",
- "SmallVector_test.cpp",
- "StaticVector_test.cpp",
+ "future_test.cpp",
+ "small_map_test.cpp",
+ "small_vector_test.cpp",
+ "static_vector_test.cpp",
],
cflags: [
"-Wall",
diff --git a/libs/ftl/README.md b/libs/ftl/README.md
new file mode 100644
index 0000000..bdd750f
--- /dev/null
+++ b/libs/ftl/README.md
@@ -0,0 +1,41 @@
+# FTL
+
+FTL is a template library shared by SurfaceFlinger and InputFlinger, inspired by
+and supplementing the C++ Standard Library. The intent is to fill gaps for areas
+not (yet) covered—like cache-efficient data structures and lock-free concurrency
+primitives—and implement proposals that are missing or experimental in Android's
+libc++ branch. The design takes some liberties with standard compliance, notably
+assuming that exceptions are disabled.
+
+## Tests
+
+ atest ftl_test
+
+## Style
+
+- Based on [Google C++ Style](https://google.github.io/styleguide/cppguide.html).
+- Informed by [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines).
+
+Naming conventions are as follows:
+
+- `PascalCase`
+ - Types and aliases, except standard interfaces.
+ - Template parameters, including non-type ones.
+- `snake_case`
+ - Variables, and data members with trailing underscore.
+ - Functions, free and member alike.
+ - Type traits, with standard `_t` and `_v` suffixes.
+- `kCamelCase`
+ - Enumerators and `constexpr` constants with static storage duration.
+- `MACRO_CASE`
+ - Macros, with `FTL_` prefix unless `#undef`ed.
+
+Template parameter packs are named with the following convention:
+
+ typename T, typename... Ts
+ typename Arg, typename... Args
+
+ std::size_t I, std::size_t... Is
+ std::size_t Size, std::size_t... Sizes
+
+The `details` namespace contains implementation details.
diff --git a/libs/ftl/SmallMap_test.cpp b/libs/ftl/SmallMap_test.cpp
deleted file mode 100644
index fa00c06..0000000
--- a/libs/ftl/SmallMap_test.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ftl/SmallMap.h>
-#include <gtest/gtest.h>
-
-#include <cctype>
-
-namespace android::test {
-
-using ftl::SmallMap;
-
-// Keep in sync with example usage in header file.
-TEST(SmallMap, Example) {
- ftl::SmallMap<int, std::string, 3> map;
- EXPECT_TRUE(map.empty());
- EXPECT_FALSE(map.dynamic());
-
- map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
- EXPECT_EQ(map.size(), 3u);
- EXPECT_FALSE(map.dynamic());
-
- EXPECT_TRUE(map.contains(123));
-
- EXPECT_EQ(map.find(42, [](const std::string& s) { return s.size(); }), 3u);
-
- const auto opt = map.find(-1);
- ASSERT_TRUE(opt);
-
- std::string& ref = *opt;
- EXPECT_TRUE(ref.empty());
- ref = "xyz";
-
- EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
-}
-
-TEST(SmallMap, Construct) {
- {
- // Default constructor.
- SmallMap<int, std::string, 2> map;
-
- EXPECT_TRUE(map.empty());
- EXPECT_FALSE(map.dynamic());
- }
- {
- // In-place constructor with same types.
- SmallMap<int, std::string, 5> map =
- ftl::init::map<int, std::string>(123, "abc")(456, "def")(789, "ghi");
-
- EXPECT_EQ(map.size(), 3u);
- EXPECT_EQ(map.max_size(), 5u);
- EXPECT_FALSE(map.dynamic());
-
- EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc")(456, "def")(789, "ghi")));
- }
- {
- // In-place constructor with different types.
- SmallMap<int, std::string, 5> map =
- ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
-
- EXPECT_EQ(map.size(), 3u);
- EXPECT_EQ(map.max_size(), 5u);
- EXPECT_FALSE(map.dynamic());
-
- EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???")(123, "abc")(-1, "\0\0\0")));
- }
- {
- // In-place constructor with implicit size.
- SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
-
- static_assert(std::is_same_v<decltype(map), SmallMap<int, std::string, 3>>);
- EXPECT_EQ(map.size(), 3u);
- EXPECT_EQ(map.max_size(), 3u);
- EXPECT_FALSE(map.dynamic());
-
- EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "\0\0\0")(42, "???")(123, "abc")));
- }
-}
-
-TEST(SmallMap, Find) {
- {
- // Constant reference.
- const ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
-
- const auto opt = map.find('b');
- EXPECT_EQ(opt, 'B');
-
- const char d = 'D';
- const auto ref = map.find('d').value_or(std::cref(d));
- EXPECT_EQ(ref.get(), 'D');
- }
- {
- // Mutable reference.
- ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
-
- const auto opt = map.find('c');
- EXPECT_EQ(opt, 'C');
-
- char d = 'd';
- const auto ref = map.find('d').value_or(std::ref(d));
- ref.get() = 'D';
- EXPECT_EQ(d, 'D');
- }
- {
- // Constant unary operation.
- const ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
- EXPECT_EQ(map.find('c', [](char c) { return std::toupper(c); }), 'Z');
- }
- {
- // Mutable unary operation.
- ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
- EXPECT_TRUE(map.find('c', [](char& c) { c = std::toupper(c); }));
-
- EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x')));
- }
-}
-
-} // namespace android::test
diff --git a/libs/ftl/SmallVector_test.cpp b/libs/ftl/SmallVector_test.cpp
deleted file mode 100644
index d0c2858..0000000
--- a/libs/ftl/SmallVector_test.cpp
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ftl/SmallVector.h>
-#include <gtest/gtest.h>
-
-#include <algorithm>
-#include <iterator>
-#include <string>
-#include <utility>
-
-using namespace std::string_literals;
-
-namespace android::test {
-
-using ftl::SmallVector;
-
-// Keep in sync with example usage in header file.
-TEST(SmallVector, Example) {
- ftl::SmallVector<char, 3> vector;
- EXPECT_TRUE(vector.empty());
- EXPECT_FALSE(vector.dynamic());
-
- vector = {'a', 'b', 'c'};
- EXPECT_EQ(vector.size(), 3u);
- EXPECT_FALSE(vector.dynamic());
-
- vector.push_back('d');
- EXPECT_TRUE(vector.dynamic());
-
- vector.unstable_erase(vector.begin());
- EXPECT_EQ(vector, (ftl::SmallVector{'d', 'b', 'c'}));
-
- vector.pop_back();
- EXPECT_EQ(vector.back(), 'b');
- EXPECT_TRUE(vector.dynamic());
-
- const char array[] = "hi";
- vector = ftl::SmallVector(array);
- EXPECT_EQ(vector, (ftl::SmallVector{'h', 'i', '\0'}));
- EXPECT_FALSE(vector.dynamic());
-
- ftl::SmallVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
- ASSERT_EQ(strings.size(), 3u);
- EXPECT_FALSE(strings.dynamic());
-
- EXPECT_EQ(strings[0], "abc");
- EXPECT_EQ(strings[1], "123");
- EXPECT_EQ(strings[2], "???");
-}
-
-TEST(SmallVector, Construct) {
- {
- // Default constructor.
- SmallVector<std::string, 2> vector;
-
- EXPECT_TRUE(vector.empty());
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // Array constructor.
- const float kFloats[] = {.1f, .2f, .3f};
- SmallVector vector(kFloats);
-
- EXPECT_EQ(vector, (SmallVector{.1f, .2f, .3f}));
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // Iterator constructor.
- const char chars[] = "abcdef";
- std::string string(chars);
- SmallVector<char, sizeof(chars)> vector(string.begin(), string.end());
-
- EXPECT_STREQ(vector.begin(), chars);
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // Variadic constructor with same types.
- SmallVector vector = {1, 2, 3};
-
- static_assert(std::is_same_v<decltype(vector), SmallVector<int, 3>>);
- EXPECT_EQ(vector, (SmallVector{1, 2, 3}));
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // Variadic constructor with different types.
- const auto copy = "quince"s;
- auto move = "tart"s;
- SmallVector vector = {copy, std::move(move)};
-
- static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 2>>);
- EXPECT_EQ(vector, (SmallVector{"quince"s, "tart"s}));
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // In-place constructor with same types.
- SmallVector vector =
- ftl::init::list<std::string>("redolent", 3u)("velveteen", 6u)("cakewalk", 4u);
-
- static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
- EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // In-place constructor with different types.
- const auto copy = "red"s;
- auto move = "velvet"s;
- std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
- SmallVector vector = ftl::init::list<std::string>(copy.c_str())(std::move(move))(list);
-
- static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
- EXPECT_TRUE(move.empty());
- EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
- EXPECT_FALSE(vector.dynamic());
- }
- {
- // Conversion from StaticVector.
- ftl::StaticVector doubles = {.1, .2, .3};
- SmallVector vector = std::move(doubles);
- EXPECT_TRUE(doubles.empty());
-
- static_assert(std::is_same_v<decltype(vector), SmallVector<double, 3>>);
- EXPECT_EQ(vector, (SmallVector{.1, .2, .3}));
- EXPECT_FALSE(vector.dynamic());
- }
-}
-
-TEST(SmallVector, String) {
- SmallVector<char, 10> chars;
- char c = 'a';
- std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
- chars.push_back('\0');
-
- EXPECT_TRUE(chars.dynamic());
- EXPECT_EQ(chars.size(), 11u);
- EXPECT_STREQ(chars.begin(), "abcdefghij");
-
- // Constructor takes iterator range.
- const char kString[] = "123456";
- SmallVector<char, 10> string(std::begin(kString), std::end(kString));
-
- EXPECT_FALSE(string.dynamic());
- EXPECT_STREQ(string.begin(), "123456");
- EXPECT_EQ(string.size(), 7u);
-
- // Similar to emplace, but replaces rather than inserts.
- string.replace(string.begin() + 5, '\0');
- EXPECT_STREQ(string.begin(), "12345");
-
- swap(chars, string);
-
- EXPECT_STREQ(chars.begin(), "12345");
- EXPECT_STREQ(string.begin(), "abcdefghij");
-
- EXPECT_FALSE(chars.dynamic());
- EXPECT_TRUE(string.dynamic());
-}
-
-TEST(SmallVector, CopyableElement) {
- struct Pair {
- // Needed because std::vector emplace does not use uniform initialization.
- Pair(int a, int b) : a(a), b(b) {}
-
- const int a, b;
- bool operator==(Pair p) const { return p.a == a && p.b == b; }
- };
-
- SmallVector<Pair, 5> pairs;
-
- EXPECT_TRUE(pairs.empty());
- EXPECT_EQ(pairs.max_size(), 5u);
-
- for (size_t i = 0; i < pairs.max_size(); ++i) {
- EXPECT_EQ(pairs.size(), i);
-
- const int a = static_cast<int>(i) * 2;
- EXPECT_EQ(pairs.emplace_back(a, a + 1), Pair(a, a + 1));
- }
-
- EXPECT_EQ(pairs.size(), 5u);
- EXPECT_FALSE(pairs.dynamic());
-
- // The vector is promoted when full.
- EXPECT_EQ(pairs.emplace_back(10, 11), Pair(10, 11));
- EXPECT_TRUE(pairs.dynamic());
-
- EXPECT_EQ(pairs,
- (SmallVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9},
- Pair{10, 11}}));
-
- // Constructor takes at most N elements.
- SmallVector<int, 6> sums = {0, 0, 0, 0, 0, 0};
- EXPECT_FALSE(sums.dynamic());
-
- // Random-access iterators comply with standard.
- std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
- EXPECT_EQ(sums, (SmallVector{1, 5, 9, 13, 17, 21}));
-
- sums.pop_back();
- std::reverse(sums.begin(), sums.end());
-
- EXPECT_EQ(sums, (SmallVector{17, 13, 9, 5, 1}));
-}
-
-TEST(SmallVector, MovableElement) {
- // Construct std::string elements in place from per-element arguments.
- SmallVector strings = ftl::init::list<std::string>()()()("cake")("velvet")("red")();
- strings.pop_back();
-
- EXPECT_EQ(strings.max_size(), 7u);
- EXPECT_EQ(strings.size(), 6u);
-
- // Erase "cake" and append a substring copy.
- {
- const auto it = std::find_if(strings.begin(), strings.end(),
- [](const auto& s) { return !s.empty(); });
- ASSERT_FALSE(it == strings.end());
- EXPECT_EQ(*it, "cake");
-
- // Construct std::string from first 4 characters of string literal.
- strings.unstable_erase(it);
- EXPECT_EQ(strings.emplace_back("cakewalk", 4u), "cake"s);
- }
-
- strings[1] = "quince"s;
-
- // Replace last empty string with "tart".
- {
- const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
- ASSERT_FALSE(rit == strings.rend());
-
- std::initializer_list<char> list = {'t', 'a', 'r', 't'};
- strings.replace(rit.base() - 1, list);
- }
-
- strings.front().assign("pie");
-
- EXPECT_EQ(strings, (SmallVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
-
- strings.push_back("nougat");
- strings.push_back("oreo");
- EXPECT_TRUE(strings.dynamic());
-
- std::rotate(strings.begin(), strings.end() - 2, strings.end());
-
- EXPECT_EQ(strings,
- (SmallVector{"nougat"s, "oreo"s, "pie"s, "quince"s, "tart"s, "red"s, "velvet"s,
- "cake"s}));
-}
-
-TEST(SmallVector, Replace) {
- // Replacing does not require a copy/move assignment operator.
- struct Word {
- explicit Word(std::string str) : str(std::move(str)) {}
- const std::string str;
-
- bool operator==(const Word& other) const { return other.str == str; }
- };
-
- SmallVector words = ftl::init::list<Word>("colored")("velour");
-
- // The replaced element can be referenced by the replacement.
- {
- const Word& word = words.replace(words.last(), words.back().str.substr(0, 3) + "vet");
- EXPECT_EQ(word, Word("velvet"));
- }
-
- // The vector is not promoted if replacing while full.
- EXPECT_FALSE(words.dynamic());
-
- words.emplace_back("cake");
- EXPECT_TRUE(words.dynamic());
-
- {
- const Word& word = words.replace(words.begin(), words.front().str.substr(4));
- EXPECT_EQ(word, Word("red"));
- }
-
- EXPECT_EQ(words, (SmallVector{Word("red"), Word("velvet"), Word("cake")}));
-}
-
-TEST(SmallVector, ReverseAppend) {
- SmallVector strings = {"red"s, "velvet"s, "cake"s};
- EXPECT_FALSE(strings.dynamic());
-
- auto rit = strings.rbegin();
- while (rit != strings.rend()) {
- // Iterator and reference are invalidated on insertion.
- const auto i = std::distance(strings.begin(), rit.base());
- std::string s = *rit;
-
- strings.push_back(std::move(s));
- rit = std::make_reverse_iterator(strings.begin() + i) + 1;
- }
-
- EXPECT_EQ(strings, (SmallVector{"red"s, "velvet"s, "cake"s, "cake"s, "velvet"s, "red"s}));
- EXPECT_TRUE(strings.dynamic());
-}
-
-TEST(SmallVector, Sort) {
- SmallVector strings = ftl::init::list<std::string>("pie")("quince")("tart")("red")("velvet");
- strings.push_back("cake"s);
-
- auto sorted = std::move(strings);
- EXPECT_TRUE(strings.empty());
-
- EXPECT_TRUE(sorted.dynamic());
- EXPECT_TRUE(strings.dynamic());
-
- std::sort(sorted.begin(), sorted.end());
- EXPECT_EQ(sorted, (SmallVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
-
- // Constructor takes array reference.
- {
- const char* kStrings[] = {"cake", "lie"};
- strings = SmallVector(kStrings);
- EXPECT_FALSE(strings.dynamic());
- }
-
- EXPECT_GT(sorted, strings);
- swap(sorted, strings);
- EXPECT_LT(sorted, strings);
-
- EXPECT_FALSE(sorted.dynamic());
- EXPECT_TRUE(strings.dynamic());
-
- // Append remaining elements, such that "pie" is the only difference.
- for (const char* str : {"quince", "red", "tart", "velvet"}) {
- sorted.emplace_back(str);
- }
- EXPECT_TRUE(sorted.dynamic());
-
- EXPECT_NE(sorted, strings);
-
- // Replace second element with "pie".
- const auto it = sorted.begin() + 1;
- EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
-
- EXPECT_EQ(sorted, strings);
-}
-
-namespace {
-
-struct DestroyCounts {
- DestroyCounts(int& live, int& dead) : counts{live, dead} {}
- DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
- DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
- ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
-
- struct {
- int& live;
- int& dead;
- } counts;
-
- bool alive = true;
-};
-
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
- std::swap(lhs.alive, rhs.alive);
-}
-
-} // namespace
-
-TEST(SmallVector, Destroy) {
- int live = 0;
- int dead = 0;
-
- { SmallVector<DestroyCounts, 3> counts; }
- EXPECT_EQ(0, live);
- EXPECT_EQ(0, dead);
-
- {
- SmallVector<DestroyCounts, 3> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- EXPECT_FALSE(counts.dynamic());
- }
- EXPECT_EQ(3, live);
- EXPECT_EQ(0, dead);
-
- live = 0;
- {
- SmallVector<DestroyCounts, 3> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- EXPECT_TRUE(counts.dynamic());
- }
- EXPECT_EQ(4, live);
- EXPECT_EQ(3, dead);
-
- live = dead = 0;
- {
- SmallVector<DestroyCounts, 2> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- auto copy = counts;
- EXPECT_TRUE(copy.dynamic());
- }
- EXPECT_EQ(6, live);
- EXPECT_EQ(2, dead);
-
- live = dead = 0;
- {
- SmallVector<DestroyCounts, 2> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- auto move = std::move(counts);
- EXPECT_TRUE(move.dynamic());
- }
- EXPECT_EQ(3, live);
- EXPECT_EQ(2, dead);
-
- live = dead = 0;
- {
- SmallVector<DestroyCounts, 2> counts1;
- counts1.emplace_back(live, dead);
- counts1.emplace_back(live, dead);
- counts1.emplace_back(live, dead);
-
- EXPECT_TRUE(counts1.dynamic());
- EXPECT_EQ(2, dead);
- dead = 0;
-
- SmallVector<DestroyCounts, 2> counts2;
- counts2.emplace_back(live, dead);
-
- EXPECT_FALSE(counts2.dynamic());
-
- swap(counts1, counts2);
-
- EXPECT_FALSE(counts1.dynamic());
- EXPECT_TRUE(counts2.dynamic());
-
- EXPECT_EQ(0, live);
- EXPECT_EQ(1, dead);
-
- dead = 0;
- }
- EXPECT_EQ(4, live);
- EXPECT_EQ(0, dead);
-}
-
-} // namespace android::test
diff --git a/libs/ftl/StaticVector_test.cpp b/libs/ftl/StaticVector_test.cpp
deleted file mode 100644
index db42d23..0000000
--- a/libs/ftl/StaticVector_test.cpp
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <ftl/StaticVector.h>
-#include <gtest/gtest.h>
-
-#include <algorithm>
-#include <iterator>
-#include <string>
-#include <utility>
-
-using namespace std::string_literals;
-
-namespace android::test {
-
-using ftl::StaticVector;
-
-// Keep in sync with example usage in header file.
-TEST(StaticVector, Example) {
- ftl::StaticVector<char, 3> vector;
- EXPECT_TRUE(vector.empty());
-
- vector = {'a', 'b'};
- EXPECT_EQ(vector.size(), 2u);
-
- vector.push_back('c');
- EXPECT_TRUE(vector.full());
-
- EXPECT_FALSE(vector.push_back('d'));
- EXPECT_EQ(vector.size(), 3u);
-
- vector.unstable_erase(vector.begin());
- EXPECT_EQ(vector, (ftl::StaticVector{'c', 'b'}));
-
- vector.pop_back();
- EXPECT_EQ(vector.back(), 'c');
-
- const char array[] = "hi";
- vector = ftl::StaticVector(array);
- EXPECT_EQ(vector, (ftl::StaticVector{'h', 'i', '\0'}));
-
- ftl::StaticVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
- ASSERT_EQ(strings.size(), 3u);
-
- EXPECT_EQ(strings[0], "abc");
- EXPECT_EQ(strings[1], "123");
- EXPECT_EQ(strings[2], "???");
-}
-
-TEST(StaticVector, Construct) {
- {
- // Default constructor.
- StaticVector<std::string, 2> vector;
- EXPECT_TRUE(vector.empty());
- }
- {
- // Array constructor.
- const float kFloats[] = {.1f, .2f, .3f};
- StaticVector vector(kFloats);
- EXPECT_EQ(vector, (StaticVector{.1f, .2f, .3f}));
- }
- {
- // Iterator constructor.
- const char chars[] = "abcdef";
- std::string string(chars);
- StaticVector<char, sizeof(chars)> vector(string.begin(), string.end());
-
- EXPECT_STREQ(vector.begin(), chars);
- }
- {
- // Variadic constructor with same types.
- StaticVector vector = {1, 2, 3};
-
- static_assert(std::is_same_v<decltype(vector), StaticVector<int, 3>>);
- EXPECT_EQ(vector, (StaticVector{1, 2, 3}));
- }
- {
- // Variadic constructor with different types.
- const auto copy = "quince"s;
- auto move = "tart"s;
- StaticVector vector = {copy, std::move(move)};
-
- static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 2>>);
- EXPECT_EQ(vector, (StaticVector{"quince"s, "tart"s}));
- }
- {
- // In-place constructor with same types.
- StaticVector vector =
- ftl::init::list<std::string>("redolent", 3u)("velveteen", 6u)("cakewalk", 4u);
-
- static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 3>>);
- EXPECT_EQ(vector, (StaticVector{"red"s, "velvet"s, "cake"s}));
- }
- {
- // In-place constructor with different types.
- const auto copy = "red"s;
- auto move = "velvet"s;
- std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
- StaticVector vector = ftl::init::list<std::string>(copy.c_str())(std::move(move))(list);
-
- static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 3>>);
- EXPECT_TRUE(move.empty());
- EXPECT_EQ(vector, (StaticVector{"red"s, "velvet"s, "cake"s}));
- }
- {
- struct String {
- explicit String(const char* str) : str(str) {}
- explicit String(const char** ptr) : str(*ptr) {}
- const char* str;
- };
-
- const char* kStrings[] = {"a", "b", "c", "d"};
-
- {
- // Two iterator-like elements.
- StaticVector<String, 3> vector(kStrings, kStrings + 3);
- ASSERT_EQ(vector.size(), 2u);
-
- EXPECT_STREQ(vector[0].str, "a");
- EXPECT_STREQ(vector[1].str, "d");
- }
- {
- // Disambiguating iterator constructor.
- StaticVector<String, 3> vector(ftl::IteratorRange, kStrings, kStrings + 3);
- ASSERT_EQ(vector.size(), 3u);
-
- EXPECT_STREQ(vector[0].str, "a");
- EXPECT_STREQ(vector[1].str, "b");
- EXPECT_STREQ(vector[2].str, "c");
- }
- }
-}
-
-TEST(StaticVector, String) {
- StaticVector<char, 10> chars;
- char c = 'a';
- std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
- chars.back() = '\0';
-
- EXPECT_STREQ(chars.begin(), "abcdefghi");
-
- // Constructor takes iterator range.
- const char kString[] = "123456";
- StaticVector<char, 10> string(std::begin(kString), std::end(kString));
-
- EXPECT_STREQ(string.begin(), "123456");
- EXPECT_EQ(string.size(), 7u);
-
- // Similar to emplace, but replaces rather than inserts.
- string.replace(string.begin() + 5, '\0');
- EXPECT_STREQ(string.begin(), "12345");
-
- swap(chars, string);
-
- EXPECT_STREQ(chars.begin(), "12345");
- EXPECT_STREQ(string.begin(), "abcdefghi");
-}
-
-TEST(StaticVector, CopyableElement) {
- struct Pair {
- const int a, b;
- bool operator==(Pair p) const { return p.a == a && p.b == b; }
- };
-
- StaticVector<Pair, 5> pairs;
-
- EXPECT_TRUE(pairs.empty());
- EXPECT_EQ(pairs.max_size(), 5u);
-
- for (size_t i = 0; i < pairs.max_size(); ++i) {
- EXPECT_EQ(pairs.size(), i);
-
- const int a = static_cast<int>(i) * 2;
- const auto it = pairs.emplace_back(a, a + 1);
- ASSERT_NE(it, pairs.end());
- EXPECT_EQ(*it, (Pair{a, a + 1}));
- }
-
- EXPECT_TRUE(pairs.full());
- EXPECT_EQ(pairs.size(), 5u);
-
- // Insertion fails if the vector is full.
- const auto it = pairs.emplace_back(10, 11);
- EXPECT_EQ(it, pairs.end());
-
- EXPECT_EQ(pairs, (StaticVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9}}));
-
- // Constructor takes at most N elements.
- StaticVector<int, 6> sums = {0, 0, 0, 0, 0, -1};
- EXPECT_TRUE(sums.full());
-
- // Random-access iterators comply with standard.
- std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
- EXPECT_EQ(sums, (StaticVector{1, 5, 9, 13, 17, -1}));
-
- sums.pop_back();
- std::reverse(sums.begin(), sums.end());
-
- EXPECT_EQ(sums, (StaticVector{17, 13, 9, 5, 1}));
-}
-
-TEST(StaticVector, MovableElement) {
- // Construct std::string elements in place from per-element arguments.
- StaticVector strings = ftl::init::list<std::string>()()()("cake")("velvet")("red")();
- strings.pop_back();
-
- EXPECT_EQ(strings.max_size(), 7u);
- EXPECT_EQ(strings.size(), 6u);
-
- // Erase "cake" and append a substring copy.
- {
- auto it = std::find_if(strings.begin(), strings.end(),
- [](const auto& s) { return !s.empty(); });
- ASSERT_FALSE(it == strings.end());
- EXPECT_EQ(*it, "cake");
-
- strings.unstable_erase(it);
-
- // Construct std::string from first 4 characters of string literal.
- it = strings.emplace_back("cakewalk", 4u);
- ASSERT_NE(it, strings.end());
- EXPECT_EQ(*it, "cake"s);
- }
-
- strings[1] = "quince"s;
-
- // Replace last empty string with "tart".
- {
- const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
- ASSERT_FALSE(rit == strings.rend());
-
- std::initializer_list<char> list = {'t', 'a', 'r', 't'};
- strings.replace(rit.base() - 1, list);
- }
-
- strings.front().assign("pie");
-
- EXPECT_EQ(strings, (StaticVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
-}
-
-TEST(StaticVector, Replace) {
- // Replacing does not require a copy/move assignment operator.
- struct Word {
- explicit Word(std::string str) : str(std::move(str)) {}
- const std::string str;
- };
-
- StaticVector words = ftl::init::list<Word>("red")("velour")("cake");
-
- // The replaced element can be referenced by the replacement.
- const auto it = words.begin() + 1;
- const Word& word = words.replace(it, it->str.substr(0, 3) + "vet");
- EXPECT_EQ(word.str, "velvet");
-}
-
-TEST(StaticVector, ReverseTruncate) {
- StaticVector<std::string, 10> strings("pie", "quince", "tart", "red", "velvet", "cake");
- EXPECT_FALSE(strings.full());
-
- for (auto it = strings.begin(); it != strings.end(); ++it) {
- strings.replace(it, strings.back());
- strings.pop_back();
- }
-
- EXPECT_EQ(strings, (StaticVector{"cake"s, "velvet"s, "red"s}));
-}
-
-TEST(StaticVector, Sort) {
- StaticVector<std::string, 7> strings("pie", "quince", "tart", "red", "velvet", "cake");
- EXPECT_FALSE(strings.full());
-
- auto sorted = std::move(strings);
- EXPECT_TRUE(strings.empty());
-
- std::sort(sorted.begin(), sorted.end());
- EXPECT_EQ(sorted, (StaticVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
-
- // Constructor takes array reference.
- {
- const char* kStrings[] = {"cake", "lie"};
- strings = StaticVector(kStrings);
- }
-
- EXPECT_GT(sorted, strings);
- swap(sorted, strings);
- EXPECT_LT(sorted, strings);
-
- // Append remaining elements, such that "pie" is the only difference.
- for (const char* str : {"quince", "red", "tart", "velvet"}) {
- sorted.emplace_back(str);
- }
-
- EXPECT_NE(sorted, strings);
-
- // Replace second element with "pie".
- const auto it = sorted.begin() + 1;
- EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
-
- EXPECT_EQ(sorted, strings);
-}
-
-namespace {
-
-struct DestroyCounts {
- DestroyCounts(int& live, int& dead) : counts{live, dead} {}
- DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
- DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
- ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
-
- struct {
- int& live;
- int& dead;
- } counts;
-
- bool alive = true;
-};
-
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
- std::swap(lhs.alive, rhs.alive);
-}
-
-} // namespace
-
-TEST(StaticVector, Destroy) {
- int live = 0;
- int dead = 0;
-
- { StaticVector<DestroyCounts, 5> counts; }
- EXPECT_EQ(0, live);
- EXPECT_EQ(0, dead);
-
- {
- StaticVector<DestroyCounts, 5> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- }
- EXPECT_EQ(3, live);
- EXPECT_EQ(0, dead);
-
- live = 0;
- {
- StaticVector<DestroyCounts, 5> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- auto copy = counts;
- }
- EXPECT_EQ(6, live);
- EXPECT_EQ(0, dead);
-
- live = 0;
- {
- StaticVector<DestroyCounts, 5> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
-
- auto move = std::move(counts);
- }
- EXPECT_EQ(3, live);
- EXPECT_EQ(3, dead);
-
- live = dead = 0;
- {
- StaticVector<DestroyCounts, 5> counts1;
- counts1.emplace_back(live, dead);
- counts1.emplace_back(live, dead);
- counts1.emplace_back(live, dead);
-
- StaticVector<DestroyCounts, 5> counts2;
- counts2.emplace_back(live, dead);
-
- swap(counts1, counts2);
-
- EXPECT_EQ(0, live);
- EXPECT_EQ(2, dead);
-
- dead = 0;
- }
- EXPECT_EQ(4, live);
- EXPECT_EQ(0, dead);
-}
-
-} // namespace android::test
diff --git a/libs/ftl/future_test.cpp b/libs/ftl/future_test.cpp
new file mode 100644
index 0000000..9b3e936
--- /dev/null
+++ b/libs/ftl/future_test.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/future.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <future>
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+TEST(Future, Example) {
+ {
+ auto future = ftl::defer([](int x) { return x + 1; }, 99);
+ EXPECT_EQ(future.get(), 100);
+ }
+ {
+ auto future = ftl::yield(42);
+ EXPECT_EQ(future.get(), 42);
+ }
+ {
+ auto ptr = std::make_unique<char>('!');
+ auto future = ftl::yield(std::move(ptr));
+ EXPECT_EQ(*future.get(), '!');
+ }
+ {
+ auto future = ftl::yield(123);
+ std::future<char> futures[] = {ftl::yield('a'), ftl::yield('b')};
+
+ std::future<char> chain = ftl::chain(std::move(future))
+ .then([](int x) { return static_cast<size_t>(x % 2); })
+ .then([&futures](size_t i) { return std::move(futures[i]); });
+
+ EXPECT_EQ(chain.get(), 'b');
+ }
+}
+
+namespace {
+
+using ByteVector = std::vector<uint8_t>;
+
+ByteVector decrement(ByteVector bytes) {
+ std::transform(bytes.begin(), bytes.end(), bytes.begin(), [](auto b) { return b - 1; });
+ return bytes;
+}
+
+} // namespace
+
+TEST(Future, Chain) {
+ std::packaged_task<const char*()> fetch_string([] { return "ifmmp-"; });
+
+ std::packaged_task<ByteVector(std::string)> append_string([](std::string str) {
+ str += "!xpsme";
+ return ByteVector{str.begin(), str.end()};
+ });
+
+ std::packaged_task<std::future<ByteVector>(ByteVector)> decrement_bytes(
+ [](ByteVector bytes) { return ftl::defer(decrement, std::move(bytes)); });
+
+ auto fetch = fetch_string.get_future();
+ std::thread fetch_thread(std::move(fetch_string));
+
+ std::thread append_thread, decrement_thread;
+
+ EXPECT_EQ(
+ "hello, world",
+ ftl::chain(std::move(fetch))
+ .then([](const char* str) { return std::string(str); })
+ .then([&](std::string str) {
+ auto append = append_string.get_future();
+ append_thread = std::thread(std::move(append_string), std::move(str));
+ return append;
+ })
+ .then([&](ByteVector bytes) {
+ auto decrement = decrement_bytes.get_future();
+ decrement_thread = std::thread(std::move(decrement_bytes), std::move(bytes));
+ return decrement;
+ })
+ .then([](std::future<ByteVector> bytes) { return bytes; })
+ .then([](const ByteVector& bytes) { return std::string(bytes.begin(), bytes.end()); })
+ .get());
+
+ fetch_thread.join();
+ append_thread.join();
+ decrement_thread.join();
+}
+
+} // namespace android::test
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
new file mode 100644
index 0000000..323b9f9
--- /dev/null
+++ b/libs/ftl/small_map_test.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/small_map.h>
+#include <gtest/gtest.h>
+
+#include <cctype>
+
+namespace android::test {
+
+using ftl::SmallMap;
+
+// Keep in sync with example usage in header file.
+TEST(SmallMap, Example) {
+ ftl::SmallMap<int, std::string, 3> map;
+ EXPECT_TRUE(map.empty());
+ EXPECT_FALSE(map.dynamic());
+
+ map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
+ EXPECT_EQ(map.size(), 3u);
+ EXPECT_FALSE(map.dynamic());
+
+ EXPECT_TRUE(map.contains(123));
+
+ EXPECT_EQ(map.find(42, [](const std::string& s) { return s.size(); }), 3u);
+
+ const auto opt = map.find(-1);
+ ASSERT_TRUE(opt);
+
+ std::string& ref = *opt;
+ EXPECT_TRUE(ref.empty());
+ ref = "xyz";
+
+ EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
+}
+
+TEST(SmallMap, Construct) {
+ {
+ // Default constructor.
+ SmallMap<int, std::string, 2> map;
+
+ EXPECT_TRUE(map.empty());
+ EXPECT_FALSE(map.dynamic());
+ }
+ {
+ // In-place constructor with same types.
+ SmallMap<int, std::string, 5> map =
+ ftl::init::map<int, std::string>(123, "abc")(456, "def")(789, "ghi");
+
+ EXPECT_EQ(map.size(), 3u);
+ EXPECT_EQ(map.max_size(), 5u);
+ EXPECT_FALSE(map.dynamic());
+
+ EXPECT_EQ(map, SmallMap(ftl::init::map(123, "abc")(456, "def")(789, "ghi")));
+ }
+ {
+ // In-place constructor with different types.
+ SmallMap<int, std::string, 5> map =
+ ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
+
+ EXPECT_EQ(map.size(), 3u);
+ EXPECT_EQ(map.max_size(), 5u);
+ EXPECT_FALSE(map.dynamic());
+
+ EXPECT_EQ(map, SmallMap(ftl::init::map(42, "???")(123, "abc")(-1, "\0\0\0")));
+ }
+ {
+ // In-place constructor with implicit size.
+ SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
+
+ static_assert(std::is_same_v<decltype(map), SmallMap<int, std::string, 3>>);
+ EXPECT_EQ(map.size(), 3u);
+ EXPECT_EQ(map.max_size(), 3u);
+ EXPECT_FALSE(map.dynamic());
+
+ EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "\0\0\0")(42, "???")(123, "abc")));
+ }
+}
+
+TEST(SmallMap, Find) {
+ {
+ // Constant reference.
+ const ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
+
+ const auto opt = map.find('b');
+ EXPECT_EQ(opt, 'B');
+
+ const char d = 'D';
+ const auto ref = map.find('d').value_or(std::cref(d));
+ EXPECT_EQ(ref.get(), 'D');
+ }
+ {
+ // Mutable reference.
+ ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
+
+ const auto opt = map.find('c');
+ EXPECT_EQ(opt, 'C');
+
+ char d = 'd';
+ const auto ref = map.find('d').value_or(std::ref(d));
+ ref.get() = 'D';
+ EXPECT_EQ(d, 'D');
+ }
+ {
+ // Constant unary operation.
+ const ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
+ EXPECT_EQ(map.find('c', [](char c) { return std::toupper(c); }), 'Z');
+ }
+ {
+ // Mutable unary operation.
+ ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
+ EXPECT_TRUE(map.find('c', [](char& c) { c = std::toupper(c); }));
+
+ EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x')));
+ }
+}
+
+} // namespace android::test
diff --git a/libs/ftl/small_vector_test.cpp b/libs/ftl/small_vector_test.cpp
new file mode 100644
index 0000000..3a03e69
--- /dev/null
+++ b/libs/ftl/small_vector_test.cpp
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/small_vector.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <utility>
+
+using namespace std::string_literals;
+
+namespace android::test {
+
+using ftl::SmallVector;
+
+// Keep in sync with example usage in header file.
+TEST(SmallVector, Example) {
+ ftl::SmallVector<char, 3> vector;
+ EXPECT_TRUE(vector.empty());
+ EXPECT_FALSE(vector.dynamic());
+
+ vector = {'a', 'b', 'c'};
+ EXPECT_EQ(vector.size(), 3u);
+ EXPECT_FALSE(vector.dynamic());
+
+ vector.push_back('d');
+ EXPECT_TRUE(vector.dynamic());
+
+ vector.unstable_erase(vector.begin());
+ EXPECT_EQ(vector, (ftl::SmallVector{'d', 'b', 'c'}));
+
+ vector.pop_back();
+ EXPECT_EQ(vector.back(), 'b');
+ EXPECT_TRUE(vector.dynamic());
+
+ const char array[] = "hi";
+ vector = ftl::SmallVector(array);
+ EXPECT_EQ(vector, (ftl::SmallVector{'h', 'i', '\0'}));
+ EXPECT_FALSE(vector.dynamic());
+
+ ftl::SmallVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
+ ASSERT_EQ(strings.size(), 3u);
+ EXPECT_FALSE(strings.dynamic());
+
+ EXPECT_EQ(strings[0], "abc");
+ EXPECT_EQ(strings[1], "123");
+ EXPECT_EQ(strings[2], "???");
+}
+
+TEST(SmallVector, Construct) {
+ {
+ // Default constructor.
+ SmallVector<std::string, 2> vector;
+
+ EXPECT_TRUE(vector.empty());
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // Array constructor.
+ const float floats[] = {.1f, .2f, .3f};
+ SmallVector vector(floats);
+
+ EXPECT_EQ(vector, (SmallVector{.1f, .2f, .3f}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // Iterator constructor.
+ const char chars[] = "abcdef";
+ std::string string(chars);
+ SmallVector<char, sizeof(chars)> vector(string.begin(), string.end());
+
+ EXPECT_STREQ(vector.begin(), chars);
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // Variadic constructor with same types.
+ SmallVector vector = {1, 2, 3};
+
+ static_assert(std::is_same_v<decltype(vector), SmallVector<int, 3>>);
+ EXPECT_EQ(vector, (SmallVector{1, 2, 3}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // Variadic constructor with different types.
+ const auto copy = "quince"s;
+ auto move = "tart"s;
+ SmallVector vector = {copy, std::move(move)};
+
+ static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 2>>);
+ EXPECT_EQ(vector, (SmallVector{"quince"s, "tart"s}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // In-place constructor with same types.
+ SmallVector vector =
+ ftl::init::list<std::string>("redolent", 3u)("velveteen", 6u)("cakewalk", 4u);
+
+ static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+ EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // In-place constructor with different types.
+ const auto copy = "red"s;
+ auto move = "velvet"s;
+ std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
+ SmallVector vector = ftl::init::list<std::string>(copy.c_str())(std::move(move))(list);
+
+ static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+ EXPECT_TRUE(move.empty());
+ EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+ {
+ // Conversion from StaticVector.
+ ftl::StaticVector doubles = {.1, .2, .3};
+ SmallVector vector = std::move(doubles);
+ EXPECT_TRUE(doubles.empty());
+
+ static_assert(std::is_same_v<decltype(vector), SmallVector<double, 3>>);
+ EXPECT_EQ(vector, (SmallVector{.1, .2, .3}));
+ EXPECT_FALSE(vector.dynamic());
+ }
+}
+
+TEST(SmallVector, String) {
+ SmallVector<char, 10> chars;
+ char c = 'a';
+ std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
+ chars.push_back('\0');
+
+ EXPECT_TRUE(chars.dynamic());
+ EXPECT_EQ(chars.size(), 11u);
+ EXPECT_STREQ(chars.begin(), "abcdefghij");
+
+ // Constructor takes iterator range.
+ const char numbers[] = "123456";
+ SmallVector<char, 10> string(std::begin(numbers), std::end(numbers));
+
+ EXPECT_FALSE(string.dynamic());
+ EXPECT_STREQ(string.begin(), "123456");
+ EXPECT_EQ(string.size(), 7u);
+
+ // Similar to emplace, but replaces rather than inserts.
+ string.replace(string.begin() + 5, '\0');
+ EXPECT_STREQ(string.begin(), "12345");
+
+ swap(chars, string);
+
+ EXPECT_STREQ(chars.begin(), "12345");
+ EXPECT_STREQ(string.begin(), "abcdefghij");
+
+ EXPECT_FALSE(chars.dynamic());
+ EXPECT_TRUE(string.dynamic());
+}
+
+TEST(SmallVector, CopyableElement) {
+ struct Pair {
+ // Needed because std::vector does not use list initialization to emplace.
+ Pair(int a, int b) : a(a), b(b) {}
+
+ const int a, b;
+ bool operator==(Pair p) const { return p.a == a && p.b == b; }
+ };
+
+ SmallVector<Pair, 5> pairs;
+
+ EXPECT_TRUE(pairs.empty());
+ EXPECT_EQ(pairs.max_size(), 5u);
+
+ for (size_t i = 0; i < pairs.max_size(); ++i) {
+ EXPECT_EQ(pairs.size(), i);
+
+ const int a = static_cast<int>(i) * 2;
+ EXPECT_EQ(pairs.emplace_back(a, a + 1), Pair(a, a + 1));
+ }
+
+ EXPECT_EQ(pairs.size(), 5u);
+ EXPECT_FALSE(pairs.dynamic());
+
+ // The vector is promoted when full.
+ EXPECT_EQ(pairs.emplace_back(10, 11), Pair(10, 11));
+ EXPECT_TRUE(pairs.dynamic());
+
+ EXPECT_EQ(pairs, (SmallVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9},
+ Pair{10, 11}}));
+
+ // Constructor takes at most N elements.
+ SmallVector<int, 6> sums = {0, 0, 0, 0, 0, 0};
+ EXPECT_FALSE(sums.dynamic());
+
+ // Random-access iterators comply with standard.
+ std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
+ EXPECT_EQ(sums, (SmallVector{1, 5, 9, 13, 17, 21}));
+
+ sums.pop_back();
+ std::reverse(sums.begin(), sums.end());
+
+ EXPECT_EQ(sums, (SmallVector{17, 13, 9, 5, 1}));
+}
+
+TEST(SmallVector, MovableElement) {
+ // Construct std::string elements in place from per-element arguments.
+ SmallVector strings = ftl::init::list<std::string>()()()("cake")("velvet")("red")();
+ strings.pop_back();
+
+ EXPECT_EQ(strings.max_size(), 7u);
+ EXPECT_EQ(strings.size(), 6u);
+
+ // Erase "cake" and append a substring copy.
+ {
+ const auto it =
+ std::find_if(strings.begin(), strings.end(), [](const auto& s) { return !s.empty(); });
+ ASSERT_FALSE(it == strings.end());
+ EXPECT_EQ(*it, "cake");
+
+ // Construct std::string from first 4 characters of string literal.
+ strings.unstable_erase(it);
+ EXPECT_EQ(strings.emplace_back("cakewalk", 4u), "cake"s);
+ }
+
+ strings[1] = "quince"s;
+
+ // Replace last empty string with "tart".
+ {
+ const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
+ ASSERT_FALSE(rit == strings.rend());
+
+ std::initializer_list<char> list = {'t', 'a', 'r', 't'};
+ strings.replace(rit.base() - 1, list);
+ }
+
+ strings.front().assign("pie");
+
+ EXPECT_EQ(strings, (SmallVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
+
+ strings.push_back("nougat");
+ strings.push_back("oreo");
+ EXPECT_TRUE(strings.dynamic());
+
+ std::rotate(strings.begin(), strings.end() - 2, strings.end());
+
+ EXPECT_EQ(strings, (SmallVector{"nougat"s, "oreo"s, "pie"s, "quince"s, "tart"s, "red"s, "velvet"s,
+ "cake"s}));
+}
+
+TEST(SmallVector, Replace) {
+ // Replacing does not require a copy/move assignment operator.
+ struct Word {
+ explicit Word(std::string str) : str(std::move(str)) {}
+ const std::string str;
+
+ bool operator==(const Word& other) const { return other.str == str; }
+ };
+
+ SmallVector words = ftl::init::list<Word>("colored")("velour");
+
+ // The replaced element can be referenced by the replacement.
+ {
+ const Word& word = words.replace(words.last(), words.back().str.substr(0, 3) + "vet");
+ EXPECT_EQ(word, Word("velvet"));
+ }
+
+ // The vector is not promoted if replacing while full.
+ EXPECT_FALSE(words.dynamic());
+
+ words.emplace_back("cake");
+ EXPECT_TRUE(words.dynamic());
+
+ {
+ const Word& word = words.replace(words.begin(), words.front().str.substr(4));
+ EXPECT_EQ(word, Word("red"));
+ }
+
+ EXPECT_EQ(words, (SmallVector{Word("red"), Word("velvet"), Word("cake")}));
+}
+
+TEST(SmallVector, ReverseAppend) {
+ SmallVector strings = {"red"s, "velvet"s, "cake"s};
+ EXPECT_FALSE(strings.dynamic());
+
+ auto rit = strings.rbegin();
+ while (rit != strings.rend()) {
+ // Iterator and reference are invalidated on insertion.
+ const auto i = std::distance(strings.begin(), rit.base());
+ std::string s = *rit;
+
+ strings.push_back(std::move(s));
+ rit = std::make_reverse_iterator(strings.begin() + i) + 1;
+ }
+
+ EXPECT_EQ(strings, (SmallVector{"red"s, "velvet"s, "cake"s, "cake"s, "velvet"s, "red"s}));
+ EXPECT_TRUE(strings.dynamic());
+}
+
+TEST(SmallVector, Sort) {
+ SmallVector strings = ftl::init::list<std::string>("pie")("quince")("tart")("red")("velvet");
+ strings.push_back("cake"s);
+
+ auto sorted = std::move(strings);
+ EXPECT_TRUE(strings.empty());
+
+ EXPECT_TRUE(sorted.dynamic());
+ EXPECT_TRUE(strings.dynamic());
+
+ std::sort(sorted.begin(), sorted.end());
+ EXPECT_EQ(sorted, (SmallVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
+
+ // Constructor takes array reference.
+ {
+ const char* array[] = {"cake", "lie"};
+ strings = SmallVector(array);
+ EXPECT_FALSE(strings.dynamic());
+ }
+
+ EXPECT_GT(sorted, strings);
+ swap(sorted, strings);
+ EXPECT_LT(sorted, strings);
+
+ EXPECT_FALSE(sorted.dynamic());
+ EXPECT_TRUE(strings.dynamic());
+
+ // Append remaining elements, such that "pie" is the only difference.
+ for (const char* str : {"quince", "red", "tart", "velvet"}) {
+ sorted.emplace_back(str);
+ }
+ EXPECT_TRUE(sorted.dynamic());
+
+ EXPECT_NE(sorted, strings);
+
+ // Replace second element with "pie".
+ const auto it = sorted.begin() + 1;
+ EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
+
+ EXPECT_EQ(sorted, strings);
+}
+
+namespace {
+
+struct DestroyCounts {
+ DestroyCounts(int& live, int& dead) : counts{live, dead} {}
+ DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
+ DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
+ ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
+
+ struct {
+ int& live;
+ int& dead;
+ } counts;
+
+ bool alive = true;
+};
+
+void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
+ std::swap(lhs.alive, rhs.alive);
+}
+
+} // namespace
+
+TEST(SmallVector, Destroy) {
+ int live = 0;
+ int dead = 0;
+
+ { SmallVector<DestroyCounts, 3> counts; }
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(0, dead);
+
+ {
+ SmallVector<DestroyCounts, 3> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ EXPECT_FALSE(counts.dynamic());
+ }
+ EXPECT_EQ(3, live);
+ EXPECT_EQ(0, dead);
+
+ live = 0;
+ {
+ SmallVector<DestroyCounts, 3> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ EXPECT_TRUE(counts.dynamic());
+ }
+ EXPECT_EQ(4, live);
+ EXPECT_EQ(3, dead);
+
+ live = dead = 0;
+ {
+ SmallVector<DestroyCounts, 2> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ auto copy = counts;
+ EXPECT_TRUE(copy.dynamic());
+ }
+ EXPECT_EQ(6, live);
+ EXPECT_EQ(2, dead);
+
+ live = dead = 0;
+ {
+ SmallVector<DestroyCounts, 2> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ auto move = std::move(counts);
+ EXPECT_TRUE(move.dynamic());
+ }
+ EXPECT_EQ(3, live);
+ EXPECT_EQ(2, dead);
+
+ live = dead = 0;
+ {
+ SmallVector<DestroyCounts, 2> counts1;
+ counts1.emplace_back(live, dead);
+ counts1.emplace_back(live, dead);
+ counts1.emplace_back(live, dead);
+
+ EXPECT_TRUE(counts1.dynamic());
+ EXPECT_EQ(2, dead);
+ dead = 0;
+
+ SmallVector<DestroyCounts, 2> counts2;
+ counts2.emplace_back(live, dead);
+
+ EXPECT_FALSE(counts2.dynamic());
+
+ swap(counts1, counts2);
+
+ EXPECT_FALSE(counts1.dynamic());
+ EXPECT_TRUE(counts2.dynamic());
+
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(1, dead);
+
+ dead = 0;
+ }
+ EXPECT_EQ(4, live);
+ EXPECT_EQ(0, dead);
+}
+
+} // namespace android::test
diff --git a/libs/ftl/static_vector_test.cpp b/libs/ftl/static_vector_test.cpp
new file mode 100644
index 0000000..cbe8dff
--- /dev/null
+++ b/libs/ftl/static_vector_test.cpp
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/static_vector.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <utility>
+
+using namespace std::string_literals;
+
+namespace android::test {
+
+using ftl::StaticVector;
+
+// Keep in sync with example usage in header file.
+TEST(StaticVector, Example) {
+ ftl::StaticVector<char, 3> vector;
+ EXPECT_TRUE(vector.empty());
+
+ vector = {'a', 'b'};
+ EXPECT_EQ(vector.size(), 2u);
+
+ vector.push_back('c');
+ EXPECT_TRUE(vector.full());
+
+ EXPECT_FALSE(vector.push_back('d'));
+ EXPECT_EQ(vector.size(), 3u);
+
+ vector.unstable_erase(vector.begin());
+ EXPECT_EQ(vector, (ftl::StaticVector{'c', 'b'}));
+
+ vector.pop_back();
+ EXPECT_EQ(vector.back(), 'c');
+
+ const char array[] = "hi";
+ vector = ftl::StaticVector(array);
+ EXPECT_EQ(vector, (ftl::StaticVector{'h', 'i', '\0'}));
+
+ ftl::StaticVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
+ ASSERT_EQ(strings.size(), 3u);
+
+ EXPECT_EQ(strings[0], "abc");
+ EXPECT_EQ(strings[1], "123");
+ EXPECT_EQ(strings[2], "???");
+}
+
+TEST(StaticVector, Construct) {
+ {
+ // Default constructor.
+ StaticVector<std::string, 2> vector;
+ EXPECT_TRUE(vector.empty());
+ }
+ {
+ // Array constructor.
+ const float floats[] = {.1f, .2f, .3f};
+ StaticVector vector(floats);
+ EXPECT_EQ(vector, (StaticVector{.1f, .2f, .3f}));
+ }
+ {
+ // Iterator constructor.
+ const char chars[] = "abcdef";
+ std::string string(chars);
+ StaticVector<char, sizeof(chars)> vector(string.begin(), string.end());
+
+ EXPECT_STREQ(vector.begin(), chars);
+ }
+ {
+ // Variadic constructor with same types.
+ StaticVector vector = {1, 2, 3};
+
+ static_assert(std::is_same_v<decltype(vector), StaticVector<int, 3>>);
+ EXPECT_EQ(vector, (StaticVector{1, 2, 3}));
+ }
+ {
+ // Variadic constructor with different types.
+ const auto copy = "quince"s;
+ auto move = "tart"s;
+ StaticVector vector = {copy, std::move(move)};
+
+ static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 2>>);
+ EXPECT_EQ(vector, (StaticVector{"quince"s, "tart"s}));
+ }
+ {
+ // In-place constructor with same types.
+ StaticVector vector =
+ ftl::init::list<std::string>("redolent", 3u)("velveteen", 6u)("cakewalk", 4u);
+
+ static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 3>>);
+ EXPECT_EQ(vector, (StaticVector{"red"s, "velvet"s, "cake"s}));
+ }
+ {
+ // In-place constructor with different types.
+ const auto copy = "red"s;
+ auto move = "velvet"s;
+ std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
+ StaticVector vector = ftl::init::list<std::string>(copy.c_str())(std::move(move))(list);
+
+ static_assert(std::is_same_v<decltype(vector), StaticVector<std::string, 3>>);
+ EXPECT_TRUE(move.empty());
+ EXPECT_EQ(vector, (StaticVector{"red"s, "velvet"s, "cake"s}));
+ }
+ {
+ struct String {
+ explicit String(const char* str) : str(str) {}
+ explicit String(const char** ptr) : str(*ptr) {}
+ const char* str;
+ };
+
+ const char* strings[] = {"a", "b", "c", "d"};
+
+ {
+ // Two iterator-like elements.
+ StaticVector<String, 3> vector(strings, strings + 3);
+ ASSERT_EQ(vector.size(), 2u);
+
+ EXPECT_STREQ(vector[0].str, "a");
+ EXPECT_STREQ(vector[1].str, "d");
+ }
+ {
+ // Disambiguating iterator constructor.
+ StaticVector<String, 3> vector(ftl::kIteratorRange, strings, strings + 3);
+ ASSERT_EQ(vector.size(), 3u);
+
+ EXPECT_STREQ(vector[0].str, "a");
+ EXPECT_STREQ(vector[1].str, "b");
+ EXPECT_STREQ(vector[2].str, "c");
+ }
+ }
+}
+
+TEST(StaticVector, String) {
+ StaticVector<char, 10> chars;
+ char c = 'a';
+ std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
+ chars.back() = '\0';
+
+ EXPECT_STREQ(chars.begin(), "abcdefghi");
+
+ // Constructor takes iterator range.
+ const char numbers[] = "123456";
+ StaticVector<char, 10> string(std::begin(numbers), std::end(numbers));
+
+ EXPECT_STREQ(string.begin(), "123456");
+ EXPECT_EQ(string.size(), 7u);
+
+ // Similar to emplace, but replaces rather than inserts.
+ string.replace(string.begin() + 5, '\0');
+ EXPECT_STREQ(string.begin(), "12345");
+
+ swap(chars, string);
+
+ EXPECT_STREQ(chars.begin(), "12345");
+ EXPECT_STREQ(string.begin(), "abcdefghi");
+}
+
+TEST(StaticVector, CopyableElement) {
+ struct Pair {
+ const int a, b;
+ bool operator==(Pair p) const { return p.a == a && p.b == b; }
+ };
+
+ StaticVector<Pair, 5> pairs;
+
+ EXPECT_TRUE(pairs.empty());
+ EXPECT_EQ(pairs.max_size(), 5u);
+
+ for (size_t i = 0; i < pairs.max_size(); ++i) {
+ EXPECT_EQ(pairs.size(), i);
+
+ const int a = static_cast<int>(i) * 2;
+ const auto it = pairs.emplace_back(a, a + 1);
+ ASSERT_NE(it, pairs.end());
+ EXPECT_EQ(*it, (Pair{a, a + 1}));
+ }
+
+ EXPECT_TRUE(pairs.full());
+ EXPECT_EQ(pairs.size(), 5u);
+
+ // Insertion fails if the vector is full.
+ const auto it = pairs.emplace_back(10, 11);
+ EXPECT_EQ(it, pairs.end());
+
+ EXPECT_EQ(pairs, (StaticVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9}}));
+
+ // Constructor takes at most N elements.
+ StaticVector<int, 6> sums = {0, 0, 0, 0, 0, -1};
+ EXPECT_TRUE(sums.full());
+
+ // Random-access iterators comply with standard.
+ std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
+ EXPECT_EQ(sums, (StaticVector{1, 5, 9, 13, 17, -1}));
+
+ sums.pop_back();
+ std::reverse(sums.begin(), sums.end());
+
+ EXPECT_EQ(sums, (StaticVector{17, 13, 9, 5, 1}));
+}
+
+TEST(StaticVector, MovableElement) {
+ // Construct std::string elements in place from per-element arguments.
+ StaticVector strings = ftl::init::list<std::string>()()()("cake")("velvet")("red")();
+ strings.pop_back();
+
+ EXPECT_EQ(strings.max_size(), 7u);
+ EXPECT_EQ(strings.size(), 6u);
+
+ // Erase "cake" and append a substring copy.
+ {
+ auto it =
+ std::find_if(strings.begin(), strings.end(), [](const auto& s) { return !s.empty(); });
+ ASSERT_FALSE(it == strings.end());
+ EXPECT_EQ(*it, "cake");
+
+ strings.unstable_erase(it);
+
+ // Construct std::string from first 4 characters of string literal.
+ it = strings.emplace_back("cakewalk", 4u);
+ ASSERT_NE(it, strings.end());
+ EXPECT_EQ(*it, "cake"s);
+ }
+
+ strings[1] = "quince"s;
+
+ // Replace last empty string with "tart".
+ {
+ const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
+ ASSERT_FALSE(rit == strings.rend());
+
+ std::initializer_list<char> list = {'t', 'a', 'r', 't'};
+ strings.replace(rit.base() - 1, list);
+ }
+
+ strings.front().assign("pie");
+
+ EXPECT_EQ(strings, (StaticVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
+}
+
+TEST(StaticVector, Replace) {
+ // Replacing does not require a copy/move assignment operator.
+ struct Word {
+ explicit Word(std::string str) : str(std::move(str)) {}
+ const std::string str;
+ };
+
+ StaticVector words = ftl::init::list<Word>("red")("velour")("cake");
+
+ // The replaced element can be referenced by the replacement.
+ const auto it = words.begin() + 1;
+ const Word& word = words.replace(it, it->str.substr(0, 3) + "vet");
+ EXPECT_EQ(word.str, "velvet");
+}
+
+TEST(StaticVector, ReverseTruncate) {
+ StaticVector<std::string, 10> strings("pie", "quince", "tart", "red", "velvet", "cake");
+ EXPECT_FALSE(strings.full());
+
+ for (auto it = strings.begin(); it != strings.end(); ++it) {
+ strings.replace(it, strings.back());
+ strings.pop_back();
+ }
+
+ EXPECT_EQ(strings, (StaticVector{"cake"s, "velvet"s, "red"s}));
+}
+
+TEST(StaticVector, Sort) {
+ StaticVector<std::string, 7> strings("pie", "quince", "tart", "red", "velvet", "cake");
+ EXPECT_FALSE(strings.full());
+
+ auto sorted = std::move(strings);
+ EXPECT_TRUE(strings.empty());
+
+ std::sort(sorted.begin(), sorted.end());
+ EXPECT_EQ(sorted, (StaticVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
+
+ // Constructor takes array reference.
+ {
+ const char* array[] = {"cake", "lie"};
+ strings = StaticVector(array);
+ }
+
+ EXPECT_GT(sorted, strings);
+ swap(sorted, strings);
+ EXPECT_LT(sorted, strings);
+
+ // Append remaining elements, such that "pie" is the only difference.
+ for (const char* str : {"quince", "red", "tart", "velvet"}) {
+ sorted.emplace_back(str);
+ }
+
+ EXPECT_NE(sorted, strings);
+
+ // Replace second element with "pie".
+ const auto it = sorted.begin() + 1;
+ EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
+
+ EXPECT_EQ(sorted, strings);
+}
+
+namespace {
+
+struct DestroyCounts {
+ DestroyCounts(int& live, int& dead) : counts{live, dead} {}
+ DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
+ DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
+ ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
+
+ struct {
+ int& live;
+ int& dead;
+ } counts;
+
+ bool alive = true;
+};
+
+void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
+ std::swap(lhs.alive, rhs.alive);
+}
+
+} // namespace
+
+TEST(StaticVector, Destroy) {
+ int live = 0;
+ int dead = 0;
+
+ { StaticVector<DestroyCounts, 5> counts; }
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(0, dead);
+
+ {
+ StaticVector<DestroyCounts, 5> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ }
+ EXPECT_EQ(3, live);
+ EXPECT_EQ(0, dead);
+
+ live = 0;
+ {
+ StaticVector<DestroyCounts, 5> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ auto copy = counts;
+ }
+ EXPECT_EQ(6, live);
+ EXPECT_EQ(0, dead);
+
+ live = 0;
+ {
+ StaticVector<DestroyCounts, 5> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ auto move = std::move(counts);
+ }
+ EXPECT_EQ(3, live);
+ EXPECT_EQ(3, dead);
+
+ live = dead = 0;
+ {
+ StaticVector<DestroyCounts, 5> counts1;
+ counts1.emplace_back(live, dead);
+ counts1.emplace_back(live, dead);
+ counts1.emplace_back(live, dead);
+
+ StaticVector<DestroyCounts, 5> counts2;
+ counts2.emplace_back(live, dead);
+
+ swap(counts1, counts2);
+
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(2, dead);
+
+ dead = 0;
+ }
+ EXPECT_EQ(4, live);
+ EXPECT_EQ(0, dead);
+}
+
+} // namespace android::test
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index af9ef06..7dd584d 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -38,9 +38,10 @@
cc_library_shared {
name: "libgui",
- vendor_available: false,
+ vendor_available: true,
vndk: {
enabled: true,
+ private: true,
},
double_loadable: true,
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index bf0386f..eaa47f9 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -22,8 +22,13 @@
#include <gui/BLASTBufferQueue.h>
#include <gui/BufferItemConsumer.h>
+#include <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
#include <gui/GLConsumer.h>
+#include <gui/IProducerListener.h>
#include <gui/Surface.h>
+#include <utils/Singleton.h>
#include <utils/Trace.h>
@@ -118,7 +123,7 @@
mSize(width, height),
mRequestedSize(mSize),
mNextTransaction(nullptr) {
- BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ createBufferQueue(&mProducer, &mConsumer);
// since the adapter is in the client process, set dequeue timeout
// explicitly so that dequeueBuffer will block
mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());
@@ -146,6 +151,19 @@
mPendingReleaseItem.releaseFence = nullptr;
}
+BLASTBufferQueue::~BLASTBufferQueue() {
+ if (mPendingTransactions.empty()) {
+ return;
+ }
+ BQA_LOGE("Applying pending transactions on dtor %d",
+ static_cast<uint32_t>(mPendingTransactions.size()));
+ SurfaceComposerClient::Transaction t;
+ for (auto& [targetFrameNumber, transaction] : mPendingTransactions) {
+ t.merge(std::move(transaction));
+ }
+ t.apply();
+}
+
void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height) {
std::unique_lock _lock{mMutex};
mSurfaceControl = surface;
@@ -179,49 +197,64 @@
void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
const std::vector<SurfaceControlStats>& stats) {
- std::unique_lock _lock{mMutex};
- ATRACE_CALL();
- BQA_LOGV("transactionCallback");
- mInitialCallbackReceived = true;
+ std::function<void(int64_t)> transactionCompleteCallback = nullptr;
+ uint64_t currFrameNumber = 0;
- if (!stats.empty()) {
- mTransformHint = stats[0].transformHint;
- mBufferItemConsumer->setTransformHint(mTransformHint);
- mBufferItemConsumer->updateFrameTimestamps(stats[0].frameEventStats.frameNumber,
- stats[0].frameEventStats.refreshStartTime,
- stats[0].frameEventStats.gpuCompositionDoneFence,
- stats[0].presentFence,
- stats[0].previousReleaseFence,
- stats[0].frameEventStats.compositorTiming,
- stats[0].latchTime,
- stats[0].frameEventStats.dequeueReadyTime);
- }
- if (mPendingReleaseItem.item.mGraphicBuffer != nullptr) {
+ {
+ std::unique_lock _lock{mMutex};
+ ATRACE_CALL();
+ BQA_LOGV("transactionCallback");
+ mInitialCallbackReceived = true;
+
if (!stats.empty()) {
- mPendingReleaseItem.releaseFence = stats[0].previousReleaseFence;
- } else {
- BQA_LOGE("Warning: no SurfaceControlStats returned in BLASTBufferQueue callback");
+ mTransformHint = stats[0].transformHint;
+ mBufferItemConsumer->setTransformHint(mTransformHint);
+ mBufferItemConsumer
+ ->updateFrameTimestamps(stats[0].frameEventStats.frameNumber,
+ stats[0].frameEventStats.refreshStartTime,
+ stats[0].frameEventStats.gpuCompositionDoneFence,
+ stats[0].presentFence, stats[0].previousReleaseFence,
+ stats[0].frameEventStats.compositorTiming,
+ stats[0].latchTime,
+ stats[0].frameEventStats.dequeueReadyTime);
+ }
+ if (mPendingReleaseItem.item.mGraphicBuffer != nullptr) {
+ if (!stats.empty()) {
+ mPendingReleaseItem.releaseFence = stats[0].previousReleaseFence;
+ } else {
+ BQA_LOGE("Warning: no SurfaceControlStats returned in BLASTBufferQueue callback");
+ mPendingReleaseItem.releaseFence = nullptr;
+ }
+ mBufferItemConsumer->releaseBuffer(mPendingReleaseItem.item,
+ mPendingReleaseItem.releaseFence
+ ? mPendingReleaseItem.releaseFence
+ : Fence::NO_FENCE);
+ mNumAcquired--;
+ mPendingReleaseItem.item = BufferItem();
mPendingReleaseItem.releaseFence = nullptr;
}
- mBufferItemConsumer->releaseBuffer(mPendingReleaseItem.item,
- mPendingReleaseItem.releaseFence
- ? mPendingReleaseItem.releaseFence
- : Fence::NO_FENCE);
- mNumAcquired--;
- mPendingReleaseItem.item = BufferItem();
- mPendingReleaseItem.releaseFence = nullptr;
+
+ if (mSubmitted.empty()) {
+ BQA_LOGE("ERROR: callback with no corresponding submitted buffer item");
+ }
+ mPendingReleaseItem.item = std::move(mSubmitted.front());
+ mSubmitted.pop();
+
+ processNextBufferLocked(false);
+
+ currFrameNumber = mPendingReleaseItem.item.mFrameNumber;
+ if (mTransactionCompleteCallback && mTransactionCompleteFrameNumber == currFrameNumber) {
+ transactionCompleteCallback = std::move(mTransactionCompleteCallback);
+ mTransactionCompleteFrameNumber = 0;
+ }
+
+ mCallbackCV.notify_all();
+ decStrong((void*)transactionCallbackThunk);
}
- if (mSubmitted.empty()) {
- BQA_LOGE("ERROR: callback with no corresponding submitted buffer item");
+ if (transactionCompleteCallback) {
+ transactionCompleteCallback(currFrameNumber);
}
- mPendingReleaseItem.item = std::move(mSubmitted.front());
- mSubmitted.pop();
-
- processNextBufferLocked(false);
-
- mCallbackCV.notify_all();
- decStrong((void*)transactionCallbackThunk);
}
void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
@@ -292,6 +325,7 @@
incStrong((void*)transactionCallbackThunk);
mLastBufferScalingMode = bufferItem.mScalingMode;
+ mLastAcquiredFrameNumber = bufferItem.mFrameNumber;
t->setBuffer(mSurfaceControl, buffer);
t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
@@ -306,7 +340,9 @@
t->setCrop(mSurfaceControl, computeCrop(bufferItem));
t->setTransform(mSurfaceControl, bufferItem.mTransform);
t->setTransformToDisplayInverse(mSurfaceControl, bufferItem.mTransformToDisplayInverse);
- t->setDesiredPresentTime(bufferItem.mTimestamp);
+ if (!bufferItem.mIsAutoTimestamp) {
+ t->setDesiredPresentTime(bufferItem.mTimestamp);
+ }
t->setFrameNumber(mSurfaceControl, bufferItem.mFrameNumber);
if (!mNextFrameTimelineVsyncIdQueue.empty()) {
@@ -319,14 +355,29 @@
mAutoRefresh = bufferItem.mAutoRefresh;
}
+ auto mergeTransaction =
+ [&t, currentFrameNumber = bufferItem.mFrameNumber](
+ std::tuple<uint64_t, SurfaceComposerClient::Transaction> pendingTransaction) {
+ auto& [targetFrameNumber, transaction] = pendingTransaction;
+ if (currentFrameNumber < targetFrameNumber) {
+ return false;
+ }
+ t->merge(std::move(transaction));
+ return true;
+ };
+
+ mPendingTransactions.erase(std::remove_if(mPendingTransactions.begin(),
+ mPendingTransactions.end(), mergeTransaction),
+ mPendingTransactions.end());
+
if (applyTransaction) {
- t->apply();
+ t->setApplyToken(mApplyToken).apply();
}
BQA_LOGV("processNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64
- " applyTransaction=%s mTimestamp=%" PRId64,
+ " applyTransaction=%s mTimestamp=%" PRId64 " mPendingTransactions.size=%d",
mSize.width, mSize.height, bufferItem.mFrameNumber, toString(applyTransaction),
- bufferItem.mTimestamp);
+ bufferItem.mTimestamp, static_cast<uint32_t>(mPendingTransactions.size()));
}
Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -389,6 +440,17 @@
return mSize != bufferSize;
}
+void BLASTBufferQueue::setTransactionCompleteCallback(
+ uint64_t frameNumber, std::function<void(int64_t)>&& transactionCompleteCallback) {
+ std::lock_guard _lock{mMutex};
+ if (transactionCompleteCallback == nullptr) {
+ mTransactionCompleteCallback = nullptr;
+ } else {
+ mTransactionCompleteCallback = std::move(transactionCompleteCallback);
+ mTransactionCompleteFrameNumber = frameNumber;
+ }
+}
+
// Check if we have acquired the maximum number of buffers.
// As a special case, we wait for the first callback before acquiring the second buffer so we
// can ensure the first buffer is presented if multiple buffers are queued in succession.
@@ -454,4 +516,122 @@
return new BBQSurface(mProducer, true, scHandle, this);
}
+void BLASTBufferQueue::mergeWithNextTransaction(SurfaceComposerClient::Transaction* t,
+ uint64_t frameNumber) {
+ std::lock_guard _lock{mMutex};
+ if (mLastAcquiredFrameNumber >= frameNumber) {
+ // Apply the transaction since we have already acquired the desired frame.
+ t->apply();
+ } else {
+ mPendingTransactions.emplace_back(frameNumber, std::move(*t));
+ }
+}
+
+// Maintains a single worker thread per process that services a list of runnables.
+class AsyncWorker : public Singleton<AsyncWorker> {
+private:
+ std::thread mThread;
+ bool mDone = false;
+ std::deque<std::function<void()>> mRunnables;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+ void run() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (!mDone) {
+ mCv.wait(lock);
+ while (!mRunnables.empty()) {
+ std::function<void()> runnable = mRunnables.front();
+ mRunnables.pop_front();
+ runnable();
+ }
+ }
+ }
+
+public:
+ AsyncWorker() : Singleton<AsyncWorker>() { mThread = std::thread(&AsyncWorker::run, this); }
+
+ ~AsyncWorker() {
+ mDone = true;
+ mCv.notify_all();
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+ }
+
+ void post(std::function<void()> runnable) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mRunnables.emplace_back(std::move(runnable));
+ mCv.notify_one();
+ }
+};
+ANDROID_SINGLETON_STATIC_INSTANCE(AsyncWorker);
+
+// Asynchronously calls ProducerListener functions so we can emulate one way binder calls.
+class AsyncProducerListener : public BnProducerListener {
+private:
+ const sp<IProducerListener> mListener;
+
+public:
+ AsyncProducerListener(const sp<IProducerListener>& listener) : mListener(listener) {}
+
+ void onBufferReleased() override {
+ AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferReleased(); });
+ }
+
+ void onBuffersDiscarded(const std::vector<int32_t>& slots) override {
+ AsyncWorker::getInstance().post(
+ [listener = mListener, slots = slots]() { listener->onBuffersDiscarded(slots); });
+ }
+};
+
+// Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls
+// can be non-blocking when the producer is in the client process.
+class BBQBufferQueueProducer : public BufferQueueProducer {
+public:
+ BBQBufferQueueProducer(const sp<BufferQueueCore>& core)
+ : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/) {}
+
+ status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp,
+ QueueBufferOutput* output) override {
+ if (!listener) {
+ return BufferQueueProducer::connect(listener, api, producerControlledByApp, output);
+ }
+
+ return BufferQueueProducer::connect(new AsyncProducerListener(listener), api,
+ producerControlledByApp, output);
+ }
+
+ int query(int what, int* value) override {
+ if (what == NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER) {
+ *value = 1;
+ return NO_ERROR;
+ }
+ return BufferQueueProducer::query(what, value);
+ }
+};
+
+// Similar to BufferQueue::createBufferQueue but creates an adapter specific bufferqueue producer.
+// This BQP allows invoking client specified ProducerListeners and invoke them asynchronously,
+// emulating one way binder call behavior. Without this, if the listener calls back into the queue,
+// we can deadlock.
+void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGraphicBufferConsumer>* outConsumer) {
+ LOG_ALWAYS_FATAL_IF(outProducer == nullptr, "BLASTBufferQueue: outProducer must not be NULL");
+ LOG_ALWAYS_FATAL_IF(outConsumer == nullptr, "BLASTBufferQueue: outConsumer must not be NULL");
+
+ sp<BufferQueueCore> core(new BufferQueueCore());
+ LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore");
+
+ sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core));
+ LOG_ALWAYS_FATAL_IF(producer == nullptr,
+ "BLASTBufferQueue: failed to create BBQBufferQueueProducer");
+
+ sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
+ LOG_ALWAYS_FATAL_IF(consumer == nullptr,
+ "BLASTBufferQueue: failed to create BufferQueueConsumer");
+
+ *outProducer = producer;
+ *outConsumer = consumer;
+}
+
} // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index e46a415..a8d6832 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -71,7 +71,7 @@
virtual status_t setTransactionState(
int64_t frameTimelineVsyncId, const Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
- const InputWindowCommands& commands, int64_t desiredPresentTime,
+ const InputWindowCommands& commands, int64_t desiredPresentTime, bool isAutoTimestamp,
const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
Parcel data, reply;
@@ -92,6 +92,7 @@
SAFE_PARCEL(data.writeStrongBinder, applyToken);
SAFE_PARCEL(commands.write, data);
SAFE_PARCEL(data.writeInt64, desiredPresentTime);
+ SAFE_PARCEL(data.writeBool, isAutoTimestamp);
SAFE_PARCEL(data.writeStrongBinder, uncacheBuffer.token.promote());
SAFE_PARCEL(data.writeUint64, uncacheBuffer.id);
SAFE_PARCEL(data.writeBool, hasListenerCallbacks);
@@ -1230,6 +1231,21 @@
return remote()->transact(BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER, data, &reply);
}
+
+ /**
+ * Get priority of the RenderEngine in surface flinger.
+ */
+ virtual int getGPUContextPriority() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+ status_t err =
+ remote()->transact(BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY, data, &reply);
+ if (err != NO_ERROR) {
+ ALOGE("getGPUContextPriority failed to read data: %s (%d)", strerror(-err), err);
+ return 0;
+ }
+ return reply.readInt32();
+ }
};
// Out-of-line virtual method definition to trigger vtable emission in this
@@ -1282,7 +1298,9 @@
SAFE_PARCEL(inputWindowCommands.read, data);
int64_t desiredPresentTime = 0;
+ bool isAutoTimestamp = true;
SAFE_PARCEL(data.readInt64, &desiredPresentTime);
+ SAFE_PARCEL(data.readBool, &isAutoTimestamp);
client_cache_t uncachedBuffer;
sp<IBinder> tmpBinder;
@@ -1308,8 +1326,8 @@
return setTransactionState(frameTimelineVsyncId, state, displays, stateFlags,
applyToken, inputWindowCommands, desiredPresentTime,
- uncachedBuffer, hasListenerCallbacks, listenerCallbacks,
- transactionId);
+ isAutoTimestamp, uncachedBuffer, hasListenerCallbacks,
+ listenerCallbacks, transactionId);
}
case BOOT_FINISHED: {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
@@ -2094,6 +2112,12 @@
return addTransactionTraceListener(listener);
}
+ case GET_GPU_CONTEXT_PRIORITY: {
+ CHECK_INTERFACE(ISurfaceComposer, data, reply);
+ int priority = getGPUContextPriority();
+ SAFE_PARCEL(reply->writeInt32, priority);
+ return NO_ERROR;
+ }
default: {
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 2dacae1..1808571 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -99,15 +99,13 @@
status_t JankData::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeInt64, frameVsyncId);
- SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(jankType));
+ SAFE_PARCEL(output->writeInt32, jankType);
return NO_ERROR;
}
status_t JankData::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readInt64, &frameVsyncId);
- int32_t jankTypeInt;
- SAFE_PARCEL(input->readInt32, &jankTypeInt);
- jankType = static_cast<JankType>(jankTypeInt);
+ SAFE_PARCEL(input->readInt32, &jankType);
return NO_ERROR;
}
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 94390aa..1bba5e4 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -985,6 +985,10 @@
}
break;
case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER: {
+ status_t err = mGraphicBufferProducer->query(what, value);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
if (composerService()->authenticateSurfaceTexture(
mGraphicBufferProducer)) {
*value = 1;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 9ed7d1c..97c2693 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -395,7 +395,9 @@
mExplicitEarlyWakeupEnd(other.mExplicitEarlyWakeupEnd),
mContainsBuffer(other.mContainsBuffer),
mDesiredPresentTime(other.mDesiredPresentTime),
- mFrameTimelineVsyncId(other.mFrameTimelineVsyncId) {
+ mIsAutoTimestamp(other.mIsAutoTimestamp),
+ mFrameTimelineVsyncId(other.mFrameTimelineVsyncId),
+ mApplyToken(other.mApplyToken) {
mDisplayStates = other.mDisplayStates;
mComposerStates = other.mComposerStates;
mInputWindowCommands = other.mInputWindowCommands;
@@ -424,8 +426,10 @@
const bool explicitEarlyWakeupEnd = parcel->readBool();
const bool containsBuffer = parcel->readBool();
const int64_t desiredPresentTime = parcel->readInt64();
+ const bool isAutoTimestamp = parcel->readBool();
const int64_t frameTimelineVsyncId = parcel->readInt64();
-
+ sp<IBinder> applyToken;
+ parcel->readNullableStrongBinder(&applyToken);
size_t count = static_cast<size_t>(parcel->readUint32());
if (count > parcel->dataSize()) {
return BAD_VALUE;
@@ -497,11 +501,13 @@
mExplicitEarlyWakeupEnd = explicitEarlyWakeupEnd;
mContainsBuffer = containsBuffer;
mDesiredPresentTime = desiredPresentTime;
+ mIsAutoTimestamp = isAutoTimestamp;
mFrameTimelineVsyncId = frameTimelineVsyncId;
mDisplayStates = displayStates;
mListenerCallbacks = listenerCallbacks;
mComposerStates = composerStates;
mInputWindowCommands = inputWindowCommands;
+ mApplyToken = applyToken;
return NO_ERROR;
}
@@ -527,7 +533,9 @@
parcel->writeBool(mExplicitEarlyWakeupEnd);
parcel->writeBool(mContainsBuffer);
parcel->writeInt64(mDesiredPresentTime);
+ parcel->writeBool(mIsAutoTimestamp);
parcel->writeInt64(mFrameTimelineVsyncId);
+ parcel->writeStrongBinder(mApplyToken);
parcel->writeUint32(static_cast<uint32_t>(mDisplayStates.size()));
for (auto const& displayState : mDisplayStates) {
displayState.write(*parcel);
@@ -603,6 +611,7 @@
mEarlyWakeup = mEarlyWakeup || other.mEarlyWakeup;
mExplicitEarlyWakeupStart = mExplicitEarlyWakeupStart || other.mExplicitEarlyWakeupStart;
mExplicitEarlyWakeupEnd = mExplicitEarlyWakeupEnd || other.mExplicitEarlyWakeupEnd;
+ mApplyToken = other.mApplyToken;
// When merging vsync Ids we take the oldest one
if (mFrameTimelineVsyncId != ISurfaceComposer::INVALID_VSYNC_ID &&
@@ -628,8 +637,10 @@
mEarlyWakeup = false;
mExplicitEarlyWakeupStart = false;
mExplicitEarlyWakeupEnd = false;
- mDesiredPresentTime = -1;
+ mDesiredPresentTime = 0;
+ mIsAutoTimestamp = true;
mFrameTimelineVsyncId = ISurfaceComposer::INVALID_VSYNC_ID;
+ mApplyToken = nullptr;
}
void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) {
@@ -640,8 +651,9 @@
uncacheBuffer.id = cacheId;
sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
- sf->setTransactionState(ISurfaceComposer::INVALID_VSYNC_ID, {}, {}, 0, applyToken, {}, -1,
- uncacheBuffer, false, {}, 0 /* Undefined transactionId */);
+ sf->setTransactionState(ISurfaceComposer::INVALID_VSYNC_ID, {}, {}, 0, applyToken, {},
+ systemTime(), true, uncacheBuffer, false, {},
+ 0 /* Undefined transactionId */);
}
void SurfaceComposerClient::Transaction::cacheBuffers() {
@@ -757,9 +769,12 @@
flags |= ISurfaceComposer::eExplicitEarlyWakeupEnd;
}
- sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
+ sp<IBinder> applyToken = mApplyToken
+ ? mApplyToken
+ : IInterface::asBinder(TransactionCompletedListener::getIInstance());
+
sf->setTransactionState(mFrameTimelineVsyncId, composerStates, displayStates, flags, applyToken,
- mInputWindowCommands, mDesiredPresentTime,
+ mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
{} /*uncacheBuffer - only set in doUncacheBufferTransaction*/,
hasListenerCallbacks, listenerCallbacks, mId);
mId = generateId();
@@ -1201,6 +1216,9 @@
}
s->what |= layer_state_t::eBufferChanged;
s->buffer = buffer;
+ if (mIsAutoTimestamp) {
+ mDesiredPresentTime = systemTime();
+ }
registerSurfaceControlForCallback(sc);
@@ -1295,6 +1313,7 @@
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesiredPresentTime(
nsecs_t desiredPresentTime) {
mDesiredPresentTime = desiredPresentTime;
+ mIsAutoTimestamp = false;
return *this;
}
@@ -1569,6 +1588,12 @@
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setApplyToken(
+ const sp<IBinder>& applyToken) {
+ mApplyToken = applyToken;
+ return *this;
+}
+
// ---------------------------------------------------------------------------
DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
@@ -1988,6 +2013,10 @@
lightRadius);
}
+int SurfaceComposerClient::getGPUContextPriority() {
+ return ComposerService::getComposerService()->getGPUContextPriority();
+}
+
// ----------------------------------------------------------------------------
status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 1139390..1198135 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -82,6 +82,9 @@
void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
const std::vector<SurfaceControlStats>& stats);
void setNextTransaction(SurfaceComposerClient::Transaction *t);
+ void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
+ void setTransactionCompleteCallback(uint64_t frameNumber,
+ std::function<void(int64_t)>&& transactionCompleteCallback);
void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height);
void flushShadowQueue() { mFlushShadowQueue = true; }
@@ -89,7 +92,7 @@
status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
status_t setFrameTimelineVsync(int64_t frameTimelineVsyncId);
- virtual ~BLASTBufferQueue() = default;
+ virtual ~BLASTBufferQueue();
private:
friend class BLASTBufferQueueHelper;
@@ -97,6 +100,8 @@
// can't be copied
BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
BLASTBufferQueue(const BLASTBufferQueue& rhs);
+ void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGraphicBufferConsumer>* outConsumer);
void processNextBufferLocked(bool useNextTransaction) REQUIRES(mMutex);
Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
@@ -137,6 +142,9 @@
sp<BLASTBufferItemConsumer> mBufferItemConsumer;
SurfaceComposerClient::Transaction* mNextTransaction GUARDED_BY(mMutex);
+ std::vector<std::tuple<uint64_t /* framenumber */, SurfaceComposerClient::Transaction>>
+ mPendingTransactions GUARDED_BY(mMutex);
+
// If set to true, the next queue buffer will wait until the shadow queue has been processed by
// the adapter.
bool mFlushShadowQueue = false;
@@ -151,6 +159,16 @@
// layer size immediately or wait until we get the next buffer. This will support scenarios
// where the layer can change sizes and the buffer will scale to fit the new size.
uint32_t mLastBufferScalingMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
+
+ // Tracks the last acquired frame number
+ uint64_t mLastAcquiredFrameNumber GUARDED_BY(mMutex) = 0;
+
+ std::function<void(int64_t)> mTransactionCompleteCallback GUARDED_BY(mMutex) = nullptr;
+ uint64_t mTransactionCompleteFrameNumber GUARDED_BY(mMutex){0};
+
+ // Queues up transactions using this token in SurfaceFlinger. This prevents queued up
+ // transactions from other parts of the client from blocking this transaction.
+ const sp<IBinder> mApplyToken = new BBinder();
};
} // namespace android
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 86e3a25..40316db 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -166,7 +166,7 @@
int64_t frameTimelineVsyncId, const Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) = 0;
/* signal that we're done booting.
@@ -504,6 +504,11 @@
*/
virtual status_t addTransactionTraceListener(
const sp<gui::ITransactionTraceListener>& listener) = 0;
+
+ /**
+ * Gets priority of the RenderEngine in SurfaceFlinger.
+ */
+ virtual int getGPUContextPriority() = 0;
};
// ----------------------------------------------------------------------------
@@ -565,6 +570,7 @@
ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
SET_FRAME_TIMELINE_VSYNC,
ADD_TRANSACTION_TRACE_LISTENER,
+ GET_GPU_CONTEXT_PRIORITY,
// Always append new enum to the end.
};
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 26b3840..cb17cee 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -69,15 +69,14 @@
status_t readFromParcel(const Parcel* input) override;
JankData();
- JankData(int64_t frameVsyncId, JankType jankType)
- : frameVsyncId(frameVsyncId),
- jankType(jankType) {}
+ JankData(int64_t frameVsyncId, int32_t jankType)
+ : frameVsyncId(frameVsyncId), jankType(jankType) {}
// Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
int64_t frameVsyncId;
- // The type of jank occurred
- JankType jankType;
+ // Bitmask of janks that occurred
+ int32_t jankType;
};
class SurfaceStats : public Parcelable {
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
index 47daf95..fc91714 100644
--- a/libs/gui/include/gui/JankInfo.h
+++ b/libs/gui/include/gui/JankInfo.h
@@ -18,24 +18,31 @@
namespace android {
-// Jank information tracked by SurfaceFlinger for the purpose of funneling to telemetry.
+// Jank information tracked by SurfaceFlinger(SF) for perfetto tracing and telemetry.
+// TODO(b/175843808): Change JankType from enum to enum class
enum JankType {
// No Jank
None = 0x0,
- // Jank not related to SurfaceFlinger or the App
- Display = 0x1,
+ // Jank that occurs in the layers below SurfaceFlinger
+ DisplayHAL = 0x1,
// SF took too long on the CPU
- SurfaceFlingerDeadlineMissed = 0x2,
+ SurfaceFlingerCpuDeadlineMissed = 0x2,
// SF took too long on the GPU
SurfaceFlingerGpuDeadlineMissed = 0x4,
// Either App or GPU took too long on the frame
AppDeadlineMissed = 0x8,
- // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
- // jank
- // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
- PredictionExpired = 0x10,
- // Latching a buffer early might cause an early present of the frame
- SurfaceFlingerEarlyLatch = 0x20,
+ // Vsync predictions have drifted beyond the threshold from the actual HWVsync
+ PredictionError = 0x10,
+ // Janks caused due to the time SF was scheduled to work on the frame
+ // Example: SF woke up too early and latched a buffer resulting in an early present
+ SurfaceFlingerScheduling = 0x20,
+ // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
+ // presented later because the previous buffer was presented in its expected vsync. This
+ // usually happens if there is an unexpectedly long frame causing the rest of the buffers
+ // to enter a stuffed state.
+ BufferStuffing = 0x40,
+ // Jank due to unknown reasons.
+ Unknown = 0x80,
};
} // namespace android
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index f1845ee..48bc5d5 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -185,6 +185,11 @@
static bool getProtectedContentSupport();
/**
+ * Gets the context priority of surface flinger's render engine.
+ */
+ static int getGPUContextPriority();
+
+ /**
* Uncaches a buffer in ISurfaceComposer. It must be uncached via a transaction so that it is
* in order with other transactions that use buffers.
*/
@@ -368,17 +373,25 @@
// to be presented. When it is not possible to present at exactly that time, it will be
// presented after the time has passed.
//
+ // If the client didn't pass a desired presentation time, mDesiredPresentTime will be
+ // populated to the time setBuffer was called, and mIsAutoTimestamp will be set to true.
+ //
// Desired present times that are more than 1 second in the future may be ignored.
// When a desired present time has already passed, the transaction will be presented as soon
// as possible.
//
// Transactions from the same process are presented in the same order that they are applied.
// The desired present time does not affect this ordering.
- int64_t mDesiredPresentTime = -1;
+ int64_t mDesiredPresentTime = 0;
+ bool mIsAutoTimestamp = true;
// The vsync Id provided by Choreographer.getVsyncId
int64_t mFrameTimelineVsyncId = ISurfaceComposer::INVALID_VSYNC_ID;
+ // If not null, transactions will be queued up using this token otherwise a common token
+ // per process will be used.
+ sp<IBinder> mApplyToken = nullptr;
+
InputWindowCommands mInputWindowCommands;
int mStatus = NO_ERROR;
@@ -546,6 +559,11 @@
// in shared buffer mode.
Transaction& setAutoRefresh(const sp<SurfaceControl>& sc, bool autoRefresh);
+ // Queues up transactions using this token in SurfaceFlinger. By default, all transactions
+ // from a client are placed on the same queue. This can be used to prevent multiple
+ // transactions from blocking each other.
+ Transaction& setApplyToken(const sp<IBinder>& token);
+
status_t setDisplaySurface(const sp<IBinder>& token,
const sp<IGraphicBufferProducer>& bufferProducer);
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 4282ef9..d69b7c3 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -24,6 +24,7 @@
#include <gui/FrameTimestamps.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
+#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SyncScreenCaptureListener.h>
#include <private/gui/ComposerService.h>
@@ -220,6 +221,32 @@
return captureResults.result;
}
+ void queueBuffer(sp<IGraphicBufferProducer> igbp, uint8_t r, uint8_t g, uint8_t b,
+ nsecs_t presentTimeDelay) {
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buf;
+ auto ret = igbp->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
+ PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+ nullptr, nullptr);
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+ ASSERT_EQ(OK, igbp->requestBuffer(slot, &buf));
+
+ uint32_t* bufData;
+ buf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_WRITE_OFTEN),
+ reinterpret_cast<void**>(&bufData));
+ fillBuffer(bufData, Rect(buf->getWidth(), buf->getHeight() / 2), buf->getStride(), r, g, b);
+ buf->unlock();
+
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ nsecs_t timestampNanos = systemTime() + presentTimeDelay;
+ IGraphicBufferProducer::QueueBufferInput input(timestampNanos, false, HAL_DATASPACE_UNKNOWN,
+ Rect(mDisplayWidth, mDisplayHeight / 2),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+ Fence::NO_FENCE);
+ igbp->queueBuffer(slot, input, &qbOutput);
+ }
+
sp<SurfaceComposerClient> mClient;
sp<ISurfaceComposer> mComposer;
@@ -475,6 +502,93 @@
/*border*/ 0, /*outsideRegion*/ true));
}
+class TestProducerListener : public BnProducerListener {
+public:
+ sp<IGraphicBufferProducer> mIgbp;
+ TestProducerListener(const sp<IGraphicBufferProducer>& igbp) : mIgbp(igbp) {}
+ void onBufferReleased() override {
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ mIgbp->detachNextBuffer(&buffer, &fence);
+ }
+};
+
+TEST_F(BLASTBufferQueueTest, CustomProducerListener) {
+ BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+ sp<IGraphicBufferProducer> igbProducer = adapter.getIGraphicBufferProducer();
+ ASSERT_NE(nullptr, igbProducer.get());
+ ASSERT_EQ(NO_ERROR, igbProducer->setMaxDequeuedBufferCount(2));
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ ASSERT_EQ(NO_ERROR,
+ igbProducer->connect(new TestProducerListener(igbProducer), NATIVE_WINDOW_API_CPU,
+ false, &qbOutput));
+ ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
+ for (int i = 0; i < 3; i++) {
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buf;
+ auto ret = igbProducer->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
+ PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
+ nullptr, nullptr);
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+ ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ IGraphicBufferProducer::QueueBufferInput input(systemTime(), false, HAL_DATASPACE_UNKNOWN,
+ Rect(mDisplayWidth, mDisplayHeight),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+ Fence::NO_FENCE);
+ igbProducer->queueBuffer(slot, input, &qbOutput);
+ }
+ adapter.waitForCallbacks();
+}
+
+TEST_F(BLASTBufferQueueTest, QueryNativeWindowQueuesToWindowComposer) {
+ BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+ sp<android::Surface> surface = new Surface(adapter.getIGraphicBufferProducer());
+ ANativeWindow* nativeWindow = (ANativeWindow*)(surface.get());
+ int queuesToNativeWindow = 0;
+ int err = nativeWindow->query(nativeWindow, NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER,
+ &queuesToNativeWindow);
+ ASSERT_EQ(NO_ERROR, err);
+ ASSERT_EQ(queuesToNativeWindow, 1);
+}
+
+TEST_F(BLASTBufferQueueTest, OutOfOrderTransactionTest) {
+ sp<SurfaceControl> bgSurface =
+ mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState);
+ ASSERT_NE(nullptr, bgSurface.get());
+ Transaction t;
+ t.setLayerStack(bgSurface, 0)
+ .show(bgSurface)
+ .setDataspace(bgSurface, ui::Dataspace::V0_SRGB)
+ .setFrame(bgSurface, Rect(0, 0, mDisplayWidth, mDisplayHeight))
+ .setLayer(bgSurface, std::numeric_limits<int32_t>::max() - 1)
+ .apply();
+
+ BLASTBufferQueueHelper slowAdapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+ sp<IGraphicBufferProducer> slowIgbProducer;
+ setUpProducer(slowAdapter, slowIgbProducer);
+ nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
+ queueBuffer(slowIgbProducer, 0 /* r */, 0 /* g */, 0 /* b */, presentTimeDelay);
+
+ BLASTBufferQueueHelper fastAdapter(bgSurface, mDisplayWidth, mDisplayHeight);
+ sp<IGraphicBufferProducer> fastIgbProducer;
+ setUpProducer(fastAdapter, fastIgbProducer);
+ uint8_t r = 255;
+ uint8_t g = 0;
+ uint8_t b = 0;
+ queueBuffer(fastIgbProducer, r, g, b, 0 /* presentTimeDelay */);
+ fastAdapter.waitForCallbacks();
+
+ // capture screen and verify that it is red
+ ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+
+ ASSERT_NO_FATAL_FAILURE(
+ checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
class BLASTBufferQueueTransformTest : public BLASTBufferQueueTest {
public:
void test(uint32_t tr) {
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index c75c46c..3965ea0 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -638,6 +638,9 @@
std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
nonTouchableSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
nonTouchableSurface->mInputInfo.ownerUid = 22222;
+ // Overriding occlusion mode otherwise the touch would be discarded at InputDispatcher by
+ // the default obscured/untrusted touch filter introduced in S.
+ nonTouchableSurface->mInputInfo.touchOcclusionMode = TouchOcclusionMode::ALLOW;
nonTouchableSurface->showAt(100, 100);
injectTap(190, 199);
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index ce3afa2..fa98cd4 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -700,7 +700,7 @@
const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
const sp<IBinder>& /*applyToken*/,
const InputWindowCommands& /*inputWindowCommands*/,
- int64_t /*desiredPresentTime*/,
+ int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
const client_cache_t& /*cachedBuffer*/,
bool /*hasListenerCallbacks*/,
const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
@@ -887,6 +887,8 @@
return NO_ERROR;
}
+ int getGPUContextPriority() override { return 0; };
+
protected:
IBinder* onAsBinder() override { return nullptr; }
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 34eba5b..2ed441d 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -23,6 +23,7 @@
#include <android-base/stringprintf.h>
#include <input/InputDevice.h>
#include <input/InputEventLabels.h>
+#include <input/NamedEnum.h>
using android::base::StringPrintf;
@@ -166,7 +167,9 @@
mKeyCharacterMap(other.mKeyCharacterMap),
mHasVibrator(other.mHasVibrator),
mHasButtonUnderPad(other.mHasButtonUnderPad),
- mMotionRanges(other.mMotionRanges) {}
+ mHasSensor(other.mHasSensor),
+ mMotionRanges(other.mMotionRanges),
+ mSensors(other.mSensors) {}
InputDeviceInfo::~InputDeviceInfo() {
}
@@ -185,7 +188,9 @@
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
mHasVibrator = false;
mHasButtonUnderPad = false;
+ mHasSensor = false;
mMotionRanges.clear();
+ mSensors.clear();
}
const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
@@ -214,4 +219,28 @@
mMotionRanges.push_back(range);
}
+void InputDeviceInfo::addSensorInfo(const InputDeviceSensorInfo& info) {
+ if (mSensors.find(info.type) != mSensors.end()) {
+ ALOGW("Sensor type %s already exists, will be replaced by new sensor added.",
+ NamedEnum::string(info.type).c_str());
+ }
+ mSensors.insert_or_assign(info.type, info);
+}
+
+const std::vector<InputDeviceSensorType> InputDeviceInfo::getSensorTypes() {
+ std::vector<InputDeviceSensorType> types;
+ for (const auto& [type, info] : mSensors) {
+ types.push_back(type);
+ }
+ return types;
+}
+
+const InputDeviceSensorInfo* InputDeviceInfo::getSensorInfo(InputDeviceSensorType type) {
+ auto it = mSensors.find(type);
+ if (it == mSensors.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
} // namespace android
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index f5432ad..44f3f34 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -103,6 +103,48 @@
}
}
+bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const {
+ if (mType != other.mType) {
+ return false;
+ }
+ if (mKeys.size() != other.mKeys.size() ||
+ mKeysByScanCode.size() != other.mKeysByScanCode.size() ||
+ mKeysByUsageCode.size() != other.mKeysByUsageCode.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < mKeys.size(); i++) {
+ if (mKeys.keyAt(i) != other.mKeys.keyAt(i)) {
+ return false;
+ }
+ const Key* key = mKeys.valueAt(i);
+ const Key* otherKey = other.mKeys.valueAt(i);
+ if (key->label != otherKey->label || key->number != otherKey->number) {
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < mKeysByScanCode.size(); i++) {
+ if (mKeysByScanCode.keyAt(i) != other.mKeysByScanCode.keyAt(i)) {
+ return false;
+ }
+ if (mKeysByScanCode.valueAt(i) != other.mKeysByScanCode.valueAt(i)) {
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < mKeysByUsageCode.size(); i++) {
+ if (mKeysByUsageCode.keyAt(i) != other.mKeysByUsageCode.keyAt(i)) {
+ return false;
+ }
+ if (mKeysByUsageCode.valueAt(i) != other.mKeysByUsageCode.valueAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
base::Result<std::shared_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename,
Format format) {
Tokenizer* tokenizer;
@@ -112,7 +154,7 @@
}
std::unique_ptr<Tokenizer> t(tokenizer);
auto ret = load(t.get(), format);
- if (ret) {
+ if (ret.ok()) {
(*ret)->mLoadFileName = filename;
}
return ret;
@@ -128,7 +170,7 @@
}
std::unique_ptr<Tokenizer> t(tokenizer);
auto ret = load(t.get(), format);
- if (ret) {
+ if (ret.ok()) {
(*ret)->mLoadFileName = filename;
}
return ret;
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 16ce48a..fa5a541 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -20,12 +20,13 @@
#include <android/keycodes.h>
#include <input/InputEventLabels.h>
-#include <input/Keyboard.h>
#include <input/KeyLayoutMap.h>
-#include <utils/Log.h>
+#include <input/Keyboard.h>
+#include <input/NamedEnum.h>
#include <utils/Errors.h>
-#include <utils/Tokenizer.h>
+#include <utils/Log.h>
#include <utils/Timers.h>
+#include <utils/Tokenizer.h>
// Enables debug output for the parser.
#define DEBUG_PARSER 0
@@ -41,6 +42,26 @@
static const char* WHITESPACE = " \t\r";
+#define SENSOR_ENTRY(type) NamedEnum::string(type), type
+static const std::unordered_map<std::string, InputDeviceSensorType> SENSOR_LIST =
+ {{SENSOR_ENTRY(InputDeviceSensorType::ACCELEROMETER)},
+ {SENSOR_ENTRY(InputDeviceSensorType::MAGNETIC_FIELD)},
+ {SENSOR_ENTRY(InputDeviceSensorType::ORIENTATION)},
+ {SENSOR_ENTRY(InputDeviceSensorType::GYROSCOPE)},
+ {SENSOR_ENTRY(InputDeviceSensorType::LIGHT)},
+ {SENSOR_ENTRY(InputDeviceSensorType::PRESSURE)},
+ {SENSOR_ENTRY(InputDeviceSensorType::TEMPERATURE)},
+ {SENSOR_ENTRY(InputDeviceSensorType::PROXIMITY)},
+ {SENSOR_ENTRY(InputDeviceSensorType::GRAVITY)},
+ {SENSOR_ENTRY(InputDeviceSensorType::LINEAR_ACCELERATION)},
+ {SENSOR_ENTRY(InputDeviceSensorType::ROTATION_VECTOR)},
+ {SENSOR_ENTRY(InputDeviceSensorType::RELATIVE_HUMIDITY)},
+ {SENSOR_ENTRY(InputDeviceSensorType::AMBIENT_TEMPERATURE)},
+ {SENSOR_ENTRY(InputDeviceSensorType::MAGNETIC_FIELD_UNCALIBRATED)},
+ {SENSOR_ENTRY(InputDeviceSensorType::GAME_ROTATION_VECTOR)},
+ {SENSOR_ENTRY(InputDeviceSensorType::GYROSCOPE_UNCALIBRATED)},
+ {SENSOR_ENTRY(InputDeviceSensorType::SIGNIFICANT_MOTION)}};
+
// --- KeyLayoutMap ---
KeyLayoutMap::KeyLayoutMap() {
@@ -59,7 +80,7 @@
}
std::unique_ptr<Tokenizer> t(tokenizer);
auto ret = load(t.get());
- if (ret) {
+ if (ret.ok()) {
(*ret)->mLoadFileName = filename;
}
return ret;
@@ -74,7 +95,7 @@
}
std::unique_ptr<Tokenizer> t(tokenizer);
auto ret = load(t.get());
- if (ret) {
+ if (ret.ok()) {
(*ret)->mLoadFileName = filename;
}
return ret;
@@ -127,6 +148,24 @@
return NO_ERROR;
}
+// Return pair of sensor type and sensor data index, for the input device abs code
+base::Result<std::pair<InputDeviceSensorType, int32_t>> KeyLayoutMap::mapSensor(int32_t absCode) {
+ auto it = mSensorsByAbsCode.find(absCode);
+ if (it == mSensorsByAbsCode.end()) {
+#if DEBUG_MAPPING
+ ALOGD("mapSensor: absCode=%d, ~ Failed.", absCode);
+#endif
+ return Errorf("Can't find abs code {}.", absCode);
+ }
+ const Sensor& sensor = it->second;
+
+#if DEBUG_MAPPING
+ ALOGD("mapSensor: absCode=%d, sensorType=0x%0x, sensorDataIndex=0x%x.", absCode,
+ NamedEnum::string(sensor.sensorType), sensor.sensorDataIndex);
+#endif
+ return std::make_pair(sensor.sensorType, sensor.sensorDataIndex);
+}
+
const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
if (usageCode) {
ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
@@ -242,6 +281,10 @@
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseLed();
if (status) return status;
+ } else if (keywordToken == "sensor") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseSensor();
+ if (status) return status;
} else {
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
keywordToken.string());
@@ -468,4 +511,84 @@
map.add(code, led);
return NO_ERROR;
}
+
+static std::optional<InputDeviceSensorType> getSensorType(const char* token) {
+ auto it = SENSOR_LIST.find(std::string(token));
+ if (it == SENSOR_LIST.end()) {
+ return std::nullopt;
+ }
+ return it->second;
+}
+
+static std::optional<int32_t> getSensorDataIndex(String8 token) {
+ std::string tokenStr(token.string());
+ if (tokenStr == "X") {
+ return 0;
+ } else if (tokenStr == "Y") {
+ return 1;
+ } else if (tokenStr == "Z") {
+ return 2;
+ }
+ return std::nullopt;
+}
+
+// Parse sensor type and data index mapping, as below format
+// sensor <raw abs> <sensor type> <sensor data index>
+// raw abs : the linux abs code of the axis
+// sensor type : string name of InputDeviceSensorType
+// sensor data index : the data index of sensor, out of [X, Y, Z]
+// Examples:
+// sensor 0x00 ACCELEROMETER X
+// sensor 0x01 ACCELEROMETER Y
+// sensor 0x02 ACCELEROMETER Z
+// sensor 0x03 GYROSCOPE X
+// sensor 0x04 GYROSCOPE Y
+// sensor 0x05 GYROSCOPE Z
+status_t KeyLayoutMap::Parser::parseSensor() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected sensor %s number, got '%s'.", mTokenizer->getLocation().string(),
+ "abs code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ std::unordered_map<int32_t, Sensor>& map = mMap->mSensorsByAbsCode;
+ if (map.find(code) != map.end()) {
+ ALOGE("%s: Duplicate entry for sensor %s '%s'.", mTokenizer->getLocation().string(),
+ "abs code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 sensorTypeToken = mTokenizer->nextToken(WHITESPACE);
+ std::optional<InputDeviceSensorType> typeOpt = getSensorType(sensorTypeToken.string());
+ if (!typeOpt) {
+ ALOGE("%s: Expected sensor code label, got '%s'.", mTokenizer->getLocation().string(),
+ sensorTypeToken.string());
+ return BAD_VALUE;
+ }
+ InputDeviceSensorType sensorType = typeOpt.value();
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 sensorDataIndexToken = mTokenizer->nextToken(WHITESPACE);
+ std::optional<int32_t> indexOpt = getSensorDataIndex(sensorDataIndexToken);
+ if (!indexOpt) {
+ ALOGE("%s: Expected sensor data index label, got '%s'.", mTokenizer->getLocation().string(),
+ sensorDataIndexToken.string());
+ return BAD_VALUE;
+ }
+ int32_t sensorDataIndex = indexOpt.value();
+
+#if DEBUG_PARSER
+ ALOGD("Parsed sensor: abs code=%d, sensorType=%d, sensorDataIndex=%d.", code,
+ NamedEnum::string(sensorType).c_str(), sensorDataIndex);
+#endif
+
+ Sensor sensor;
+ sensor.sensorType = sensorType;
+ sensor.sensorDataIndex = sensorDataIndex;
+ map.emplace(code, sensor);
+ return NO_ERROR;
+}
};
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index 14dc9e5..f0895b3 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -111,7 +111,7 @@
}
base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(path);
- if (!ret) {
+ if (!ret.ok()) {
return ret.error().code();
}
keyLayoutMap = *ret;
@@ -129,7 +129,7 @@
base::Result<std::shared_ptr<KeyCharacterMap>> ret =
KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
- if (!ret) {
+ if (!ret.ok()) {
return ret.error().code();
}
keyCharacterMap = *ret;
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index c174ae9..f8f2f4e 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-
+#include <binder/Binder.h>
+#include <binder/Parcel.h>
#include <gtest/gtest.h>
#include <input/InputDevice.h>
+#include <input/KeyLayoutMap.h>
+#include <input/Keyboard.h>
namespace android {
@@ -31,4 +34,52 @@
ASSERT_EQ(std::string("deviceName-123_version_C_"), identifier.getCanonicalName());
}
-} // namespace android
\ No newline at end of file
+class InputDeviceKeyMapTest : public testing::Test {
+protected:
+ void loadKeyLayout(const char* name) {
+ std::string path =
+ getInputDeviceConfigurationFilePathByName(name,
+ InputDeviceConfigurationFileType::
+ KEY_LAYOUT);
+ ASSERT_FALSE(path.empty());
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(path);
+ ASSERT_TRUE(ret.ok()) << "Cannot load KeyLayout at " << path;
+ mKeyMap.keyLayoutMap = std::move(*ret);
+ mKeyMap.keyLayoutFile = path;
+ }
+
+ void loadKeyCharacterMap(const char* name) {
+ InputDeviceIdentifier identifier;
+ identifier.name = name;
+ std::string path =
+ getInputDeviceConfigurationFilePathByName(identifier.getCanonicalName(),
+ InputDeviceConfigurationFileType::
+ KEY_CHARACTER_MAP);
+ ASSERT_FALSE(path.empty()) << "KeyCharacterMap for " << name << " not found";
+ base::Result<std::shared_ptr<KeyCharacterMap>> ret =
+ KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
+ ASSERT_TRUE(ret.ok()) << "Cannot load KeyCharacterMap at " << path;
+ mKeyMap.keyCharacterMap = *ret;
+ mKeyMap.keyCharacterMapFile = path;
+ }
+
+ virtual void SetUp() override {
+ loadKeyLayout("Generic");
+ loadKeyCharacterMap("Generic");
+ }
+
+ virtual void TearDown() override {}
+
+ KeyMap mKeyMap;
+};
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapParcelingTest) {
+ Parcel parcel;
+ mKeyMap.keyCharacterMap->writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ std::shared_ptr<KeyCharacterMap> map = KeyCharacterMap::readFromParcel(&parcel);
+ // Verify the key character map is the same as original
+ ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
+}
+
+} // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 279e648..c88e298 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -222,6 +222,29 @@
return err;
}
+std::optional<RenderEngine::ContextPriority> GLESRenderEngine::createContextPriority(
+ const RenderEngineCreationArgs& args) {
+ if (!GLExtensions::getInstance().hasContextPriority()) {
+ return std::nullopt;
+ }
+
+ switch (args.contextPriority) {
+ case RenderEngine::ContextPriority::REALTIME:
+ if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
+ return RenderEngine::ContextPriority::REALTIME;
+ } else {
+ ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
+ return RenderEngine::ContextPriority::HIGH;
+ }
+ case RenderEngine::ContextPriority::HIGH:
+ case RenderEngine::ContextPriority::MEDIUM:
+ case RenderEngine::ContextPriority::LOW:
+ return args.contextPriority;
+ default:
+ return std::nullopt;
+ }
+}
+
std::unique_ptr<GLESRenderEngine> GLESRenderEngine::create(const RenderEngineCreationArgs& args) {
// initialize EGL for the default display
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -235,6 +258,7 @@
LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
}
+ // Use the Android impl to grab EGL_NV_context_priority_realtime
const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
if (!eglExtensions) {
checkGlError(__FUNCTION__, __LINE__);
@@ -251,17 +275,16 @@
config = chooseEglConfig(display, args.pixelFormat, /*logConfig*/ true);
}
- bool useContextPriority =
- extensions.hasContextPriority() && args.contextPriority == ContextPriority::HIGH;
+ const std::optional<RenderEngine::ContextPriority> priority = createContextPriority(args);
EGLContext protectedContext = EGL_NO_CONTEXT;
if (args.enableProtectedContext && extensions.hasProtectedContent()) {
- protectedContext = createEglContext(display, config, nullptr, useContextPriority,
- Protection::PROTECTED);
+ protectedContext =
+ createEglContext(display, config, nullptr, priority, Protection::PROTECTED);
ALOGE_IF(protectedContext == EGL_NO_CONTEXT, "Can't create protected context");
}
- EGLContext ctxt = createEglContext(display, config, protectedContext, useContextPriority,
- Protection::UNPROTECTED);
+ EGLContext ctxt =
+ createEglContext(display, config, protectedContext, priority, Protection::UNPROTECTED);
// if can't create a GL context, we can only abort.
LOG_ALWAYS_FATAL_IF(ctxt == EGL_NO_CONTEXT, "EGLContext creation failed");
@@ -311,7 +334,6 @@
ALOGI("extensions: %s", extensions.getExtensions());
ALOGI("GL_MAX_TEXTURE_SIZE = %zu", engine->getMaxTextureSize());
ALOGI("GL_MAX_VIEWPORT_DIMS = %zu", engine->getMaxViewportDims());
-
return engine;
}
@@ -802,6 +824,12 @@
ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
}
+int GLESRenderEngine::getContextPriority() {
+ int value;
+ eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
+ return value;
+}
+
FloatRect GLESRenderEngine::setupLayerCropping(const LayerSettings& layer, Mesh& mesh) {
// Translate win by the rounded corners rect coordinates, to have all values in
// layer coordinate space.
@@ -1617,7 +1645,8 @@
}
EGLContext GLESRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext, bool useContextPriority,
+ EGLContext shareContext,
+ std::optional<ContextPriority> contextPriority,
Protection protection) {
EGLint renderableType = 0;
if (config == EGL_NO_CONFIG) {
@@ -1640,9 +1669,23 @@
contextAttributes.reserve(7);
contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
contextAttributes.push_back(contextClientVersion);
- if (useContextPriority) {
+ if (contextPriority) {
contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ switch (*contextPriority) {
+ case ContextPriority::REALTIME:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_REALTIME_NV);
+ break;
+ case ContextPriority::MEDIUM:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_MEDIUM_IMG);
+ break;
+ case ContextPriority::LOW:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
+ break;
+ case ContextPriority::HIGH:
+ default:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ break;
+ }
}
if (protection == Protection::PROTECTED) {
contextAttributes.push_back(EGL_PROTECTED_CONTENT_EXT);
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 92e1529..64d6c6b 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -71,6 +71,7 @@
const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
bool cleanupPostRender(CleanupMode mode) override;
+ int getContextPriority() override;
EGLDisplay getEGLDisplay() const { return mEGLDisplay; }
// Creates an output image for rendering to
@@ -116,8 +117,11 @@
static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
static GlesVersion parseGlesVersion(const char* str);
static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext, bool useContextPriority,
+ EGLContext shareContext,
+ std::optional<ContextPriority> contextPriority,
Protection protection);
+ static std::optional<RenderEngine::ContextPriority> createContextPriority(
+ const RenderEngineCreationArgs& args);
static EGLSurface createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
int hwcFormat, Protection protection);
std::unique_ptr<Framebuffer> createFramebuffer();
diff --git a/libs/renderengine/gl/GLExtensions.cpp b/libs/renderengine/gl/GLExtensions.cpp
index 2924b0e..3dd534e 100644
--- a/libs/renderengine/gl/GLExtensions.cpp
+++ b/libs/renderengine/gl/GLExtensions.cpp
@@ -120,6 +120,10 @@
if (extensionSet.hasExtension("EGL_KHR_surfaceless_context")) {
mHasSurfacelessContext = true;
}
+
+ if (extensionSet.hasExtension("EGL_NV_context_priority_realtime")) {
+ mHasRealtimePriority = true;
+ }
}
char const* GLExtensions::getEGLVersion() const {
diff --git a/libs/renderengine/gl/GLExtensions.h b/libs/renderengine/gl/GLExtensions.h
index ef00009..e415ff3 100644
--- a/libs/renderengine/gl/GLExtensions.h
+++ b/libs/renderengine/gl/GLExtensions.h
@@ -41,6 +41,7 @@
bool hasContextPriority() const { return mHasContextPriority; }
bool hasSurfacelessContext() const { return mHasSurfacelessContext; }
bool hasProtectedTexture() const { return mHasProtectedTexture; }
+ bool hasRealtimePriority() const { return mHasRealtimePriority; }
void initWithGLStrings(GLubyte const* vendor, GLubyte const* renderer, GLubyte const* version,
GLubyte const* extensions);
@@ -67,6 +68,7 @@
bool mHasContextPriority = false;
bool mHasSurfacelessContext = false;
bool mHasProtectedTexture = false;
+ bool mHasRealtimePriority = false;
String8 mVendor;
String8 mRenderer;
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
index 7fc0499..5ff9240 100644
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ b/libs/renderengine/gl/ProgramCache.cpp
@@ -740,15 +740,6 @@
if (needs.isOpaque()) {
fs << "gl_FragColor.a = 1.0;";
}
- if (needs.hasAlpha()) {
- // modulate the current alpha value with alpha set
- if (needs.isPremultiplied()) {
- // ... and the color too if we're premultiplied
- fs << "gl_FragColor *= color.a;";
- } else {
- fs << "gl_FragColor.a *= color.a;";
- }
- }
}
if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
@@ -768,6 +759,23 @@
}
}
+ /*
+ * Whether applying layer alpha before or after color transform doesn't matter,
+ * as long as we can undo premultiplication. But we cannot un-premultiply
+ * for color transform if the layer alpha = 0, e.g. 0 / (0 + 0.0019) = 0.
+ */
+ if (!needs.drawShadows()) {
+ if (needs.hasAlpha()) {
+ // modulate the current alpha value with alpha set
+ if (needs.isPremultiplied()) {
+ // ... and the color too if we're premultiplied
+ fs << "gl_FragColor *= color.a;";
+ } else {
+ fs << "gl_FragColor.a *= color.a;";
+ }
+ }
+ }
+
if (needs.hasRoundedCorners()) {
if (needs.isPremultiplied()) {
fs << "gl_FragColor *= vec4(applyCornerRadius(outCropCoords));";
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index d8d989e..3a727c9 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -154,6 +154,9 @@
int backgroundBlurRadius = 0;
std::vector<BlurRegion> blurRegions;
+
+ // Name associated with the layer for debugging purposes.
+ std::string name;
};
// Keep in sync with custom comparison function in
@@ -274,6 +277,10 @@
*os << "\n .colorTransform = " << settings.colorTransform;
*os << "\n .disableBlending = " << settings.disableBlending;
*os << "\n .backgroundBlurRadius = " << settings.backgroundBlurRadius;
+ for (auto blurRegion : settings.blurRegions) {
+ *os << "\n";
+ PrintTo(blurRegion, os);
+ }
*os << "\n .shadow = ";
PrintTo(settings.shadow, os);
*os << "\n}";
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index ef12fd2..9157066 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -75,6 +75,7 @@
LOW = 1,
MEDIUM = 2,
HIGH = 3,
+ REALTIME = 4,
};
enum class RenderEngineType {
@@ -181,6 +182,10 @@
const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
base::unique_fd&& bufferFence, base::unique_fd* drawFence) = 0;
virtual void cleanFramebufferCache() = 0;
+ // Returns the priority this context was actually created with. Note: this may not be
+ // the same as specified at context creation time, due to implementation limits on the
+ // number of contexts that can be created at a specific priority level in the system.
+ virtual int getContextPriority() = 0;
protected:
friend class threaded::RenderEngineThreaded;
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index 95ee925..2c34da4 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -53,6 +53,7 @@
const sp<GraphicBuffer>&, const bool, base::unique_fd&&,
base::unique_fd*));
MOCK_METHOD0(cleanFramebufferCache, void());
+ MOCK_METHOD0(getContextPriority, int());
};
} // namespace mock
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index ee0a70a..46ae18c 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -169,17 +169,16 @@
config = chooseEglConfig(display, args.pixelFormat, /*logConfig*/ true);
}
- bool useContextPriority =
- extensions.hasContextPriority() && args.contextPriority == ContextPriority::HIGH;
EGLContext protectedContext = EGL_NO_CONTEXT;
+ const std::optional<RenderEngine::ContextPriority> priority = createContextPriority(args);
if (args.enableProtectedContext && extensions.hasProtectedContent()) {
- protectedContext = createEglContext(display, config, nullptr, useContextPriority,
- Protection::PROTECTED);
+ protectedContext =
+ createEglContext(display, config, nullptr, priority, Protection::PROTECTED);
ALOGE_IF(protectedContext == EGL_NO_CONTEXT, "Can't create protected context");
}
- EGLContext ctxt = createEglContext(display, config, protectedContext, useContextPriority,
- Protection::UNPROTECTED);
+ EGLContext ctxt =
+ createEglContext(display, config, protectedContext, priority, Protection::UNPROTECTED);
// if can't create a GL context, we can only abort.
LOG_ALWAYS_FATAL_IF(ctxt == EGL_NO_CONTEXT, "EGLContext creation failed");
@@ -477,10 +476,23 @@
mGrContext.get());
SkCanvas* canvas = mCapture.tryCapture(surface.get());
+ if (canvas == nullptr) {
+ ALOGE("Cannot acquire canvas from Skia.");
+ return BAD_VALUE;
+ }
// Clear the entire canvas with a transparent black to prevent ghost images.
canvas->clear(SK_ColorTRANSPARENT);
canvas->save();
+ if (mCapture.isCaptureRunning()) {
+ // Record display settings when capture is running.
+ std::stringstream displaySettings;
+ PrintTo(display, &displaySettings);
+ // Store the DisplaySettings in additional information.
+ canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings",
+ SkData::MakeWithCString(displaySettings.str().c_str()));
+ }
+
// Before doing any drawing, let's make sure that we'll start at the origin of the display.
// Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual
// displays might have different scaling when compared to the physical screen.
@@ -511,6 +523,15 @@
for (const auto& layer : layers) {
canvas->save();
+ if (mCapture.isCaptureRunning()) {
+ // Record the name of the layer if the capture is running.
+ std::stringstream layerSettings;
+ PrintTo(*layer, &layerSettings);
+ // Store the LayerSettings in additional information.
+ canvas->drawAnnotation(SkRect::MakeEmpty(), layer->name.c_str(),
+ SkData::MakeWithCString(layerSettings.str().c_str()));
+ }
+
// Layers have a local transform that should be applied to them
canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
@@ -651,13 +672,17 @@
? getSkRect(layer->geometry.roundedCornersCrop)
: dest;
drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
+ } else {
+ // Shadows are assumed to live only on their own layer - it's not valid
+ // to draw the boundary retangles when there is already a caster shadow
+ // TODO(b/175915334): consider relaxing this restriction to enable more flexible
+ // composition - using a well-defined invalid color is long-term less error-prone.
+ // Push the clipRRect onto the clip stack. Draw the image. Pop the clip.
+ if (layer->geometry.roundedCornersRadius > 0) {
+ canvas->clipRRect(getRoundedRect(layer), true);
+ }
+ canvas->drawRect(dest, paint);
}
-
- // Push the clipRRect onto the clip stack. Draw the image. Pop the clip.
- if (layer->geometry.roundedCornersRadius > 0) {
- canvas->clipRRect(getRoundedRect(layer), true);
- }
- canvas->drawRect(dest, paint);
canvas->restore();
}
canvas->restore();
@@ -810,7 +835,8 @@
}
EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext, bool useContextPriority,
+ EGLContext shareContext,
+ std::optional<ContextPriority> contextPriority,
Protection protection) {
EGLint renderableType = 0;
if (config == EGL_NO_CONFIG_KHR) {
@@ -833,9 +859,23 @@
contextAttributes.reserve(7);
contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
contextAttributes.push_back(contextClientVersion);
- if (useContextPriority) {
+ if (contextPriority) {
contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ switch (*contextPriority) {
+ case ContextPriority::REALTIME:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_REALTIME_NV);
+ break;
+ case ContextPriority::MEDIUM:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_MEDIUM_IMG);
+ break;
+ case ContextPriority::LOW:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
+ break;
+ case ContextPriority::HIGH:
+ default:
+ contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ break;
+ }
}
if (protection == Protection::PROTECTED) {
contextAttributes.push_back(EGL_PROTECTED_CONTENT_EXT);
@@ -860,6 +900,29 @@
return context;
}
+std::optional<RenderEngine::ContextPriority> SkiaGLRenderEngine::createContextPriority(
+ const RenderEngineCreationArgs& args) {
+ if (!gl::GLExtensions::getInstance().hasContextPriority()) {
+ return std::nullopt;
+ }
+
+ switch (args.contextPriority) {
+ case RenderEngine::ContextPriority::REALTIME:
+ if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
+ return RenderEngine::ContextPriority::REALTIME;
+ } else {
+ ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
+ return RenderEngine::ContextPriority::HIGH;
+ }
+ case RenderEngine::ContextPriority::HIGH:
+ case RenderEngine::ContextPriority::MEDIUM:
+ case RenderEngine::ContextPriority::LOW:
+ return args.contextPriority;
+ default:
+ return std::nullopt;
+ }
+}
+
EGLSurface SkiaGLRenderEngine::createPlaceholderEglPbufferSurface(EGLDisplay display,
EGLConfig config, int hwcFormat,
Protection protection) {
@@ -884,6 +947,12 @@
void SkiaGLRenderEngine::cleanFramebufferCache() {}
+int SkiaGLRenderEngine::getContextPriority() {
+ int value;
+ eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
+ return value;
+}
+
} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index b53250e..3d8c693 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -56,6 +56,7 @@
const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
void cleanFramebufferCache() override;
+ int getContextPriority() override;
bool isProtected() const override { return mInProtectedContext; }
bool supportsProtectedContent() const override;
bool useProtectedContext(bool useProtectedContext) override;
@@ -68,8 +69,11 @@
private:
static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext, bool useContextPriority,
+ EGLContext shareContext,
+ std::optional<ContextPriority> contextPriority,
Protection protection);
+ static std::optional<RenderEngine::ContextPriority> createContextPriority(
+ const RenderEngineCreationArgs& args);
static EGLSurface createPlaceholderEglPbufferSurface(EGLDisplay display, EGLConfig config,
int hwcFormat, Protection protection);
inline SkRect getSkRect(const FloatRect& layer);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 2352c7e..12b8586 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -56,6 +56,7 @@
return 0;
};
virtual bool cleanupPostRender(CleanupMode) override { return true; };
+ virtual int getContextPriority() override { return 0; }
};
} // namespace skia
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
index 52717a7..eaaf598 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.h
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -45,6 +45,8 @@
SkCanvas* tryCapture(SkSurface* surface);
// Called at the end of every frame.
void endCapture();
+ // Returns whether the capture is running.
+ bool isCaptureRunning() { return mCaptureRunning; }
private:
// Performs the first-frame work of a multi frame SKP capture. Returns true if successful.
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 712e5e2..15fb1b8 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -20,6 +20,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <chrono>
#include <condition_variable>
@@ -305,6 +306,26 @@
backgroundColor.a);
}
+ void expectShadowColorWithoutCaster(const FloatRect& casterBounds,
+ const renderengine::ShadowSettings& shadow,
+ const ubyte4& backgroundColor) {
+ const float shadowInset = shadow.length * -1.0f;
+ const Rect casterRect(casterBounds);
+ const Rect shadowRect =
+ Rect(casterRect).inset(shadowInset, shadowInset, shadowInset, shadowInset);
+
+ const Region backgroundRegion =
+ Region(fullscreenRect()).subtractSelf(casterRect).subtractSelf(shadowRect);
+
+ expectAlpha(shadowRect, 255);
+ // (0, 0, 0) fill on the bounds of the layer should be ignored.
+ expectBufferColor(casterRect, 255, 255, 255, 255, 254);
+
+ // verify background
+ expectBufferColor(backgroundRegion, backgroundColor.r, backgroundColor.g, backgroundColor.b,
+ backgroundColor.a);
+ }
+
static renderengine::ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
bool casterIsTranslucent) {
renderengine::ShadowSettings shadow;
@@ -406,6 +427,12 @@
void fillBufferColorTransform();
template <typename SourceVariant>
+ void fillBufferWithColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
+ void fillBufferColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
void fillRedBufferWithRoundedCorners();
template <typename SourceVariant>
@@ -440,6 +467,10 @@
const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
const ubyte4& backgroundColor);
+ void drawShadowWithoutCaster(const FloatRect& castingBounds,
+ const renderengine::ShadowSettings& shadow,
+ const ubyte4& backgroundColor);
+
std::unique_ptr<renderengine::gl::GLESRenderEngine> mRE;
// Dumb hack to avoid NPE in the EGL driver: the GraphicBuffer needs to
@@ -765,6 +796,36 @@
}
template <typename SourceVariant>
+void RenderEngineTest::fillBufferWithColorTransformZeroLayerAlpha() {
+ renderengine::DisplaySettings settings;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = Rect(1, 1);
+
+ std::vector<const renderengine::LayerSettings*> layers;
+
+ renderengine::LayerSettings layer;
+ layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+ SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
+ layer.alpha = 0;
+
+ // construct a fake color matrix
+ // simple inverse color
+ settings.colorTransform = mat4(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 1, 1, 1, 1);
+
+ layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+
+ layers.push_back(&layer);
+
+ invokeDraw(settings, layers, mBuffer);
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferColorTransformZeroLayerAlpha() {
+ fillBufferWithColorTransformZeroLayerAlpha<SourceVariant>();
+ expectBufferColor(fullscreenRect(), 0, 0, 0, 0);
+}
+
+template <typename SourceVariant>
void RenderEngineTest::fillRedBufferWithRoundedCorners() {
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
@@ -1082,6 +1143,37 @@
invokeDraw(settings, layers, mBuffer);
}
+void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds,
+ const renderengine::ShadowSettings& shadow,
+ const ubyte4& backgroundColor) {
+ renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = fullscreenRect();
+
+ std::vector<const renderengine::LayerSettings*> layers;
+
+ // add background layer
+ renderengine::LayerSettings bgLayer;
+ bgLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ bgLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+ ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
+ backgroundColor.b / 255.0f, this);
+ bgLayer.alpha = backgroundColor.a / 255.0f;
+ layers.push_back(&bgLayer);
+
+ // add shadow layer
+ renderengine::LayerSettings shadowLayer;
+ shadowLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ shadowLayer.geometry.boundaries = castingBounds;
+ shadowLayer.alpha = 1.0f;
+ ColorSourceVariant::fillColor(shadowLayer, 0, 0, 0, this);
+ shadowLayer.shadow = shadow;
+ layers.push_back(&shadowLayer);
+
+ invokeDraw(settings, layers, mBuffer);
+}
+
INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
testing::Values(std::make_shared<GLESRenderEngineFactory>(),
std::make_shared<GLESCMRenderEngineFactory>(),
@@ -1240,6 +1332,13 @@
fillBufferWithRoundedCorners<ColorSourceVariant>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<ColorSourceVariant>();
+}
+
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
@@ -1337,6 +1436,12 @@
fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
const auto& renderEngineFactory = GetParam();
@@ -1436,6 +1541,13 @@
fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
@@ -1552,6 +1664,22 @@
EXPECT_FALSE(mRE->isImageCachedForTesting(bufferId));
}
+TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ const ubyte4 backgroundColor(255, 255, 255, 255);
+ const float shadowLength = 5.0f;
+ Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
+ casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
+ renderengine::ShadowSettings settings =
+ getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
+ false /* casterIsTranslucent */);
+
+ drawShadowWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
+ expectShadowColorWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
+}
+
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
@@ -1788,4 +1916,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 5453302..08f2949 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -304,6 +304,21 @@
resultFuture.wait();
}
+int RenderEngineThreaded::getContextPriority() {
+ std::promise<int> resultPromise;
+ std::future<int> resultFuture = resultPromise.get_future();
+ {
+ std::lock_guard lock(mThreadMutex);
+ mFunctionCalls.push([&resultPromise](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::getContextPriority");
+ int priority = instance.getContextPriority();
+ resultPromise.set_value(priority);
+ });
+ }
+ mCondition.notify_one();
+ return resultFuture.get();
+}
+
} // namespace threaded
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index cdfbd04..8b1e2de 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -63,6 +63,7 @@
base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
void cleanFramebufferCache() override;
+ int getContextPriority() override;
private:
void threadMain(CreateInstanceFactory factory);
diff --git a/libs/ui/include/ui/BlurRegion.h b/libs/ui/include/ui/BlurRegion.h
index c5a5d47..69a586e 100644
--- a/libs/ui/include/ui/BlurRegion.h
+++ b/libs/ui/include/ui/BlurRegion.h
@@ -17,6 +17,8 @@
#pragma once
#include <inttypes.h>
+#include <iosfwd>
+#include <iostream>
namespace android {
@@ -33,4 +35,19 @@
int bottom;
};
+static inline void PrintTo(const BlurRegion& blurRegion, ::std::ostream* os) {
+ *os << "BlurRegion {";
+ *os << "\n .blurRadius = " << blurRegion.blurRadius;
+ *os << "\n .cornerRadiusTL = " << blurRegion.cornerRadiusTL;
+ *os << "\n .cornerRadiusTR = " << blurRegion.cornerRadiusTR;
+ *os << "\n .cornerRadiusBL = " << blurRegion.cornerRadiusBL;
+ *os << "\n .cornerRadiusBR = " << blurRegion.cornerRadiusBR;
+ *os << "\n .alpha = " << blurRegion.alpha;
+ *os << "\n .left = " << blurRegion.left;
+ *os << "\n .top = " << blurRegion.top;
+ *os << "\n .right = " << blurRegion.right;
+ *os << "\n .bottom = " << blurRegion.bottom;
+ *os << "\n}";
+}
+
} // namespace android
\ No newline at end of file
diff --git a/opengl/Android.bp b/opengl/Android.bp
index 393ced7..8b94f61 100644
--- a/opengl/Android.bp
+++ b/opengl/Android.bp
@@ -52,36 +52,18 @@
license: "include/KHR/NOTICE",
}
-llndk_library {
- name: "libEGL.llndk",
- symbol_file: "libs/libEGL.map.txt",
- export_include_dirs: ["include"],
-}
-
-llndk_library {
- name: "libGLESv1_CM.llndk",
- symbol_file: "libs/libGLESv1_CM.map.txt",
- export_include_dirs: ["include"],
-}
-
-llndk_library {
- name: "libGLESv2.llndk",
- symbol_file: "libs/libGLESv2.map.txt",
- export_include_dirs: ["include"],
-}
-
-llndk_library {
- name: "libGLESv3.llndk",
- symbol_file: "libs/libGLESv3.map.txt",
- export_include_dirs: ["include"],
-}
-
cc_library_headers {
name: "gl_headers",
+ host_supported: true,
vendor_available: true,
export_include_dirs: ["include"],
}
+llndk_headers {
+ name: "gl_llndk_headers",
+ export_include_dirs: ["include"],
+}
+
subdirs = [
"*",
]
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 77d887c..5d17561 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -225,3 +225,27 @@
srcs: ["GLES2/gl2.cpp"],
cflags: ["-DLOG_TAG=\"libGLESv3\""],
}
+
+llndk_library {
+ name: "libEGL.llndk",
+ symbol_file: "libEGL.map.txt",
+ export_llndk_headers: ["gl_llndk_headers"],
+}
+
+llndk_library {
+ name: "libGLESv1_CM.llndk",
+ symbol_file: "libGLESv1_CM.map.txt",
+ export_llndk_headers: ["gl_llndk_headers"],
+}
+
+llndk_library {
+ name: "libGLESv2.llndk",
+ symbol_file: "libGLESv2.map.txt",
+ export_llndk_headers: ["gl_llndk_headers"],
+}
+
+llndk_library {
+ name: "libGLESv3.llndk",
+ symbol_file: "libGLESv3.map.txt",
+ export_llndk_headers: ["gl_llndk_headers"],
+}
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index 940a26b..9606daa 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -20,6 +20,7 @@
},
srcs: [
"GpuMemTest.cpp",
+ "GpuMemTracerTest.cpp",
"GpuStatsTest.cpp",
],
shared_libs: [
@@ -29,14 +30,19 @@
"libcutils",
"libgfxstats",
"libgpumem",
+ "libgpumemtracer",
"libgraphicsenv",
"liblog",
+ "libprotobuf-cpp-lite",
+ "libprotoutil",
"libstatslog",
"libstatspull",
"libutils",
],
static_libs: [
"libgmock",
+ "libperfetto_client_experimental",
+ "perfetto_trace_protos",
],
require_root: true,
}
diff --git a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
new file mode 100644
index 0000000..cd8e19c
--- /dev/null
+++ b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "gpuservice_unittest"
+
+#include <bpf/BpfMap.h>
+#include <gpumem/GpuMem.h>
+#include <gtest/gtest.h>
+#include <perfetto/trace/trace.pb.h>
+#include <tracing/GpuMemTracer.h>
+
+#include "TestableGpuMem.h"
+
+namespace android {
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint64_t TEST_GLOBAL_KEY = 0;
+constexpr uint32_t TEST_GLOBAL_PID = 0;
+constexpr uint64_t TEST_GLOBAL_VAL = 123;
+constexpr uint32_t TEST_GLOBAL_GPU_ID = 0;
+constexpr uint64_t TEST_PROC_KEY_1 = 1;
+constexpr uint32_t TEST_PROC_PID_1 = 1;
+constexpr uint64_t TEST_PROC_VAL_1 = 234;
+constexpr uint32_t TEST_PROC_1_GPU_ID = 0;
+constexpr uint64_t TEST_PROC_KEY_2 = 4294967298; // (1 << 32) + 2
+constexpr uint32_t TEST_PROC_PID_2 = 2;
+constexpr uint64_t TEST_PROC_VAL_2 = 345;
+constexpr uint32_t TEST_PROC_2_GPU_ID = 1;
+
+class GpuMemTracerTest : public testing::Test {
+public:
+ GpuMemTracerTest() {
+ 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());
+ }
+
+ ~GpuMemTracerTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+ void SetUp() override {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+ bpf::setrlimitForTest();
+
+ mGpuMem = std::make_shared<GpuMem>();
+ mGpuMemTracer = std::make_unique<GpuMemTracer>();
+ mGpuMemTracer->initializeForTest(mGpuMem);
+ mTestableGpuMem = TestableGpuMem(mGpuMem.get());
+
+ errno = 0;
+ mTestMap = bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE,
+ BPF_F_NO_PREALLOC);
+
+ EXPECT_EQ(0, errno);
+ EXPECT_LE(0, mTestMap.getMap().get());
+ EXPECT_TRUE(mTestMap.isValid());
+ }
+
+ int getTracerThreadCount() { return mGpuMemTracer->tracerThreadCount; }
+
+ std::vector<perfetto::protos::TracePacket> readGpuMemTotalPacketsBlocking(
+ perfetto::TracingSession* tracingSession) {
+ std::vector<char> raw_trace = tracingSession->ReadTraceBlocking();
+ perfetto::protos::Trace trace;
+ trace.ParseFromArray(raw_trace.data(), int(raw_trace.size()));
+
+ std::vector<perfetto::protos::TracePacket> packets;
+ for (const auto& packet : trace.packet()) {
+ if (!packet.has_gpu_mem_total_event()) {
+ continue;
+ }
+ packets.emplace_back(packet);
+ }
+ return packets;
+ }
+
+ std::shared_ptr<GpuMem> mGpuMem;
+ TestableGpuMem mTestableGpuMem;
+ std::unique_ptr<GpuMemTracer> mGpuMemTracer;
+ bpf::BpfMap<uint64_t, uint64_t> mTestMap;
+};
+
+static constexpr uint64_t getSizeForPid(uint32_t pid) {
+ switch (pid) {
+ case TEST_GLOBAL_PID:
+ return TEST_GLOBAL_VAL;
+ case TEST_PROC_PID_1:
+ return TEST_PROC_VAL_1;
+ case TEST_PROC_PID_2:
+ return TEST_PROC_VAL_2;
+ }
+ return 0;
+}
+
+static constexpr uint32_t getGpuIdForPid(uint32_t pid) {
+ switch (pid) {
+ case TEST_GLOBAL_PID:
+ return TEST_GLOBAL_GPU_ID;
+ case TEST_PROC_PID_1:
+ return TEST_PROC_1_GPU_ID;
+ case TEST_PROC_PID_2:
+ return TEST_PROC_2_GPU_ID;
+ }
+ return 0;
+}
+
+TEST_F(GpuMemTracerTest, traceInitialCountersAfterGpuMemInitialize) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+ ASSERT_RESULT_OK(mTestMap.writeValue(TEST_GLOBAL_KEY, TEST_GLOBAL_VAL, BPF_ANY));
+ ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_1, TEST_PROC_VAL_1, BPF_ANY));
+ ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_2, TEST_PROC_VAL_2, BPF_ANY));
+ mTestableGpuMem.setGpuMemTotalMap(mTestMap);
+ mTestableGpuMem.setInitialized();
+
+ // Only 1 tracer thread should be existing for test.
+ EXPECT_EQ(getTracerThreadCount(), 1);
+ auto tracingSession = mGpuMemTracer->getTracingSessionForTest();
+
+ tracingSession->StartBlocking();
+ // Sleep for a short time to let the tracer thread finish its work
+ sleep(1);
+ tracingSession->StopBlocking();
+
+ // The test tracer thread should have finished its execution by now.
+ EXPECT_EQ(getTracerThreadCount(), 0);
+
+ auto packets = readGpuMemTotalPacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 3);
+
+ const auto& packet0 = packets[0];
+ ASSERT_TRUE(packet0.has_timestamp());
+ ASSERT_TRUE(packet0.has_gpu_mem_total_event());
+ const auto& gpuMemEvent0 = packet0.gpu_mem_total_event();
+ ASSERT_TRUE(gpuMemEvent0.has_pid());
+ const auto& pid0 = gpuMemEvent0.pid();
+ ASSERT_TRUE(gpuMemEvent0.has_size());
+ EXPECT_EQ(gpuMemEvent0.size(), getSizeForPid(pid0));
+ ASSERT_TRUE(gpuMemEvent0.has_gpu_id());
+ EXPECT_EQ(gpuMemEvent0.gpu_id(), getGpuIdForPid(pid0));
+
+ const auto& packet1 = packets[1];
+ ASSERT_TRUE(packet1.has_timestamp());
+ ASSERT_TRUE(packet1.has_gpu_mem_total_event());
+ const auto& gpuMemEvent1 = packet1.gpu_mem_total_event();
+ ASSERT_TRUE(gpuMemEvent1.has_pid());
+ const auto& pid1 = gpuMemEvent1.pid();
+ ASSERT_TRUE(gpuMemEvent1.has_size());
+ EXPECT_EQ(gpuMemEvent1.size(), getSizeForPid(pid1));
+ ASSERT_TRUE(gpuMemEvent1.has_gpu_id());
+ EXPECT_EQ(gpuMemEvent1.gpu_id(), getGpuIdForPid(pid1));
+
+ const auto& packet2 = packets[2];
+ ASSERT_TRUE(packet2.has_timestamp());
+ ASSERT_TRUE(packet2.has_gpu_mem_total_event());
+ const auto& gpuMemEvent2 = packet2.gpu_mem_total_event();
+ ASSERT_TRUE(gpuMemEvent2.has_pid());
+ const auto& pid2 = gpuMemEvent2.pid();
+ ASSERT_TRUE(gpuMemEvent2.has_size());
+ EXPECT_EQ(gpuMemEvent2.size(), getSizeForPid(pid2));
+ ASSERT_TRUE(gpuMemEvent2.has_gpu_id());
+ EXPECT_EQ(gpuMemEvent2.gpu_id(), getGpuIdForPid(pid2));
+}
+
+TEST_F(GpuMemTracerTest, noTracingWithoutGpuMemInitialize) {
+ // Only 1 tracer thread should be existing for test.
+ EXPECT_EQ(getTracerThreadCount(), 1);
+
+ auto tracingSession = mGpuMemTracer->getTracingSessionForTest();
+
+ tracingSession->StartBlocking();
+ // Sleep for a short time to let the tracer thread finish its work
+ sleep(1);
+ tracingSession->StopBlocking();
+
+ // The test tracer thread should have finished its execution by now.
+ EXPECT_EQ(getTracerThreadCount(), 0);
+
+ auto packets = readGpuMemTotalPacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 0);
+}
+} // namespace android
diff --git a/services/gpuservice/tracing/GpuMemTracer.cpp b/services/gpuservice/tracing/GpuMemTracer.cpp
index 000cf27..6975151 100644
--- a/services/gpuservice/tracing/GpuMemTracer.cpp
+++ b/services/gpuservice/tracing/GpuMemTracer.cpp
@@ -44,9 +44,35 @@
args.backends = perfetto::kSystemBackend;
perfetto::Tracing::Initialize(args);
registerDataSource();
- std::thread tracerThread(&GpuMemTracer::threadLoop, this);
+ std::thread tracerThread(&GpuMemTracer::threadLoop, this, true);
pthread_setname_np(tracerThread.native_handle(), "GpuMemTracerThread");
tracerThread.detach();
+ tracerThreadCount++;
+}
+
+void GpuMemTracer::initializeForTest(std::shared_ptr<GpuMem> gpuMem) {
+ mGpuMem = gpuMem;
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kInProcessBackend;
+ perfetto::Tracing::Initialize(args);
+ registerDataSource();
+ std::thread tracerThread(&GpuMemTracer::threadLoop, this, false);
+ pthread_setname_np(tracerThread.native_handle(), "GpuMemTracerThreadForTest");
+ tracerThread.detach();
+ tracerThreadCount++;
+}
+
+// Each tracing session can be used for a single block of Start -> Stop.
+std::unique_ptr<perfetto::TracingSession> GpuMemTracer::getTracingSessionForTest() {
+ perfetto::TraceConfig cfg;
+ cfg.set_duration_ms(500);
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ ds_cfg->set_name(GpuMemTracer::kGpuMemDataSource);
+
+ auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+ tracingSession->Setup(cfg);
+ return tracingSession;
}
void GpuMemTracer::registerDataSource() {
@@ -55,8 +81,8 @@
GpuMemDataSource::Register(dsd);
}
-void GpuMemTracer::threadLoop() {
- while (true) {
+void GpuMemTracer::threadLoop(bool infiniteLoop) {
+ do {
{
std::unique_lock<std::mutex> lock(GpuMemTracer::sTraceMutex);
while (!sTraceStarted) {
@@ -68,7 +94,11 @@
std::lock_guard<std::mutex> lock(GpuMemTracer::sTraceMutex);
sTraceStarted = false;
}
- }
+ } while (infiniteLoop);
+
+ // Thread loop is exiting. Reduce the tracerThreadCount to reflect the number of active threads
+ // in the wait loop.
+ tracerThreadCount--;
}
void GpuMemTracer::traceInitialCounters() {
diff --git a/services/gpuservice/tracing/include/tracing/GpuMemTracer.h b/services/gpuservice/tracing/include/tracing/GpuMemTracer.h
index 40deb4c..ae871f1 100644
--- a/services/gpuservice/tracing/include/tracing/GpuMemTracer.h
+++ b/services/gpuservice/tracing/include/tracing/GpuMemTracer.h
@@ -20,6 +20,10 @@
#include <mutex>
+namespace perfetto::protos {
+class TracePacket;
+}
+
namespace android {
class GpuMem;
@@ -45,16 +49,37 @@
// perfetto::kInProcessBackend in tests.
void registerDataSource();
+ // TODO(b/175904796): Refactor gpuservice lib to include perfetto lib and move the test
+ // functions into the unittests.
+ // Functions only used for testing with in-process backend. These functions require the static
+ // perfetto lib to be linked. If the tests have a perfetto linked, while libgpumemtracer.so also
+ // has one linked, they will both use different static states maintained in perfetto. Since the
+ // static perfetto states are not shared, tracing sessions created in the unit test are not
+ // recognized by GpuMemTracer. As a result, we cannot use any of the perfetto functions from
+ // this class, which defeats the purpose of the unit test. To solve this, we restrict all
+ // tracing functionality to this class, while the unit test validates the data.
+ // Sets up the perfetto in-process backend and calls into registerDataSource.
+ void initializeForTest(std::shared_ptr<GpuMem>);
+ // Creates a tracing session with in process backend, for testing.
+ std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest();
+ // Read and filter the gpu memory packets from the created trace.
+ std::vector<perfetto::protos::TracePacket> readGpuMemTotalPacketsForTestBlocking(
+ perfetto::TracingSession* tracingSession);
+
static constexpr char kGpuMemDataSource[] = "android.gpu.memory";
static std::condition_variable sCondition;
static std::mutex sTraceMutex;
static bool sTraceStarted;
private:
- void traceInitialCounters();
- void threadLoop();
+ // Friend class for testing
+ friend class GpuMemTracerTest;
+ void threadLoop(bool infiniteLoop);
+ void traceInitialCounters();
std::shared_ptr<GpuMem> mGpuMem;
+ // Count of how many tracer threads are currently active. Useful for testing.
+ std::atomic<int32_t> tracerThreadCount = 0;
};
} // namespace android
diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputClassifier.cpp
index eafb5ab..f5f0400 100644
--- a/services/inputflinger/InputClassifier.cpp
+++ b/services/inputflinger/InputClassifier.cpp
@@ -391,6 +391,11 @@
mListener->notifyMotion(&newArgs);
}
+void InputClassifier::notifySensor(const NotifySensorArgs* args) {
+ // pass through
+ mListener->notifySensor(args);
+}
+
void InputClassifier::notifySwitch(const NotifySwitchArgs* args) {
// pass through
mListener->notifySwitch(args);
diff --git a/services/inputflinger/InputClassifier.h b/services/inputflinger/InputClassifier.h
index 6965940..bf10920 100644
--- a/services/inputflinger/InputClassifier.h
+++ b/services/inputflinger/InputClassifier.h
@@ -229,6 +229,7 @@
virtual void notifyKey(const NotifyKeyArgs* args) override;
virtual void notifyMotion(const NotifyMotionArgs* args) override;
virtual void notifySwitch(const NotifySwitchArgs* args) override;
+ virtual void notifySensor(const NotifySensorArgs* args) override;
virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 49a813e..4be49b1 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -211,6 +211,41 @@
listener->notifySwitch(this);
}
+// --- NotifySensorArgs ---
+
+NotifySensorArgs::NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
+ nsecs_t hwTimestamp, std::vector<float> values)
+ : NotifyArgs(id, eventTime),
+ deviceId(deviceId),
+ source(source),
+ sensorType(sensorType),
+ accuracy(accuracy),
+ accuracyChanged(accuracyChanged),
+ hwTimestamp(hwTimestamp),
+ values(std::move(values)) {}
+
+NotifySensorArgs::NotifySensorArgs(const NotifySensorArgs& other)
+ : NotifyArgs(other.id, other.eventTime),
+ deviceId(other.deviceId),
+ source(other.source),
+ sensorType(other.sensorType),
+ accuracy(other.accuracy),
+ accuracyChanged(other.accuracyChanged),
+ hwTimestamp(other.hwTimestamp),
+ values(other.values) {}
+
+bool NotifySensorArgs::operator==(const NotifySensorArgs rhs) const {
+ return id == rhs.id && eventTime == rhs.eventTime && sensorType == rhs.sensorType &&
+ accuracy == rhs.accuracy && accuracyChanged == rhs.accuracyChanged &&
+ hwTimestamp == rhs.hwTimestamp && values == rhs.values;
+}
+
+void NotifySensorArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifySensor(this);
+}
+
// --- NotifyDeviceResetArgs ---
NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId)
@@ -286,6 +321,11 @@
mArgsQueue.push_back(new NotifySwitchArgs(*args));
}
+void QueuedInputListener::notifySensor(const NotifySensorArgs* args) {
+ traceEvent(__func__, args->id);
+ mArgsQueue.push_back(new NotifySensorArgs(*args));
+}
+
void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
traceEvent(__func__, args->id);
mArgsQueue.push_back(new NotifyDeviceResetArgs(*args));
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 3d99589..a50e5c7 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -148,7 +148,7 @@
}
base::Result<std::unique_ptr<InputChannel>> channel = mDispatcher->createInputChannel(name);
- if (!channel) {
+ if (!channel.ok()) {
return binder::Status::fromExceptionCode(exceptionCodeFromStatusT(channel.error().code()),
channel.error().message().c_str());
}
diff --git a/services/inputflinger/OWNERS b/services/inputflinger/OWNERS
index 0313a40..82c6ee1 100644
--- a/services/inputflinger/OWNERS
+++ b/services/inputflinger/OWNERS
@@ -1,2 +1,3 @@
+lzye@google.com
michaelwr@google.com
svv@google.com
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 9fea298..4e55872 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -66,6 +66,13 @@
void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
+ void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+ const std::vector<float>& values) override {}
+
+ void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy) override {}
+
void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
@@ -96,6 +103,8 @@
void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+ void setPointerCapture(bool enabled) override {}
+
InputDispatcherConfiguration mConfig;
};
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 29df00b..6953d04 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -114,6 +114,22 @@
return StringPrintf("FocusEvent(hasFocus=%s)", hasFocus ? "true" : "false");
}
+// --- PointerCaptureChangedEntry ---
+
+// PointerCaptureChanged notifications always go to apps, so set the flag POLICY_FLAG_PASS_TO_USER
+// for all entries.
+PointerCaptureChangedEntry::PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime,
+ bool hasPointerCapture)
+ : EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
+ pointerCaptureEnabled(hasPointerCapture) {}
+
+PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
+
+std::string PointerCaptureChangedEntry::getDescription() const {
+ return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
+ pointerCaptureEnabled ? "true" : "false");
+}
+
// --- KeyEntry ---
KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
@@ -223,6 +239,41 @@
return msg;
}
+// --- SensorEntry ---
+
+SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, nsecs_t hwTimestamp,
+ InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy,
+ bool accuracyChanged, std::vector<float> values)
+ : EventEntry(id, Type::SENSOR, eventTime, policyFlags),
+ deviceId(deviceId),
+ source(source),
+ sensorType(sensorType),
+ accuracy(accuracy),
+ accuracyChanged(accuracyChanged),
+ hwTimestamp(hwTimestamp),
+ values(std::move(values)) {}
+
+SensorEntry::~SensorEntry() {}
+
+std::string SensorEntry::getDescription() const {
+ std::string msg;
+ msg += StringPrintf("SensorEntry(deviceId=%d, source=0x%08x, sensorType=0x%08x, "
+ "accuracy=0x%08x, hwTimestamp=%" PRId64,
+ deviceId, source, sensorType, accuracy, hwTimestamp);
+
+ if (!GetBoolProperty("ro.debuggable", false)) {
+ for (size_t i = 0; i < values.size(); i++) {
+ if (i > 0) {
+ msg += ", ";
+ }
+ msg += StringPrintf("(%.3f)", values[i]);
+ }
+ }
+ msg += StringPrintf(", policyFlags=0x%08x", policyFlags);
+ return msg;
+}
+
// --- DispatchEntry ---
volatile int32_t DispatchEntry::sNextSeqAtomic;
@@ -255,7 +306,8 @@
keyEntry(nullptr),
userActivityEventType(0),
seq(0),
- handled(false) {}
+ handled(false),
+ enabled(false) {}
CommandEntry::~CommandEntry() {}
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 0661709..3a860f0 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -36,23 +36,10 @@
FOCUS,
KEY,
MOTION,
+ SENSOR,
+ POINTER_CAPTURE_CHANGED,
};
- static const char* typeToString(Type type) {
- switch (type) {
- case Type::CONFIGURATION_CHANGED:
- return "CONFIGURATION_CHANGED";
- case Type::DEVICE_RESET:
- return "DEVICE_RESET";
- case Type::FOCUS:
- return "FOCUS";
- case Type::KEY:
- return "KEY";
- case Type::MOTION:
- return "MOTION";
- }
- }
-
int32_t id;
Type type;
nsecs_t eventTime;
@@ -115,6 +102,15 @@
virtual ~FocusEntry();
};
+struct PointerCaptureChangedEntry : EventEntry {
+ bool pointerCaptureEnabled;
+
+ PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
+ std::string getDescription() const override;
+
+ virtual ~PointerCaptureChangedEntry();
+};
+
struct KeyEntry : EventEntry {
int32_t deviceId;
uint32_t source;
@@ -179,6 +175,25 @@
virtual ~MotionEntry();
};
+struct SensorEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ InputDeviceSensorType sensorType;
+ InputDeviceSensorAccuracy accuracy;
+ bool accuracyChanged;
+ nsecs_t hwTimestamp;
+
+ std::vector<float> values;
+
+ SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, nsecs_t hwTimestamp, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
+ std::vector<float> values);
+ std::string getDescription() const override;
+
+ virtual ~SensorEntry();
+};
+
// Tracks the progress of dispatching a particular event to a particular connection.
struct DispatchEntry {
const uint32_t seq; // unique sequence number, never 0
@@ -245,6 +260,7 @@
sp<Connection> connection;
nsecs_t eventTime;
std::shared_ptr<KeyEntry> keyEntry;
+ std::shared_ptr<SensorEntry> sensorEntry;
std::shared_ptr<InputApplicationHandle> inputApplicationHandle;
std::string reason;
int32_t userActivityEventType;
@@ -254,6 +270,7 @@
sp<IBinder> oldToken;
sp<IBinder> newToken;
std::string obscuringPackage;
+ bool enabled;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index ed4f05a..f3d969e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -47,8 +47,6 @@
// Log debug messages about hover events.
#define DEBUG_HOVER 0
-#include "InputDispatcher.h"
-
#include <android-base/chrono_utils.h>
#include <android-base/stringprintf.h>
#include <android/os/IInputConstants.h>
@@ -73,6 +71,7 @@
#include <sstream>
#include "Connection.h"
+#include "InputDispatcher.h"
#define INDENT " "
#define INDENT2 " "
@@ -322,6 +321,17 @@
static std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
std::shared_ptr<EventEntry> eventEntry,
int32_t inputTargetFlags) {
+ if (eventEntry->type == EventEntry::Type::MOTION) {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
+ if (motionEntry.source & AINPUT_SOURCE_CLASS_JOYSTICK) {
+ const ui::Transform identityTransform;
+ // Use identity transform for joystick events events because they don't depend on
+ // the window info
+ return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, identityTransform,
+ 1.0f /*globalScaleFactor*/);
+ }
+ }
+
if (inputTarget.useDefaultPointerTransform()) {
const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
@@ -435,6 +445,14 @@
return interface_cast<IPlatformCompatNative>(service);
}
+static KeyEvent createKeyEvent(const KeyEntry& entry) {
+ KeyEvent event;
+ event.initialize(entry.id, entry.deviceId, entry.source, entry.displayId, INVALID_HMAC,
+ entry.action, entry.flags, entry.keyCode, entry.scanCode, entry.metaState,
+ entry.repeatCount, entry.downTime, entry.eventTime);
+ return event;
+}
+
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -454,6 +472,8 @@
mInTouchMode(true),
mMaximumObscuringOpacityForTouch(1.0f),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+ mFocusedWindowRequestedPointerCapture(false),
+ mWindowTokenWithPointerCapture(nullptr),
mCompatService(getCompatService()) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -713,6 +733,14 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto typedEntry =
+ std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
+ dispatchPointerCaptureChangedLocked(currentTime, typedEntry, dropReason);
+ done = true;
+ break;
+ }
+
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
if (isAppSwitchDue) {
@@ -748,6 +776,23 @@
done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
break;
}
+
+ case EventEntry::Type::SENSOR: {
+ std::shared_ptr<SensorEntry> sensorEntry =
+ std::static_pointer_cast<SensorEntry>(mPendingEvent);
+ if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DropReason::APP_SWITCH;
+ }
+ // Sensor timestamps use SYSTEM_TIME_BOOTTIME time base, so we can't use
+ // 'currentTime' here, get SYSTEM_TIME_BOOTTIME instead.
+ nsecs_t bootTime = systemTime(SYSTEM_TIME_BOOTTIME);
+ if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(bootTime, *sensorEntry)) {
+ dropReason = DropReason::STALE;
+ }
+ dispatchSensorLocked(currentTime, sensorEntry, &dropReason, nextWakeupTime);
+ done = true;
+ break;
+ }
}
if (done) {
@@ -862,7 +907,9 @@
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::SENSOR:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
// nothing to do
break;
}
@@ -872,7 +919,10 @@
}
void InputDispatcher::addRecentEventLocked(std::shared_ptr<EventEntry> entry) {
- mRecentQueue.push_back(entry);
+ // Do not store sensor event in recent queue to avoid flooding the queue.
+ if (entry->type != EventEntry::Type::SENSOR) {
+ mRecentQueue.push_back(entry);
+ }
if (mRecentQueue.size() > RECENT_QUEUE_MAX_SIZE) {
mRecentQueue.pop_front();
}
@@ -968,6 +1018,10 @@
ALOGI("Dropped event because it is stale.");
reason = "inbound event was dropped because it is stale";
break;
+ case DropReason::NO_POINTER_CAPTURE:
+ ALOGI("Dropped event because there is no window with Pointer Capture.");
+ reason = "inbound event was dropped because there is no window with Pointer Capture";
+ break;
case DropReason::NOT_DROPPED: {
LOG_ALWAYS_FATAL("Should not be dropping a NOT_DROPPED event");
return;
@@ -991,10 +1045,16 @@
}
break;
}
+ case EventEntry::Type::SENSOR: {
+ break;
+ }
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ break;
+ }
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
- LOG_ALWAYS_FATAL("Should not drop %s events", EventEntry::typeToString(entry.type));
+ LOG_ALWAYS_FATAL("Should not drop %s events", NamedEnum::string(entry.type).c_str());
break;
}
}
@@ -1176,6 +1236,55 @@
dispatchEventLocked(currentTime, entry, {target});
}
+void InputDispatcher::dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) {
+ const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
+ if (entry->pointerCaptureEnabled == haveWindowWithPointerCapture) {
+ LOG_ALWAYS_FATAL_IF(mFocusedWindowRequestedPointerCapture,
+ "The Pointer Capture state has already been dispatched to the window.");
+ // Pointer capture was already forcefully disabled because of focus change.
+ dropReason = DropReason::NOT_DROPPED;
+ return;
+ }
+
+ // Set drop reason for early returns
+ dropReason = DropReason::NO_POINTER_CAPTURE;
+
+ sp<IBinder> token;
+ if (entry->pointerCaptureEnabled) {
+ // Enable Pointer Capture
+ if (!mFocusedWindowRequestedPointerCapture) {
+ // This can happen if a window requests capture and immediately releases capture.
+ ALOGW("No window requested Pointer Capture.");
+ return;
+ }
+ token = getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+ mWindowTokenWithPointerCapture = token;
+ } else {
+ // Disable Pointer Capture
+ token = mWindowTokenWithPointerCapture;
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ }
+
+ auto channel = getInputChannelLocked(token);
+ if (channel == nullptr) {
+ // Window has gone away, clean up Pointer Capture state.
+ mWindowTokenWithPointerCapture = nullptr;
+ mFocusedWindowRequestedPointerCapture = false;
+ return;
+ }
+ InputTarget target;
+ target.inputChannel = channel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ entry->dispatchInProgress = true;
+ dispatchEventLocked(currentTime, entry, {target});
+
+ dropReason = DropReason::NOT_DROPPED;
+}
+
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
@@ -1298,6 +1407,51 @@
#endif
}
+void InputDispatcher::doNotifySensorLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ const std::shared_ptr<SensorEntry>& entry = commandEntry->sensorEntry;
+ if (entry->accuracyChanged) {
+ mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
+ }
+ mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
+ entry->hwTimestamp, entry->values);
+ mLock.lock();
+}
+
+void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime, std::shared_ptr<SensorEntry> entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
+ "source=0x%x, sensorType=%s",
+ entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
+ NamedEnum::string(sensorType).c_str());
+#endif
+ std::unique_ptr<CommandEntry> commandEntry =
+ std::make_unique<CommandEntry>(&InputDispatcher::doNotifySensorLockedInterruptible);
+ commandEntry->sensorEntry = entry;
+ postCommandLocked(std::move(commandEntry));
+}
+
+bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("flushSensor deviceId=%d, sensorType=%s", deviceId,
+ NamedEnum::string(sensorType).c_str());
+#endif
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ for (auto it = mInboundQueue.begin(); it != mInboundQueue.end(); it++) {
+ std::shared_ptr<EventEntry> entry = *it;
+ if (entry->type == EventEntry::Type::SENSOR) {
+ it = mInboundQueue.erase(it);
+ releaseInboundEventLocked(entry);
+ }
+ }
+ }
+ return true;
+}
+
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
ATRACE_CALL();
@@ -1483,10 +1637,12 @@
displayId = motionEntry.displayId;
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
- ALOGE("%s events do not have a target display", EventEntry::typeToString(entry.type));
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::SENSOR: {
+ ALOGE("%s events do not have a target display", NamedEnum::string(entry.type).c_str());
return ADISPLAY_ID_NONE;
}
}
@@ -1540,7 +1696,7 @@
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
- EventEntry::typeToString(entry.type), displayId);
+ NamedEnum::string(entry.type).c_str(), displayId);
return InputEventInjectionResult::FAILED;
}
@@ -1565,7 +1721,7 @@
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
- EventEntry::typeToString(entry.type));
+ NamedEnum::string(entry.type).c_str());
return InputEventInjectionResult::FAILED;
} else {
// Still waiting for the focused window
@@ -1877,6 +2033,8 @@
}
if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
}
BitSet32 pointerIds;
@@ -2366,8 +2524,10 @@
}
void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
- if (eventEntry.type == EventEntry::Type::FOCUS) {
- // Focus events are passed to apps, but do not represent user activity.
+ if (eventEntry.type == EventEntry::Type::FOCUS ||
+ eventEntry.type == EventEntry::Type::POINTER_CAPTURE_CHANGED) {
+ // Focus or pointer capture changed events are passed to apps, but do not represent user
+ // activity.
return;
}
int32_t displayId = getTargetDisplayId(eventEntry);
@@ -2405,9 +2565,11 @@
}
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::SENSOR:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
LOG_ALWAYS_FATAL("%s events are not user activity",
- EventEntry::typeToString(eventEntry.type));
+ NamedEnum::string(eventEntry.type).c_str());
break;
}
}
@@ -2451,7 +2613,7 @@
if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
"Entry type %s should not have FLAG_SPLIT",
- EventEntry::typeToString(eventEntry->type));
+ NamedEnum::string(eventEntry->type).c_str());
const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
@@ -2617,13 +2779,18 @@
break;
}
- case EventEntry::Type::FOCUS: {
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ break;
+ }
+ case EventEntry::Type::SENSOR: {
+ LOG_ALWAYS_FATAL("SENSOR events should not go to apps via input channel");
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
LOG_ALWAYS_FATAL("%s events should not go to apps",
- EventEntry::typeToString(newEntry.type));
+ NamedEnum::string(newEntry.type).c_str());
break;
}
}
@@ -2821,6 +2988,7 @@
reportTouchEventForStatistics(motionEntry);
break;
}
+
case EventEntry::Type::FOCUS: {
const FocusEntry& focusEntry = static_cast<const FocusEntry&>(eventEntry);
status = connection->inputPublisher.publishFocusEvent(dispatchEntry->seq,
@@ -2830,10 +2998,20 @@
break;
}
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ const auto& captureEntry =
+ static_cast<const PointerCaptureChangedEntry&>(eventEntry);
+ status = connection->inputPublisher
+ .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+ captureEntry.pointerCaptureEnabled);
+ break;
+ }
+
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::SENSOR: {
LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
- EventEntry::typeToString(eventEntry.type));
+ NamedEnum::string(eventEntry.type).c_str());
return;
}
}
@@ -3047,8 +3225,8 @@
void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
const CancelationOptions& options) {
- for (const auto& pair : mConnectionsByFd) {
- synthesizeCancelationEventsForConnectionLocked(pair.second, options);
+ for (const auto& [fd, connection] : mConnectionsByFd) {
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
}
}
@@ -3124,14 +3302,17 @@
static_cast<const MotionEntry&>(*cancelationEventEntry));
break;
}
- case EventEntry::Type::FOCUS: {
- LOG_ALWAYS_FATAL("Canceling focus events is not supported");
+ case EventEntry::Type::FOCUS:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
+ LOG_ALWAYS_FATAL("Canceling %s events is not supported",
+ NamedEnum::string(cancelationEventEntry->type).c_str());
break;
}
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::SENSOR: {
LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
- EventEntry::typeToString(cancelationEventEntry->type));
+ NamedEnum::string(cancelationEventEntry->type).c_str());
break;
}
}
@@ -3185,9 +3366,11 @@
case EventEntry::Type::KEY:
case EventEntry::Type::FOCUS:
case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET: {
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::POINTER_CAPTURE_CHANGED:
+ case EventEntry::Type::SENSOR: {
LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
- EventEntry::typeToString(downEventEntry->type));
+ NamedEnum::string(downEventEntry->type).c_str());
break;
}
}
@@ -3250,7 +3433,9 @@
// The first/last pointer went down/up.
action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
? AMOTION_EVENT_ACTION_DOWN
- : AMOTION_EVENT_ACTION_UP;
+ : (originalMotionEntry.flags & AMOTION_EVENT_FLAG_CANCELED) != 0
+ ? AMOTION_EVENT_ACTION_CANCEL
+ : AMOTION_EVENT_ACTION_UP;
} else {
// A secondary pointer went down/up.
uint32_t splitPointerIndex = 0;
@@ -3520,6 +3705,34 @@
}
}
+void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
+ " sensorType=%s",
+ args->id, args->eventTime, args->deviceId, args->source,
+ NamedEnum::string(args->sensorType).c_str());
+#endif
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ // Just enqueue a new sensor event.
+ std::unique_ptr<SensorEntry> newEntry =
+ std::make_unique<SensorEntry>(args->id, args->eventTime, args->deviceId,
+ args->source, 0 /* policyFlags*/, args->hwTimestamp,
+ args->sensorType, args->accuracy,
+ args->accuracyChanged, args->values);
+
+ needWake = enqueueInboundEventLocked(std::move(newEntry));
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
return mInputFilterEnabled;
}
@@ -3562,7 +3775,17 @@
args->enabled ? "true" : "false");
#endif
- // TODO(prabirmsp): Implement.
+ bool needWake;
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
+ args->enabled);
+ needWake = enqueueInboundEventLocked(std::move(entry));
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
}
InputEventInjectionResult InputDispatcher::injectInputEvent(
@@ -4456,6 +4679,24 @@
return dump;
}
+std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+ std::string dump;
+
+ dump += StringPrintf(INDENT "FocusedWindowRequestedPointerCapture: %s\n",
+ toString(mFocusedWindowRequestedPointerCapture));
+
+ std::string windowName = "None";
+ if (mWindowTokenWithPointerCapture) {
+ const sp<InputWindowHandle> captureWindowHandle =
+ getWindowHandleLocked(mWindowTokenWithPointerCapture);
+ windowName = captureWindowHandle ? captureWindowHandle->getName().c_str()
+ : "token has capture without window";
+ }
+ dump += StringPrintf(INDENT "CurrentWindowWithPointerCapture: %s\n", windowName.c_str());
+
+ return dump;
+}
+
void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
@@ -4479,6 +4720,7 @@
dump += dumpFocusedWindowsLocked();
dump += dumpPendingFocusRequestsLocked();
+ dump += dumpPointerCaptureStateLocked();
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
@@ -4711,7 +4953,7 @@
}
base::Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(
- int32_t displayId, bool isGestureMonitor, const std::string& name) {
+ int32_t displayId, bool isGestureMonitor, const std::string& name, int32_t pid) {
std::shared_ptr<InputChannel> serverChannel;
std::unique_ptr<InputChannel> clientChannel;
status_t result = openInputChannelPair(name, serverChannel, clientChannel);
@@ -4735,7 +4977,7 @@
auto& monitorsByDisplay =
isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay;
- monitorsByDisplay[displayId].emplace_back(serverChannel);
+ monitorsByDisplay[displayId].emplace_back(serverChannel, pid);
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
}
@@ -4860,6 +5102,39 @@
return OK;
}
+void InputDispatcher::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ if (DEBUG_FOCUS) {
+ const sp<InputWindowHandle> windowHandle = getWindowHandleLocked(windowToken);
+ ALOGI("Request to %s Pointer Capture from: %s.", enabled ? "enable" : "disable",
+ windowHandle != nullptr ? windowHandle->getName().c_str()
+ : "token without window");
+ }
+
+ const sp<IBinder> focusedToken =
+ getValueByKey(mFocusedWindowTokenByDisplay, mFocusedDisplayId);
+ if (focusedToken != windowToken) {
+ ALOGW("Ignoring request to %s Pointer Capture: window does not have focus.",
+ enabled ? "enable" : "disable");
+ return;
+ }
+
+ if (enabled == mFocusedWindowRequestedPointerCapture) {
+ ALOGW("Ignoring request to %s Pointer Capture: "
+ "window has %s requested pointer capture.",
+ enabled ? "enable" : "disable", enabled ? "already" : "not");
+ return;
+ }
+
+ mFocusedWindowRequestedPointerCapture = enabled;
+ setPointerCaptureLocked(enabled);
+ } // release lock
+
+ // Wake the thread to process command entries.
+ mLooper->wake();
+}
+
std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
const sp<IBinder>& token) {
for (const auto& it : mGestureMonitorsByDisplay) {
@@ -5384,14 +5659,6 @@
mLock.lock();
}
-KeyEvent InputDispatcher::createKeyEvent(const KeyEntry& entry) {
- KeyEvent event;
- event.initialize(entry.id, entry.deviceId, entry.source, entry.displayId, INVALID_HMAC,
- entry.action, entry.flags, entry.keyCode, entry.scanCode, entry.metaState,
- entry.repeatCount, entry.downTime, entry.eventTime);
- return event;
-}
-
void InputDispatcher::reportDispatchStatistics(std::chrono::nanoseconds eventDuration,
const Connection& connection, bool handled) {
// TODO Write some statistics about how long we spend waiting.
@@ -5578,11 +5845,50 @@
enqueueFocusEventLocked(newFocusedToken, true /*hasFocus*/, reason);
}
+ // If a window has pointer capture, then it must have focus. We need to ensure that this
+ // contract is upheld when pointer capture is being disabled due to a loss of window focus.
+ // If the window loses focus before it loses pointer capture, then the window can be in a state
+ // where it has pointer capture but not focus, violating the contract. Therefore we must
+ // dispatch the pointer capture event before the focus event. Since focus events are added to
+ // the front of the queue (above), we add the pointer capture event to the front of the queue
+ // after the focus events are added. This ensures the pointer capture event ends up at the
+ // front.
+ disablePointerCaptureForcedLocked();
+
if (mFocusedDisplayId == displayId) {
notifyFocusChangedLocked(oldFocusedToken, newFocusedToken);
}
}
+void InputDispatcher::disablePointerCaptureForcedLocked() {
+ if (!mFocusedWindowRequestedPointerCapture && !mWindowTokenWithPointerCapture) {
+ return;
+ }
+
+ ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
+
+ if (mFocusedWindowRequestedPointerCapture) {
+ mFocusedWindowRequestedPointerCapture = false;
+ setPointerCaptureLocked(false);
+ }
+
+ if (!mWindowTokenWithPointerCapture) {
+ // No need to send capture changes because no window has capture.
+ return;
+ }
+
+ if (mPendingEvent != nullptr) {
+ // Move the pending event to the front of the queue. This will give the chance
+ // for the pending event to be dropped if it is a captured event.
+ mInboundQueue.push_front(mPendingEvent);
+ mPendingEvent = nullptr;
+ }
+
+ auto entry = std::make_unique<PointerCaptureChangedEntry>(mIdGenerator.nextId(), now(),
+ false /* hasCapture */);
+ mInboundQueue.push_front(std::move(entry));
+}
+
/**
* Checks if the window token can be focused on a display. The token can be focused if there is
* at least one window handle that is visible with the same token and all window handles with the
@@ -5626,4 +5932,21 @@
return FocusResult::OK;
}
+
+void InputDispatcher::setPointerCaptureLocked(bool enabled) {
+ std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
+ &InputDispatcher::doSetPointerCaptureLockedInterruptible);
+ commandEntry->enabled = enabled;
+ postCommandLocked(std::move(commandEntry));
+}
+
+void InputDispatcher::doSetPointerCaptureLockedInterruptible(
+ android::inputdispatcher::CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->setPointerCapture(commandEntry->enabled);
+
+ mLock.lock();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 8f58785..df0be99 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -94,6 +94,7 @@
virtual void notifyKey(const NotifyKeyArgs* args) override;
virtual void notifyMotion(const NotifyMotionArgs* args) override;
virtual void notifySwitch(const NotifySwitchArgs* args) override;
+ virtual void notifySensor(const NotifySensorArgs* args) override;
virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
@@ -123,10 +124,14 @@
virtual base::Result<std::unique_ptr<InputChannel>> createInputChannel(
const std::string& name) override;
virtual void setFocusedWindow(const FocusRequest&) override;
- virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(
- int32_t displayId, bool isGestureMonitor, const std::string& name) override;
+ virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+ bool isGestureMonitor,
+ const std::string& name,
+ int32_t pid) override;
virtual status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
virtual status_t pilferPointers(const sp<IBinder>& token) override;
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
+ virtual bool flushSensor(int deviceId, InputDeviceSensorType sensorType) override;
std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
@@ -138,6 +143,7 @@
DISABLED,
BLOCKED,
STALE,
+ NO_POINTER_CAPTURE,
};
enum class FocusResult {
@@ -351,6 +357,21 @@
// Top focused display.
int32_t mFocusedDisplayId GUARDED_BY(mLock);
+ // Whether the focused window on the focused display has requested Pointer Capture.
+ // The state of this variable should always be in sync with the state of Pointer Capture in the
+ // policy, which is updated through setPointerCaptureLocked(enabled).
+ bool mFocusedWindowRequestedPointerCapture GUARDED_BY(mLock);
+
+ // The window token that has Pointer Capture.
+ // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
+ sp<IBinder> mWindowTokenWithPointerCapture GUARDED_BY(mLock);
+
+ // Disable Pointer Capture as a result of loss of window focus.
+ void disablePointerCaptureForcedLocked() REQUIRES(mLock);
+
+ // Set the Pointer Capture state in the Policy.
+ void setPointerCaptureLocked(bool enabled) REQUIRES(mLock);
+
// Dispatcher state at time of last ANR.
std::string mLastAnrState GUARDED_BY(mLock);
@@ -370,9 +391,13 @@
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry)
REQUIRES(mLock);
+ void dispatchPointerCaptureChangedLocked(
+ nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ DropReason& dropReason) REQUIRES(mLock);
void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-
+ void dispatchSensorLocked(nsecs_t currentTime, std::shared_ptr<SensorEntry> entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
void logOutboundMotionDetails(const char* prefix, const MotionEntry& entry);
@@ -533,6 +558,7 @@
void logDispatchStateLocked() REQUIRES(mLock);
std::string dumpFocusedWindowsLocked() REQUIRES(mLock);
std::string dumpPendingFocusRequestsLocked() REQUIRES(mLock);
+ std::string dumpPointerCaptureStateLocked() REQUIRES(mLock);
// Registration.
void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
@@ -571,10 +597,12 @@
REQUIRES(mLock);
void doNotifyConnectionResponsiveLockedInterruptible(CommandEntry* commandEntry)
REQUIRES(mLock);
+ void doNotifySensorLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
void doNotifyUntrustedTouchLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry)
REQUIRES(mLock);
void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doSetPointerCaptureLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
bool handled) REQUIRES(mLock);
@@ -582,7 +610,6 @@
DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
bool handled) REQUIRES(mLock);
void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
- KeyEvent createKeyEvent(const KeyEntry& entry);
void doOnPointerDownOutsideFocusLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
// Statistics gathering.
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 1656a21..3bb0bc9 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "input/InputDevice.h"
+
#include "InputState.h"
#include "InputDispatcher.h"
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index b347674..bbce759 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -19,7 +19,8 @@
namespace android::inputdispatcher {
// --- Monitor ---
-Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel) : inputChannel(inputChannel) {}
+Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid)
+ : inputChannel(inputChannel), pid(pid) {}
// --- TouchedMonitor ---
TouchedMonitor::TouchedMonitor(const Monitor& monitor, float xOffset, float yOffset)
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index fc0b020..7be0760 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -24,7 +24,9 @@
struct Monitor {
std::shared_ptr<InputChannel> inputChannel; // never null
- explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel);
+ int32_t pid;
+
+ explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid);
};
// For tracking the offsets we need to apply when adding gesture monitor targets.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 9154d48..3491893 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -25,6 +25,7 @@
#include <android/os/InputEventInjectionResult.h>
#include <android/os/InputEventInjectionSync.h>
#include <input/InputApplication.h>
+#include <input/InputDevice.h>
#include <input/InputTransport.h>
#include <input/InputWindow.h>
#include <unordered_map>
@@ -172,8 +173,10 @@
*
* This method may be called on any thread (usually by the input manager).
*/
- virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(
- int32_t displayId, bool gestureMonitor, const std::string& name) = 0;
+ virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+ bool gestureMonitor,
+ const std::string& name,
+ int32_t pid) = 0;
/* Removes input channels that will no longer receive input events.
*
@@ -186,6 +189,18 @@
* This method may be called on any thread (usually by the input manager).
*/
virtual status_t pilferPointers(const sp<IBinder>& token) = 0;
+
+ /**
+ * Enables Pointer Capture on the specified window if the window has focus.
+ *
+ * InputDispatcher is the source of truth of Pointer Capture.
+ */
+ virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) = 0;
+ /* Flush input device motion sensor.
+ *
+ * Returns true on success.
+ */
+ virtual bool flushSensor(int deviceId, InputDeviceSensorType sensorType) = 0;
};
} // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 1125257..d9ec020 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -68,6 +68,11 @@
/* Notifies the system that an input channel is unrecoverably broken. */
virtual void notifyInputChannelBroken(const sp<IBinder>& token) = 0;
virtual void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) = 0;
+ virtual void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+ const std::vector<float>& values) = 0;
+ virtual void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy) = 0;
/* Notifies the system that an untrusted touch occurred. */
virtual void notifyUntrustedTouch(const std::string& obscuringPackage) = 0;
@@ -134,6 +139,12 @@
* The touchedToken passed as an argument is the window that received the input event.
*/
virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) = 0;
+
+ /* Change the Pointer Capture state in InputReader.
+ *
+ * InputDispatcher is solely responsible for updating the Pointer Capture state.
+ */
+ virtual void setPointerCapture(bool enabled) = 0;
};
} // namespace android
diff --git a/services/inputflinger/docs/pointer_capture.md b/services/inputflinger/docs/pointer_capture.md
new file mode 100644
index 0000000..8da699d
--- /dev/null
+++ b/services/inputflinger/docs/pointer_capture.md
@@ -0,0 +1,44 @@
+# Pointer Capture in InputFlinger
+
+## Introduction
+
+[Pointer Capture](https://developer.android.com/training/gestures/movement#pointer-capture) is a feature that was introduced to the Android input pipeline in Android 8.0 (Oreo). Pointer Capture can be enabled or disabled for an `InputWindow` through requests to `InputManagerService`. Enabling Pointer Capture performs the following changes related to the mouse cursor and the devices that control it:
+
+- The position of the mouse cursor is fixed to its location before Pointer Capture was enabled.
+- The mouse cursor is hidden.
+- Events from a mouse will be delivered with the source `SOURCE_MOUSE_RELATIVE`, and their `AXIS_X` and `AXIS_Y` will report relative position changes.
+- Events from a touchpad will be delivered with the source `SOURCE_TOUCHPAD`, and their `AXIS_X` and `AXIS_Y` will report the absolute position of each of the pointers on the touchpad.
+- Events from mouse and touchpad devices are dispatched to the focused `InputWindow`.
+- Events from devices that do not normally control the mouse cursor are not affected.
+
+`InputWindow`s can only gain Pointer Capture if they have window focus. If a window with Pointer Capture loses focus, Pointer Capture is disabled.
+
+## Pointer Capture pipeline in InputFlinger
+
+`InputDispatcher` is responsible for controlling the state of Pointer Capture. Since the feature requires changes to how events are generated, Pointer Capture is configured in `InputReader`.
+
+### Enabling Pointer Capture
+
+There are four key steps that take place when Pointer Capture is enabled:
+
+1. Requests to enable Pointer Capture are forwarded from `InputManagerService` to `InputDispatcher`.
+2. If the window that makes the request has focus, `InputDispatcher` enables the Pointer Capture state in `InputReader` through the `InputDispatcherPolicy`.
+3. When `InputReader` is successfully configured, it notifies `InputDispatcher` through the `InputListener` interface.
+4. `InputDispatcher` then notifies the `InputWindow` that Pointer Capture has been enabled by sending a special `CAPTURE` event through the `InputChannel`.
+
+### Disabling Pointer Capture
+
+Pointer Capture can be disabled in two ways: by a request through `InputManagerService`, and as a result of the `InputWindow` losing focus.
+
+When Pointer Capture is disabled by a request from the application, it follows the same pipeline as when Pointer Capture is enabled.
+
+#### Window loses Pointer Capture when it loses focus
+
+When an `InputWindow` with Pointer Capture loses focus, Pointer Capture is disabled immediately. The `InputWindow` receives a `CAPTURE` event through the `InputChannel`, followed by a `FOCUS` event to notify loss of focus.
+
+## Pointer Capture in `InputDispatcher`
+
+`InputDispatcher` tracks two pieces of state information regarding Pointer Capture:
+
+- `mFocusedWindowRequestedPointerCapture`: Whether or not the focused window has requested Pointer Capture. This is updated whenever the Dispatcher receives requests from `InputManagerService`.
+- `mWindowTokenWithPointerCapture`: The Binder token of the `InputWindow` that currently has Pointer Capture. This is only updated during the dispatch cycle. If it is not `nullptr`, it signifies that the window was notified that it has Pointer Capture.
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index 58eb915..11f726d 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -20,6 +20,7 @@
#include <vector>
#include <input/Input.h>
+#include <input/InputDevice.h>
#include <input/TouchVideoFrame.h>
#include <utils/RefBase.h>
@@ -141,6 +142,30 @@
virtual void notify(const sp<InputListenerInterface>& listener) const;
};
+/* Describes a sensor event. */
+struct NotifySensorArgs : public NotifyArgs {
+ int32_t deviceId;
+ uint32_t source;
+ InputDeviceSensorType sensorType;
+ InputDeviceSensorAccuracy accuracy;
+ bool accuracyChanged;
+ nsecs_t hwTimestamp;
+ std::vector<float> values;
+
+ inline NotifySensorArgs() {}
+
+ NotifySensorArgs(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy,
+ bool accuracyChanged, nsecs_t hwTimestamp, std::vector<float> values);
+
+ NotifySensorArgs(const NotifySensorArgs& other);
+
+ bool operator==(const NotifySensorArgs rhs) const;
+
+ ~NotifySensorArgs() override {}
+
+ void notify(const sp<InputListenerInterface>& listener) const override;
+};
/* Describes a switch event. */
struct NotifySwitchArgs : public NotifyArgs {
@@ -211,6 +236,7 @@
virtual void notifyKey(const NotifyKeyArgs* args) = 0;
virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
+ virtual void notifySensor(const NotifySensorArgs* args) = 0;
virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0;
};
@@ -231,6 +257,7 @@
virtual void notifyKey(const NotifyKeyArgs* args) override;
virtual void notifyMotion(const NotifyMotionArgs* args) override;
virtual void notifySwitch(const NotifySwitchArgs* args) override;
+ virtual void notifySensor(const NotifySensorArgs* args) override;
virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 6cce8ec..ea9b483 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -111,6 +111,17 @@
/* Return true if the device can send input events to the specified display. */
virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
+
+ /* Enable sensor in input reader mapper. */
+ virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) = 0;
+
+ /* Disable sensor in input reader mapper. */
+ virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+ /* Flush sensor data in input reader mapper. */
+ virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
};
// --- InputReaderConfiguration ---
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 0ccada9..abda4ef 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -37,6 +37,7 @@
"mapper/KeyboardInputMapper.cpp",
"mapper/MultiTouchInputMapper.cpp",
"mapper/RotaryEncoderInputMapper.cpp",
+ "mapper/SensorInputMapper.cpp",
"mapper/SingleTouchInputMapper.cpp",
"mapper/SwitchInputMapper.cpp",
"mapper/TouchInputMapper.cpp",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f864c0e..b97ff90 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -157,6 +157,18 @@
}
}
+ if (deviceClasses.test(InputDeviceClass::SENSOR)) {
+ switch (axis) {
+ case ABS_X:
+ case ABS_Y:
+ case ABS_Z:
+ case ABS_RX:
+ case ABS_RY:
+ case ABS_RZ:
+ return InputDeviceClass::SENSOR;
+ }
+ }
+
// External stylus gets the pressure axis
if (deviceClasses.test(InputDeviceClass::EXTERNAL_STYLUS)) {
if (axis == ABS_PRESSURE) {
@@ -250,6 +262,11 @@
// uses the timestamps extensively and assumes they were recorded using the monotonic
// clock.
int clockId = CLOCK_MONOTONIC;
+ if (classes.test(InputDeviceClass::SENSOR)) {
+ // Each new sensor event should use the same time base as
+ // SystemClock.elapsedRealtimeNanos().
+ clockId = CLOCK_BOOTTIME;
+ }
bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
ALOGI("usingClockIoctl=%s", toString(usingClockIoctl));
}
@@ -550,6 +567,15 @@
: false;
}
+bool EventHub::hasMscEvent(int32_t deviceId, int mscEvent) const {
+ std::scoped_lock _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ return mscEvent >= 0 && mscEvent <= MSC_MAX && device != nullptr
+ ? device->mscBitmask.test(mscEvent)
+ : false;
+}
+
int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
if (scanCode >= 0 && scanCode <= KEY_MAX) {
std::scoped_lock _l(mLock);
@@ -705,6 +731,17 @@
return NAME_NOT_FOUND;
}
+base::Result<std::pair<InputDeviceSensorType, int32_t>> EventHub::mapSensor(int32_t deviceId,
+ int32_t absCode) {
+ std::scoped_lock _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device != nullptr && device->keyMap.haveKeyLayout()) {
+ return device->keyMap.keyLayoutMap->mapSensor(absCode);
+ }
+ return Errorf("Device not found or device has no key layout.");
+}
+
void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
std::scoped_lock _l(mLock);
@@ -1405,6 +1442,7 @@
device->readDeviceBitMask(EVIOCGBIT(EV_SW, 0), device->swBitmask);
device->readDeviceBitMask(EVIOCGBIT(EV_LED, 0), device->ledBitmask);
device->readDeviceBitMask(EVIOCGBIT(EV_FF, 0), device->ffBitmask);
+ device->readDeviceBitMask(EVIOCGBIT(EV_MSC, 0), device->mscBitmask);
device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask);
// See if this is a keyboard. Ignore everything in the button range except for
@@ -1469,6 +1507,11 @@
}
}
+ // Check whether this device is an accelerometer.
+ if (device->propBitmask.test(INPUT_PROP_ACCELEROMETER)) {
+ device->classes |= InputDeviceClass::SENSOR;
+ }
+
// Check whether this device has switches.
for (int i = 0; i <= SW_MAX; i++) {
if (device->swBitmask.test(i)) {
@@ -1493,9 +1536,11 @@
}
// Load the key map.
- // We need to do this for joysticks too because the key layout may specify axes.
+ // We need to do this for joysticks too because the key layout may specify axes, and for
+ // sensor as well because the key layout may specify the axes to sensor data mapping.
status_t keyMapStatus = NAME_NOT_FOUND;
- if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK)) {
+ if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK |
+ InputDeviceClass::SENSOR)) {
// Load the keymap for the device.
keyMapStatus = device->loadKeyMapLocked();
}
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index d25d64a..c7c8e7c 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -28,6 +28,7 @@
#include "KeyboardInputMapper.h"
#include "MultiTouchInputMapper.h"
#include "RotaryEncoderInputMapper.h"
+#include "SensorInputMapper.h"
#include "SingleTouchInputMapper.h"
#include "SwitchInputMapper.h"
#include "VibratorInputMapper.h"
@@ -53,10 +54,11 @@
if (!hasEventHubDevices()) {
return false;
}
- // devices are either all enabled or all disabled, so we only need to check the first
- auto& devicePair = mDevices.begin()->second;
- auto& contextPtr = devicePair.first;
- return contextPtr->isDeviceEnabled();
+ // An input device composed of sub devices can be individually enabled or disabled.
+ // If any of the sub device is enabled then the input device is considered as enabled.
+ bool enabled = false;
+ for_each_subdevice([&enabled](auto& context) { enabled |= context.isDeviceEnabled(); });
+ return enabled;
}
void InputDevice::setEnabled(bool enabled, nsecs_t when) {
@@ -196,6 +198,11 @@
mappers.push_back(std::make_unique<JoystickInputMapper>(*contextPtr));
}
+ // Motion sensor enabled devices.
+ if (classes.test(InputDeviceClass::SENSOR)) {
+ mappers.push_back(std::make_unique<SensorInputMapper>(*contextPtr));
+ }
+
// External stylus-like devices.
if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
mappers.push_back(std::make_unique<ExternalStylusInputMapper>(*contextPtr));
@@ -460,6 +467,25 @@
return vibrators;
}
+bool InputDevice::enableSensor(InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) {
+ bool success = true;
+ for_each_mapper(
+ [&success, sensorType, samplingPeriod, maxBatchReportLatency](InputMapper& mapper) {
+ success &= mapper.enableSensor(sensorType, samplingPeriod, maxBatchReportLatency);
+ });
+ return success;
+}
+
+void InputDevice::disableSensor(InputDeviceSensorType sensorType) {
+ for_each_mapper([sensorType](InputMapper& mapper) { mapper.disableSensor(sensorType); });
+}
+
+void InputDevice::flushSensor(InputDeviceSensorType sensorType) {
+ for_each_mapper([sensorType](InputMapper& mapper) { mapper.flushSensor(sensorType); });
+}
+
void InputDevice::cancelTouch(nsecs_t when) {
for_each_mapper([when](InputMapper& mapper) { mapper.cancelTouch(when); });
}
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index a6b5e2d..6c493ff 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -220,6 +220,13 @@
if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
notifyExternalStylusPresenceChangedLocked();
}
+
+ // Sensor input device is noisy, to save power disable it by default.
+ // Input device is classified as SENSOR when any sub device is a SENSOR device, check Eventhub
+ // device class to disable SENSOR sub device only.
+ if (mEventHub->getDeviceClasses(eventHubId).test(InputDeviceClass::SENSOR)) {
+ mEventHub->disableDevice(eventHubId);
+ }
}
void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) {
@@ -639,6 +646,36 @@
return {};
}
+void InputReader::disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ device->disableSensor(sensorType);
+ }
+}
+
+bool InputReader::enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->enableSensor(sensorType, samplingPeriod, maxBatchReportLatency);
+ }
+ return false;
+}
+
+void InputReader::flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ device->flushSensor(sensorType);
+ }
+}
+
bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 9e38d0a..2cea017 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -118,6 +118,9 @@
/* The input device has a rotary encoder */
ROTARY_ENCODER = 0x00001000,
+ /* The input device has a sensor like accelerometer, gyro, etc */
+ SENSOR = 0x00002000,
+
/* The input device is virtual (not a real device, not part of UI configuration). */
VIRTUAL = 0x40000000,
@@ -177,6 +180,8 @@
virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+ virtual bool hasMscEvent(int32_t deviceId, int mscEvent) const = 0;
+
virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
uint32_t* outFlags) const = 0;
@@ -201,6 +206,8 @@
*/
virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;
virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) = 0;
+ virtual base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
+ int32_t absCode) = 0;
/*
* Query current input state.
@@ -346,6 +353,8 @@
bool hasInputProperty(int32_t deviceId, int property) const override final;
+ bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
+
status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState,
uint32_t* outFlags) const override final;
@@ -353,6 +362,9 @@
status_t mapAxis(int32_t deviceId, int32_t scanCode,
AxisInfo* outAxisInfo) const override final;
+ base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
+ int32_t deviceId, int32_t absCode) override final;
+
void setExcludedDevices(const std::vector<std::string>& devices) override final;
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
@@ -420,6 +432,7 @@
BitArray<LED_MAX> ledBitmask;
BitArray<FF_MAX> ffBitmask;
BitArray<INPUT_PROP_MAX> propBitmask;
+ BitArray<MSC_MAX> mscBitmask;
std::string configurationFile;
std::unique_ptr<PropertyMap> configuration;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 8b14b06..5af76b7 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -87,6 +87,10 @@
bool isVibrating();
std::vector<int32_t> getVibratorIds();
void cancelTouch(nsecs_t when);
+ bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency);
+ void disableSensor(InputDeviceSensorType sensorType);
+ void flushSensor(InputDeviceSensorType sensorType);
int32_t getMetaState();
void updateMetaState(int32_t keyCode);
@@ -229,9 +233,12 @@
inline bool hasRelativeAxis(int32_t code) const {
return mEventHub->hasRelativeAxis(mId, code);
}
- inline bool hasInputProperty(int property) const {
+ inline bool hasInputProperty(int32_t property) const {
return mEventHub->hasInputProperty(mId, property);
}
+
+ inline bool hasMscEvent(int mscEvent) const { return mEventHub->hasMscEvent(mId, mscEvent); }
+
inline status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
return mEventHub->mapKey(mId, scanCode, usageCode, metaState, outKeycode, outMetaState,
@@ -240,6 +247,10 @@
inline status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
return mEventHub->mapAxis(mId, scanCode, outAxisInfo);
}
+ inline base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t absCode) {
+ return mEventHub->mapSensor(mId, absCode);
+ }
+
inline std::vector<TouchVideoFrame> getVideoFrames() { return mEventHub->getVideoFrames(mId); }
inline int32_t getScanCodeState(int32_t scanCode) const {
return mEventHub->getScanCodeState(mId, scanCode);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index b16b86c..48d4596 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -87,6 +87,14 @@
bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) override;
+ bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) override;
+
+ void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) override;
+
+ void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) override;
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 913cef7..1ce54ae 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -70,6 +70,16 @@
void InputMapper::cancelTouch(nsecs_t when) {}
+bool InputMapper::enableSensor(InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) {
+ return true;
+}
+
+void InputMapper::disableSensor(InputDeviceSensorType sensorType) {}
+
+void InputMapper::flushSensor(InputDeviceSensorType sensorType) {}
+
int32_t InputMapper::getMetaState() {
return 0;
}
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 088dbd8..bd64d8d 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -68,6 +68,11 @@
virtual bool isVibrating();
virtual std::vector<int32_t> getVibratorIds();
virtual void cancelTouch(nsecs_t when);
+ virtual bool enableSensor(InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency);
+ virtual void disableSensor(InputDeviceSensorType sensorType);
+ virtual void flushSensor(InputDeviceSensorType sensorType);
virtual int32_t getMetaState();
virtual void updateMetaState(int32_t keyCode);
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
new file mode 100644
index 0000000..7ac2dec
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <locale>
+
+#include "../Macros.h"
+
+#include "SensorInputMapper.h"
+
+// Log detailed debug messages about each sensor event notification to the dispatcher.
+constexpr bool DEBUG_SENSOR_EVENT_DETAILS = false;
+
+namespace android {
+
+// Mask for the LSB 2nd, 3rd and fourth bits.
+constexpr int REPORTING_MODE_MASK = 0xE;
+constexpr int REPORTING_MODE_SHIFT = 1;
+constexpr float GRAVITY_MS2_UNIT = 9.80665f;
+constexpr float DEGREE_RADIAN_UNIT = 0.0174533f;
+
+/* Convert the sensor data from Linux to Android
+ * Linux accelerometer unit is per g, Android unit is m/s^2
+ * Linux gyroscope unit is degree/second, Android unit is radians/second
+ */
+static void convertFromLinuxToAndroid(std::vector<float>& values,
+ InputDeviceSensorType sensorType) {
+ for (size_t i = 0; i < values.size(); i++) {
+ switch (sensorType) {
+ case InputDeviceSensorType::ACCELEROMETER:
+ values[i] *= GRAVITY_MS2_UNIT;
+ break;
+ case InputDeviceSensorType::GYROSCOPE:
+ values[i] *= DEGREE_RADIAN_UNIT;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+SensorInputMapper::SensorInputMapper(InputDeviceContext& deviceContext)
+ : InputMapper(deviceContext) {}
+
+SensorInputMapper::~SensorInputMapper() {}
+
+uint32_t SensorInputMapper::getSources() {
+ return AINPUT_SOURCE_SENSOR;
+}
+
+template <typename T>
+bool SensorInputMapper::tryGetProperty(std::string keyName, T& outValue) {
+ const auto& config = getDeviceContext().getConfiguration();
+ return config.tryGetProperty(String8(keyName.c_str()), outValue);
+}
+
+void SensorInputMapper::parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode,
+ int32_t sensorDataIndex, const Axis& axis) {
+ auto it = mSensors.find(sensorType);
+ if (it == mSensors.end()) {
+ Sensor sensor = createSensor(sensorType, axis);
+ sensor.dataVec[sensorDataIndex] = absCode;
+ mSensors.emplace(sensorType, sensor);
+ } else {
+ it->second.dataVec[sensorDataIndex] = absCode;
+ }
+}
+
+void SensorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ for (const auto& [sensorType, sensor] : mSensors) {
+ info->addSensorInfo(sensor.sensorInfo);
+ info->setHasSensor(true);
+ }
+}
+
+void SensorInputMapper::dump(std::string& dump) {
+ dump += INDENT2 "Sensor Input Mapper:\n";
+ dump += StringPrintf(INDENT3 " isDeviceEnabled %d\n", getDeviceContext().isDeviceEnabled());
+ dump += StringPrintf(INDENT3 " mHasHardwareTimestamp %d\n", mHasHardwareTimestamp);
+ dump += INDENT3 "Sensors:\n";
+ for (const auto& [sensorType, sensor] : mSensors) {
+ dump += StringPrintf(INDENT4 "%s\n", NamedEnum::string(sensorType).c_str());
+ dump += StringPrintf(INDENT5 "enabled: %d\n", sensor.enabled);
+ dump += StringPrintf(INDENT5 "samplingPeriod: %lld\n", sensor.samplingPeriod.count());
+ dump += StringPrintf(INDENT5 "maxBatchReportLatency: %lld\n",
+ sensor.maxBatchReportLatency.count());
+ dump += StringPrintf(INDENT5 "maxRange: %f\n", sensor.sensorInfo.maxRange);
+ dump += StringPrintf(INDENT5 "power: %f\n", sensor.sensorInfo.power);
+ for (ssize_t i = 0; i < SENSOR_VEC_LEN; i++) {
+ int32_t rawAxis = sensor.dataVec[i];
+ dump += StringPrintf(INDENT5 "[%zd]: rawAxis: %d \n", i, rawAxis);
+ const auto it = mAxes.find(rawAxis);
+ if (it != mAxes.end()) {
+ const Axis& axis = it->second;
+ dump += StringPrintf(INDENT5 " min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f,"
+ "resolution=%0.5f\n",
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ dump += StringPrintf(INDENT5 " scale=%0.5f, offset=%0.5f\n", axis.scale,
+ axis.offset);
+ dump += StringPrintf(INDENT5 " rawMin=%d, rawMax=%d, "
+ "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n",
+ axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
+ axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz,
+ axis.rawAxisInfo.resolution);
+ }
+ }
+ }
+}
+
+void SensorInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+ uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ mDeviceEnabled = true;
+ // Check if device has MSC_TIMESTAMP event.
+ mHasHardwareTimestamp = getDeviceContext().hasMscEvent(MSC_TIMESTAMP);
+ // Collect all axes.
+ for (int32_t abs = ABS_X; abs <= ABS_MAX; abs++) {
+ // axis must be claimed by sensor class device
+ if (!(getAbsAxisUsage(abs, getDeviceContext().getDeviceClasses())
+ .test(InputDeviceClass::SENSOR))) {
+ continue;
+ }
+ RawAbsoluteAxisInfo rawAxisInfo;
+ getAbsoluteAxisInfo(abs, &rawAxisInfo);
+ if (rawAxisInfo.valid) {
+ AxisInfo axisInfo;
+ // Axis doesn't need to be mapped, as sensor mapper doesn't generate any motion
+ // input events
+ axisInfo.mode = AxisInfo::MODE_NORMAL;
+ axisInfo.axis = -1;
+ // Check key layout map for sensor data mapping to axes
+ auto ret = getDeviceContext().mapSensor(abs);
+ if (ret.ok()) {
+ InputDeviceSensorType sensorType = (*ret).first;
+ int32_t sensorDataIndex = (*ret).second;
+ const Axis& axis = createAxis(axisInfo, rawAxisInfo);
+ parseSensorConfiguration(sensorType, abs, sensorDataIndex, axis);
+
+ mAxes.insert({abs, axis});
+ }
+ }
+ }
+ }
+}
+
+SensorInputMapper::Axis SensorInputMapper::createAxis(const AxisInfo& axisInfo,
+ const RawAbsoluteAxisInfo& rawAxisInfo) {
+ // Apply flat override.
+ int32_t rawFlat = axisInfo.flatOverride < 0 ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+ float scale = std::numeric_limits<float>::signaling_NaN();
+ float offset = 0;
+
+ // resolution is 1 of sensor's unit. For accelerometer, it is G, for gyroscope,
+ // it is degree/s.
+ scale = 1.0f / rawAxisInfo.resolution;
+ offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
+
+ const float max = rawAxisInfo.maxValue / rawAxisInfo.resolution;
+ const float min = rawAxisInfo.minValue / rawAxisInfo.resolution;
+ const float flat = rawFlat * scale;
+ const float fuzz = rawAxisInfo.fuzz * scale;
+ const float resolution = rawAxisInfo.resolution;
+
+ // To eliminate noise while the Sensor is at rest, filter out small variations
+ // in axis values up front.
+ const float filter = fuzz ? fuzz : flat * 0.25f;
+ return Axis(rawAxisInfo, axisInfo, scale, offset, min, max, flat, fuzz, resolution, filter);
+}
+
+void SensorInputMapper::reset(nsecs_t when) {
+ // Recenter all axes.
+ for (std::pair<const int32_t, Axis>& pair : mAxes) {
+ Axis& axis = pair.second;
+ axis.resetValue();
+ }
+ mHardwareTimestamp = 0;
+ mPrevMscTime = 0;
+ InputMapper::reset(when);
+}
+
+SensorInputMapper::Sensor SensorInputMapper::createSensor(InputDeviceSensorType sensorType,
+ const Axis& axis) {
+ InputDeviceIdentifier identifier = getDeviceContext().getDeviceIdentifier();
+ // Sensor Id will be assigned to device Id to distinguish same sensor from multiple input
+ // devices, in such a way that the sensor Id will be same as input device Id.
+ // The sensorType is to distinguish different sensors within one device.
+ // One input device can only have 1 sensor for each sensor Type.
+ InputDeviceSensorInfo sensorInfo(identifier.name, std::to_string(identifier.vendor),
+ identifier.version, sensorType,
+ InputDeviceSensorAccuracy::ACCURACY_HIGH,
+ axis.max /* maxRange */, axis.scale /* resolution */,
+ 0.0f /* power */, 0 /* minDelay */,
+ 0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */,
+ NamedEnum::string(sensorType), 0 /* maxDelay */, 0 /* flags */,
+ getDeviceId());
+
+ std::string prefix = "sensor." + NamedEnum::string(sensorType);
+ transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower);
+
+ int32_t reportingMode = 0;
+ if (!tryGetProperty(prefix + ".reportingMode", reportingMode)) {
+ sensorInfo.flags |= (reportingMode & REPORTING_MODE_MASK) << REPORTING_MODE_SHIFT;
+ }
+
+ tryGetProperty(prefix + ".maxDelay", sensorInfo.maxDelay);
+
+ tryGetProperty(prefix + ".minDelay", sensorInfo.minDelay);
+
+ tryGetProperty(prefix + ".power", sensorInfo.power);
+
+ tryGetProperty(prefix + ".fifoReservedEventCount", sensorInfo.fifoReservedEventCount);
+
+ tryGetProperty(prefix + ".fifoMaxEventCount", sensorInfo.fifoMaxEventCount);
+
+ return Sensor(sensorInfo);
+}
+
+void SensorInputMapper::processHardWareTimestamp(nsecs_t evTime, int32_t mscTime) {
+ // Since MSC_TIMESTAMP initial state is different from the system time, we
+ // calculate the difference between two MSC_TIMESTAMP events, and use that
+ // to calculate the system time that should be tagged on the event.
+ // if the first time MSC_TIMESTAMP, store it
+ // else calculate difference between previous and current MSC_TIMESTAMP
+ if (mPrevMscTime == 0) {
+ mHardwareTimestamp = evTime;
+ if (DEBUG_SENSOR_EVENT_DETAILS) {
+ ALOGD("Initialize hardware timestamp = %" PRId64, mHardwareTimestamp);
+ }
+ } else {
+ // Calculate the difference between current msc_timestamp and
+ // previous msc_timestamp, including when msc_timestamp wraps around.
+ uint32_t timeDiff = (mPrevMscTime > static_cast<uint32_t>(mscTime))
+ ? (UINT32_MAX - mPrevMscTime + static_cast<uint32_t>(mscTime + 1))
+ : (static_cast<uint32_t>(mscTime) - mPrevMscTime);
+
+ mHardwareTimestamp += timeDiff * 1000LL;
+ }
+ mPrevMscTime = static_cast<uint32_t>(mscTime);
+}
+
+void SensorInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_ABS: {
+ auto it = mAxes.find(rawEvent->code);
+ if (it != mAxes.end()) {
+ Axis& axis = it->second;
+ axis.newValue = rawEvent->value * axis.scale + axis.offset;
+ }
+ break;
+ }
+
+ case EV_SYN:
+ switch (rawEvent->code) {
+ case SYN_REPORT:
+ for (std::pair<const int32_t, Axis>& pair : mAxes) {
+ Axis& axis = pair.second;
+ axis.currentValue = axis.newValue;
+ }
+ sync(rawEvent->when, false /*force*/);
+ break;
+ }
+ break;
+
+ case EV_MSC:
+ switch (rawEvent->code) {
+ case MSC_TIMESTAMP:
+ // hardware timestamp is nano seconds
+ processHardWareTimestamp(rawEvent->when, rawEvent->value);
+ break;
+ }
+ }
+}
+
+bool SensorInputMapper::setSensorEnabled(InputDeviceSensorType sensorType, bool enabled) {
+ auto it = mSensors.find(sensorType);
+ if (it == mSensors.end()) {
+ return false;
+ }
+
+ it->second.enabled = enabled;
+ if (!enabled) {
+ it->second.resetValue();
+ }
+
+ /* Currently we can't enable/disable sensors individually. Enabling any sensor will enable
+ * the device
+ */
+ mDeviceEnabled = false;
+ for (const auto& [sensorType, sensor] : mSensors) {
+ // If any sensor is on we will turn on the device.
+ if (sensor.enabled) {
+ mDeviceEnabled = true;
+ break;
+ }
+ }
+ return true;
+}
+
+void SensorInputMapper::flushSensor(InputDeviceSensorType sensorType) {
+ auto it = mSensors.find(sensorType);
+ if (it == mSensors.end()) {
+ return;
+ }
+ auto& sensor = it->second;
+ sensor.lastSampleTimeNs = 0;
+ for (size_t i = 0; i < SENSOR_VEC_LEN; i++) {
+ int32_t abs = sensor.dataVec[i];
+ auto itAxis = mAxes.find(abs);
+ if (itAxis != mAxes.end()) {
+ Axis& axis = itAxis->second;
+ axis.resetValue();
+ }
+ }
+}
+
+bool SensorInputMapper::enableSensor(InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) {
+ if (DEBUG_SENSOR_EVENT_DETAILS) {
+ ALOGD("Enable Sensor %s samplingPeriod %lld maxBatchReportLatency %lld",
+ NamedEnum::string(sensorType).c_str(), samplingPeriod.count(),
+ maxBatchReportLatency.count());
+ }
+
+ if (!setSensorEnabled(sensorType, true /* enabled */)) {
+ return false;
+ }
+
+ // Enable device
+ if (mDeviceEnabled) {
+ getDeviceContext().enableDevice();
+ }
+
+ // We know the sensor exists now, update the sampling period and batch report latency.
+ auto it = mSensors.find(sensorType);
+ it->second.samplingPeriod =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(samplingPeriod);
+ it->second.maxBatchReportLatency =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(maxBatchReportLatency);
+ return true;
+}
+
+void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) {
+ if (DEBUG_SENSOR_EVENT_DETAILS) {
+ ALOGD("Disable Sensor %s", NamedEnum::string(sensorType).c_str());
+ }
+
+ if (!setSensorEnabled(sensorType, false /* enabled */)) {
+ return;
+ }
+
+ // Disable device
+ if (!mDeviceEnabled) {
+ mHardwareTimestamp = 0;
+ mPrevMscTime = 0;
+ getDeviceContext().disableDevice();
+ }
+}
+
+void SensorInputMapper::sync(nsecs_t when, bool force) {
+ for (auto& [sensorType, sensor] : mSensors) {
+ // Skip if sensor not enabled
+ if (!sensor.enabled) {
+ continue;
+ }
+ std::vector<float> values;
+ for (ssize_t i = 0; i < SENSOR_VEC_LEN; i++) {
+ int32_t abs = sensor.dataVec[i];
+ auto it = mAxes.find(abs);
+ if (it != mAxes.end()) {
+ const Axis& axis = it->second;
+ values.push_back(axis.currentValue);
+ }
+ }
+
+ nsecs_t timestamp = mHasHardwareTimestamp ? mHardwareTimestamp : when;
+ if (DEBUG_SENSOR_EVENT_DETAILS) {
+ ALOGD("Sensor %s timestamp %" PRIu64 " values [%f %f %f]",
+ NamedEnum::string(sensorType).c_str(), timestamp, values[0], values[1],
+ values[2]);
+ }
+ if (sensor.lastSampleTimeNs.has_value() &&
+ timestamp - sensor.lastSampleTimeNs.value() < sensor.samplingPeriod.count()) {
+ if (DEBUG_SENSOR_EVENT_DETAILS) {
+ ALOGD("Sensor %s Skip a sample.", NamedEnum::string(sensorType).c_str());
+ }
+ } else {
+ // Convert to Android unit
+ convertFromLinuxToAndroid(values, sensorType);
+ // Notify dispatcher for sensor event
+ NotifySensorArgs args(getContext()->getNextId(), when, getDeviceId(),
+ AINPUT_SOURCE_SENSOR, sensorType, sensor.sensorInfo.accuracy,
+ sensor.accuracy !=
+ sensor.sensorInfo.accuracy /* accuracyChanged */,
+ timestamp /* hwTimestamp */, values);
+
+ getListener()->notifySensor(&args);
+ sensor.lastSampleTimeNs = timestamp;
+ sensor.accuracy = sensor.sensorInfo.accuracy;
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
new file mode 100644
index 0000000..1797fe3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H
+#define _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H
+
+#include "InputMapper.h"
+
+namespace android {
+// sensor data vector length
+static constexpr ssize_t SENSOR_VEC_LEN = 3;
+
+class SensorInputMapper : public InputMapper {
+public:
+ explicit SensorInputMapper(InputDeviceContext& deviceContext);
+ ~SensorInputMapper() override;
+
+ uint32_t getSources() override;
+ void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+ void dump(std::string& dump) override;
+ void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
+ void reset(nsecs_t when) override;
+ void process(const RawEvent* rawEvent) override;
+ bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) override;
+ void disableSensor(InputDeviceSensorType sensorType) override;
+ void flushSensor(InputDeviceSensorType sensorType) override;
+
+private:
+ struct Axis {
+ explicit Axis(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo, float scale,
+ float offset, float min, float max, float flat, float fuzz, float resolution,
+ float filter)
+ : rawAxisInfo(rawAxisInfo),
+ axisInfo(axisInfo),
+ scale(scale),
+ offset(offset),
+ min(min),
+ max(max),
+ flat(flat),
+ fuzz(fuzz),
+ resolution(resolution),
+ filter(filter) {
+ resetValue();
+ }
+
+ RawAbsoluteAxisInfo rawAxisInfo;
+ AxisInfo axisInfo;
+
+ float scale; // scale factor from raw to normalized values
+ float offset; // offset to add after scaling for normalization
+
+ float min; // normalized inclusive minimum
+ float max; // normalized inclusive maximum
+ float flat; // normalized flat region size
+ float fuzz; // normalized error tolerance
+ float resolution; // normalized resolution in units
+
+ float filter; // filter out small variations of this size
+ float currentValue; // current value
+ float newValue; // most recent value
+
+ void resetValue() {
+ this->currentValue = 0;
+ this->newValue = 0;
+ }
+ };
+
+ struct Sensor {
+ explicit Sensor(const InputDeviceSensorInfo& sensorInfo) : sensorInfo(sensorInfo) {
+ resetValue();
+ }
+ bool enabled;
+ InputDeviceSensorAccuracy accuracy;
+ std::chrono::nanoseconds samplingPeriod;
+ std::chrono::nanoseconds maxBatchReportLatency;
+ // last sample time in nano seconds
+ std::optional<nsecs_t> lastSampleTimeNs;
+ InputDeviceSensorInfo sensorInfo;
+ // Sensor X, Y, Z data mapping to abs
+ std::array<int32_t, SENSOR_VEC_LEN> dataVec;
+ void resetValue() {
+ this->enabled = false;
+ this->accuracy = InputDeviceSensorAccuracy::ACCURACY_NONE;
+ this->samplingPeriod = std::chrono::nanoseconds(0);
+ this->maxBatchReportLatency = std::chrono::nanoseconds(0);
+ this->lastSampleTimeNs = std::nullopt;
+ }
+ };
+
+ static Axis createAxis(const AxisInfo& AxisInfo, const RawAbsoluteAxisInfo& rawAxisInfo);
+
+ // Axes indexed by raw ABS_* axis index.
+ std::unordered_map<int32_t, Axis> mAxes;
+
+ // hardware timestamp from MSC_TIMESTAMP
+ nsecs_t mHardwareTimestamp;
+ uint32_t mPrevMscTime;
+
+ bool mDeviceEnabled;
+ // Does device support MSC_TIMESTAMP
+ bool mHasHardwareTimestamp;
+
+ // Sensor list
+ std::unordered_map<InputDeviceSensorType, Sensor> mSensors;
+
+ void sync(nsecs_t when, bool force);
+
+ template <typename T>
+ bool tryGetProperty(std::string keyName, T& outValue);
+
+ void parseSensorConfiguration(InputDeviceSensorType sensorType, int32_t absCode,
+ int32_t sensorDataIndex, const Axis& axis);
+
+ void processHardWareTimestamp(nsecs_t evTime, int32_t evValue);
+
+ Sensor createSensor(InputDeviceSensorType sensorType, const Axis& axis);
+
+ bool setSensorEnabled(InputDeviceSensorType sensorType, bool enabled);
+};
+
+} // namespace android
+
+#endif // _UI_INPUTREADER_SENSOR_INPUT_MAPPER_H
\ No newline at end of file
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5ab2ae3..6dbc88f 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -19,10 +19,10 @@
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <binder/Binder.h>
-#include <input/Input.h>
-
#include <gtest/gtest.h>
+#include <input/Input.h>
#include <linux/input.h>
+
#include <cinttypes>
#include <thread>
#include <unordered_set>
@@ -48,6 +48,9 @@
static const int32_t INJECTOR_PID = 999;
static const int32_t INJECTOR_UID = 1001;
+// An arbitrary pid of the gesture monitor window
+static constexpr int32_t MONITOR_PID = 2001;
+
struct PointF {
float x;
float y;
@@ -71,12 +74,10 @@
InputDispatcherConfiguration mConfig;
protected:
- virtual ~FakeInputDispatcherPolicy() {
- }
+ virtual ~FakeInputDispatcherPolicy() {}
public:
- FakeInputDispatcherPolicy() {
- }
+ FakeInputDispatcherPolicy() {}
void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_KEY, args.eventTime, args.action,
@@ -211,6 +212,33 @@
mConfig.keyRepeatDelay = delay;
}
+ void waitForSetPointerCapture(bool enabled) {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
+ [this, enabled]() REQUIRES(mLock) {
+ return mPointerCaptureEnabled &&
+ *mPointerCaptureEnabled ==
+ enabled;
+ })) {
+ FAIL() << "Timed out waiting for setPointerCapture(" << enabled << ") to be called.";
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
+ void assertSetPointerCaptureNotCalled() {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
+ FAIL() << "Expected setPointerCapture(enabled) to not be called, but was called. "
+ "enabled = "
+ << *mPointerCaptureEnabled;
+ }
+ mPointerCaptureEnabled.reset();
+ }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -218,6 +246,9 @@
sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
+ std::condition_variable mPointerCaptureChangedCondition;
+ std::optional<bool> mPointerCaptureEnabled GUARDED_BY(mLock);
+
// ANR handling
std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
std::queue<sp<IBinder>> mAnrConnectionTokens GUARDED_BY(mLock);
@@ -254,6 +285,12 @@
void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
+ void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+ const std::vector<float>& values) override {}
+
+ void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy) override {}
void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
*outConfig = mConfig;
@@ -307,6 +344,12 @@
mOnPointerDownToken = newToken;
}
+ void setPointerCapture(bool enabled) override {
+ std::scoped_lock lock(mLock);
+ mPointerCaptureEnabled = {enabled};
+ mPointerCaptureChangedCondition.notify_all();
+ }
+
void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
int32_t displayId) {
std::scoped_lock lock(mLock);
@@ -342,7 +385,7 @@
mFakePolicy = new FakeInputDispatcherPolicy();
mDispatcher = new InputDispatcher(mFakePolicy);
mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
- //Start InputDispatcher thread
+ // Start InputDispatcher thread
ASSERT_EQ(OK, mDispatcher->start());
}
@@ -379,7 +422,6 @@
}
};
-
TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
KeyEvent event;
@@ -574,9 +616,7 @@
}
virtual ~FakeApplicationHandle() {}
- virtual bool updateInfo() override {
- return true;
- }
+ virtual bool updateInfo() override { return true; }
void setDispatchingTimeout(std::chrono::milliseconds timeout) {
mInfo.dispatchingTimeoutMillis = timeout.count();
@@ -674,6 +714,9 @@
case AINPUT_EVENT_TYPE_FOCUS: {
FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
}
+ case AINPUT_EVENT_TYPE_CAPTURE: {
+ FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
+ }
default: {
FAIL() << mName.c_str() << ": invalid event type: " << expectedEventType;
}
@@ -696,6 +739,21 @@
EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
}
+ void consumeCaptureEvent(bool hasCapture) {
+ const InputEvent* event = consume();
+ ASSERT_NE(nullptr, event) << mName.c_str()
+ << ": consumer should have returned non-NULL event.";
+ ASSERT_EQ(AINPUT_EVENT_TYPE_CAPTURE, event->getType())
+ << "Got " << inputEventTypeToString(event->getType())
+ << " event instead of CAPTURE event";
+
+ ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
+ << mName.c_str() << ": event displayId should always be NONE.";
+
+ const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
+ EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
+ }
+
void assertNoEvents() {
InputEvent* event = consume();
if (event == nullptr) {
@@ -713,6 +771,10 @@
FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
ADD_FAILURE() << "Received focus event, hasFocus = "
<< (focusEvent.getHasFocus() ? "true" : "false");
+ } else if (event->getType() == AINPUT_EVENT_TYPE_CAPTURE) {
+ const auto& captureEvent = static_cast<CaptureEvent&>(*event);
+ ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
+ << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
}
FAIL() << mName.c_str()
<< ": should not have received any events, so consume() should return NULL";
@@ -800,6 +862,8 @@
void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
+ void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
+
void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
expectedFlags);
@@ -810,39 +874,40 @@
}
void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
expectedFlags);
}
void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId,
expectedFlags);
}
void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
expectedFlags);
}
void consumeMotionPointerDown(int32_t pointerIdx,
- int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) {
- int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN
- | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
+ int32_t expectedFlags = 0) {
+ int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
}
void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
- int32_t action = AMOTION_EVENT_ACTION_POINTER_UP
- | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ int32_t expectedFlags = 0) {
+ int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
consumeEvent(AINPUT_EVENT_TYPE_MOTION, action, expectedDisplayId, expectedFlags);
}
void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
- int32_t expectedFlags = 0) {
+ int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId,
expectedFlags);
}
@@ -859,6 +924,12 @@
mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
}
+ void consumeCaptureEvent(bool hasCapture) {
+ ASSERT_NE(mInputReceiver, nullptr)
+ << "Cannot consume events from a window with no receiver";
+ mInputReceiver->consumeCaptureEvent(hasCapture);
+ }
+
void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
int32_t expectedFlags) {
ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
@@ -900,6 +971,11 @@
const std::string& getName() { return mName; }
+ void setOwnerInfo(int32_t ownerPid, int32_t ownerUid) {
+ mInfo.ownerPid = ownerPid;
+ mInfo.ownerUid = ownerUid;
+ }
+
private:
const std::string mName;
std::unique_ptr<FakeInputReceiver> mInputReceiver;
@@ -1138,10 +1214,14 @@
return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
}
+static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(bool enabled) {
+ return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), enabled);
+}
+
TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(application, mDispatcher, "Fake Window",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window =
+ new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1204,10 +1284,10 @@
// The foreground window should receive the first touch down event.
TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> windowTop = new FakeWindowHandle(application, mDispatcher, "Top",
- ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> windowSecond = new FakeWindowHandle(application, mDispatcher, "Second",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> windowTop =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> windowSecond =
+ new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1488,17 +1568,18 @@
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
// Create a couple of windows
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
// Add the windows to the dispatcher
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
// Send down to the first window
- NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&downMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
@@ -1511,8 +1592,9 @@
secondWindow->consumeMotionDown();
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets no events and the second gets up
firstWindow->assertNoEvents();
@@ -1525,26 +1607,29 @@
PointF touchPoint = {10, 10};
// Create a couple of windows
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
// Add the windows to the dispatcher
mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
// Send down to the first window
- NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint});
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {touchPoint});
mDispatcher->notifyMotion(&downMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
// Send pointer down to the first window
- NotifyMotionArgs pointerDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
+ NotifyMotionArgs pointerDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {touchPoint, touchPoint});
mDispatcher->notifyMotion(&pointerDownMotionArgs);
// Only the first window should get the pointer down event
firstWindow->consumeMotionPointerDown(1);
@@ -1558,17 +1643,20 @@
secondWindow->consumeMotionPointerDown(1);
// Send pointer up to the second window
- NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint});
+ NotifyMotionArgs pointerUpMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {touchPoint, touchPoint});
mDispatcher->notifyMotion(&pointerUpMotionArgs);
// The first window gets nothing and the second gets pointer up
firstWindow->assertNoEvents();
secondWindow->consumeMotionPointerUp(1);
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets nothing and the second gets up
firstWindow->assertNoEvents();
@@ -1579,15 +1667,15 @@
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
// Create a non touch modal window that supports split touch
- sp<FakeWindowHandle> firstWindow = new FakeWindowHandle(application, mDispatcher,
- "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 600, 400));
firstWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
InputWindowInfo::Flag::SPLIT_TOUCH);
// Create a non touch modal window that supports split touch
- sp<FakeWindowHandle> secondWindow = new FakeWindowHandle(application, mDispatcher,
- "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect(0, 400, 600, 800));
secondWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
InputWindowInfo::Flag::SPLIT_TOUCH);
@@ -1599,17 +1687,20 @@
PointF pointInSecond = {300, 600};
// Send down to the first window
- NotifyMotionArgs firstDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst});
+ NotifyMotionArgs firstDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {pointInFirst});
mDispatcher->notifyMotion(&firstDownMotionArgs);
// Only the first window should get the down event
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
// Send down to the second window
- NotifyMotionArgs secondDownMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
+ NotifyMotionArgs secondDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
mDispatcher->notifyMotion(&secondDownMotionArgs);
// The first window gets a move and the second a down
firstWindow->consumeMotionMove();
@@ -1622,17 +1713,20 @@
secondWindow->consumeMotionPointerDown(1);
// Send pointer up to the second window
- NotifyMotionArgs pointerUpMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP
- | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {pointInFirst, pointInSecond});
+ NotifyMotionArgs pointerUpMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
mDispatcher->notifyMotion(&pointerUpMotionArgs);
// The first window gets nothing and the second gets pointer up
firstWindow->assertNoEvents();
secondWindow->consumeMotionPointerUp(1);
// Send up event to the second window
- NotifyMotionArgs upMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT);
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
mDispatcher->notifyMotion(&upMotionArgs);
// The first window gets nothing and the second gets up
firstWindow->assertNoEvents();
@@ -1693,12 +1787,77 @@
window->assertNoEvents(); // Key event or focus event will not be received
}
+TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ // Create first non touch modal window that supports split touch
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ firstWindow->setFrame(Rect(0, 0, 600, 400));
+ firstWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
+ InputWindowInfo::Flag::SPLIT_TOUCH);
+
+ // Create second non touch modal window that supports split touch
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ secondWindow->setFrame(Rect(0, 400, 600, 800));
+ secondWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
+ InputWindowInfo::Flag::SPLIT_TOUCH);
+
+ // Add the windows to the dispatcher
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+
+ PointF pointInFirst = {300, 200};
+ PointF pointInSecond = {300, 600};
+
+ // Send down to the first window
+ NotifyMotionArgs firstDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {pointInFirst});
+ mDispatcher->notifyMotion(&firstDownMotionArgs);
+ // Only the first window should get the down event
+ firstWindow->consumeMotionDown();
+ secondWindow->assertNoEvents();
+
+ // Send down to the second window
+ NotifyMotionArgs secondDownMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
+ mDispatcher->notifyMotion(&secondDownMotionArgs);
+ // The first window gets a move and the second a down
+ firstWindow->consumeMotionMove();
+ secondWindow->consumeMotionDown();
+
+ // Send pointer cancel to the second window
+ NotifyMotionArgs pointerUpMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInFirst, pointInSecond});
+ pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
+ mDispatcher->notifyMotion(&pointerUpMotionArgs);
+ // The first window gets move and the second gets cancel.
+ firstWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+ secondWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+
+ // Send up event.
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&upMotionArgs);
+ // The first window gets up and the second gets nothing.
+ firstWindow->consumeMotionUp();
+ secondWindow->assertNoEvents();
+}
+
class FakeMonitorReceiver {
public:
FakeMonitorReceiver(const sp<InputDispatcher>& dispatcher, const std::string name,
int32_t displayId, bool isGestureMonitor = false) {
base::Result<std::unique_ptr<InputChannel>> channel =
- dispatcher->createInputMonitor(displayId, isGestureMonitor, name);
+ dispatcher->createInputMonitor(displayId, isGestureMonitor, name, MONITOR_PID);
mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
}
@@ -1959,6 +2118,50 @@
EXPECT_EQ(motionArgs.buttonState, verifiedMotion.buttonState);
}
+TEST_F(InputDispatcherTest, NonPointerMotionEvent_JoystickNotTransformed) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ const std::string name = window->getName();
+
+ // Window gets transformed by offset values.
+ window->setWindowOffset(500.0f, 500.0f);
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ window->setFocusable(true);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+ // First, we set focused window so that focusedWindowHandle is not null.
+ setFocusedWindow(window);
+
+ // Second, we consume focus event if it is right or wrong according to onFocusChangedLocked.
+ window->consumeFocusEvent(true);
+
+ NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE,
+ AINPUT_SOURCE_JOYSTICK, ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&motionArgs);
+
+ // Third, we consume motion event.
+ InputEvent* event = window->consume();
+ ASSERT_NE(event, nullptr);
+ ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType())
+ << name.c_str() << "expected " << inputEventTypeToString(AINPUT_EVENT_TYPE_MOTION)
+ << " event, got " << inputEventTypeToString(event->getType()) << " event";
+
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
+ EXPECT_EQ(AINPUT_EVENT_TYPE_MOTION, motionEvent.getAction());
+
+ float expectedX = motionArgs.pointerCoords[0].getX();
+ float expectedY = motionArgs.pointerCoords[0].getY();
+
+ // Finally we test if the axis values from the final motion event are not transformed
+ EXPECT_EQ(expectedX, motionEvent.getX(0)) << "expected " << expectedX << " for x coord of "
+ << name.c_str() << ", got " << motionEvent.getX(0);
+ EXPECT_EQ(expectedY, motionEvent.getY(0)) << "expected " << expectedY << " for y coord of "
+ << name.c_str() << ", got " << motionEvent.getY(0);
+}
+
/**
* Ensure that separate calls to sign the same data are generating the same key.
* We avoid asserting against INVALID_HMAC. Since the key is random, there is a non-zero chance
@@ -2161,6 +2364,72 @@
window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
}
+/**
+ * Launch two windows, with different owners. One window (slipperyExitWindow) has Flag::SLIPPERY,
+ * and overlaps the other window, slipperyEnterWindow. The window 'slipperyExitWindow' is on top
+ * of the 'slipperyEnterWindow'.
+ *
+ * Inject touch down into the top window. Upon receipt of the DOWN event, move the window in such
+ * a way so that the touched location is no longer covered by the top window.
+ *
+ * Next, inject a MOVE event. Because the top window already moved earlier, this event is now
+ * positioned over the bottom (slipperyEnterWindow) only. And because the top window had
+ * Flag::SLIPPERY, this will cause the top window to lose the touch event (it will receive
+ * ACTION_CANCEL instead), and the bottom window will receive a newly generated gesture (starting
+ * with ACTION_DOWN).
+ * Thus, the touch has been transferred from the top window into the bottom window, because the top
+ * window moved itself away from the touched location and had Flag::SLIPPERY.
+ *
+ * Even though the top window moved away from the touched location, it is still obscuring the bottom
+ * window. It's just not obscuring it at the touched location. That means, FLAG_WINDOW_IS_PARTIALLY_
+ * OBSCURED should be set for the MotionEvent that reaches the bottom window.
+ *
+ * In this test, we ensure that the event received by the bottom window has
+ * FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
+ */
+TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) {
+ constexpr int32_t SLIPPERY_PID = INJECTOR_PID + 1;
+ constexpr int32_t SLIPPERY_UID = INJECTOR_UID + 1;
+
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ sp<FakeWindowHandle> slipperyExitWindow =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ slipperyExitWindow->setFlags(InputWindowInfo::Flag::NOT_TOUCH_MODAL |
+ InputWindowInfo::Flag::SLIPPERY);
+ // Make sure this one overlaps the bottom window
+ slipperyExitWindow->setFrame(Rect(25, 25, 75, 75));
+ // Change the owner uid/pid of the window so that it is considered to be occluding the bottom
+ // one. Windows with the same owner are not considered to be occluding each other.
+ slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID);
+
+ sp<FakeWindowHandle> slipperyEnterWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+
+ // Use notifyMotion instead of injecting to avoid dealing with injection permissions
+ NotifyMotionArgs args = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{50, 50}});
+ mDispatcher->notifyMotion(&args);
+ slipperyExitWindow->consumeMotionDown();
+ slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
+ mDispatcher->setInputWindows(
+ {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+
+ args = generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{51, 51}});
+ mDispatcher->notifyMotion(&args);
+
+ slipperyExitWindow->consumeMotionCancel();
+
+ slipperyEnterWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+ AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+}
+
class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
protected:
static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
@@ -2308,8 +2577,8 @@
InputDispatcherTest::SetUp();
application1 = std::make_shared<FakeApplicationHandle>();
- windowInPrimary = new FakeWindowHandle(application1, mDispatcher, "D_1",
- ADISPLAY_ID_DEFAULT);
+ windowInPrimary =
+ new FakeWindowHandle(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT);
// Set focus window for primary display, but focused display would be second one.
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
@@ -2319,8 +2588,8 @@
windowInPrimary->consumeFocusEvent(true);
application2 = std::make_shared<FakeApplicationHandle>();
- windowInSecondary = new FakeWindowHandle(application2, mDispatcher, "D_2",
- SECOND_DISPLAY_ID);
+ windowInSecondary =
+ new FakeWindowHandle(application2, mDispatcher, "D_2", SECOND_DISPLAY_ID);
// Set focus to second display window.
// Set focus display to second one.
mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
@@ -2431,7 +2700,7 @@
// Test per-display input monitors for key event.
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
- //Input monitor per display.
+ // Input monitor per display.
FakeMonitorReceiver monitorInPrimary =
FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
FakeMonitorReceiver monitorInSecondary =
@@ -2470,11 +2739,11 @@
void testNotifyMotion(int32_t displayId, bool expectToBeFiltered) {
NotifyMotionArgs motionArgs;
- motionArgs = generateMotionArgs(
- AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId);
+ motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, displayId);
mDispatcher->notifyMotion(&motionArgs);
- motionArgs = generateMotionArgs(
- AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
+ motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, displayId);
mDispatcher->notifyMotion(&motionArgs);
ASSERT_TRUE(mDispatcher->waitForIdle());
if (expectToBeFiltered) {
@@ -2542,8 +2811,8 @@
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
- mUnfocusedWindow = new FakeWindowHandle(application, mDispatcher, "Top",
- ADISPLAY_ID_DEFAULT);
+ mUnfocusedWindow =
+ new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
// Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
// window.
@@ -2618,8 +2887,7 @@
// Have two windows, one with focus. Inject MotionEvent with source TOUCHSCREEN and action
// DOWN on the window that already has focus. Ensure no window received the
// onPointerDownOutsideFocus callback.
-TEST_F(InputDispatcherOnPointerDownOutsideFocus,
- OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
+TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_TOUCH_POINT))
@@ -3824,4 +4092,88 @@
// window gets the pending key event
mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
}
+
+class InputDispatcherPointerCaptureTests : public InputDispatcherTest {
+protected:
+ std::shared_ptr<FakeApplicationHandle> mApp;
+ sp<FakeWindowHandle> mWindow;
+ sp<FakeWindowHandle> mSecondWindow;
+
+ void SetUp() override {
+ InputDispatcherTest::SetUp();
+ mApp = std::make_shared<FakeApplicationHandle>();
+ mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow->setFocusable(true);
+ mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+ mSecondWindow->setFocusable(true);
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+
+ setFocusedWindow(mWindow);
+ mWindow->consumeFocusEvent(true);
+ }
+
+ void notifyPointerCaptureChanged(bool enabled) {
+ const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(enabled);
+ mDispatcher->notifyPointerCaptureChanged(&args);
+ }
+
+ void requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window, bool enabled) {
+ mDispatcher->requestPointerCapture(window->getToken(), enabled);
+ mFakePolicy->waitForSetPointerCapture(enabled);
+ notifyPointerCaptureChanged(enabled);
+ window->consumeCaptureEvent(enabled);
+ }
+};
+
+TEST_F(InputDispatcherPointerCaptureTests, EnablePointerCaptureWhenFocused) {
+ // Ensure that capture cannot be obtained for unfocused windows.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+ mSecondWindow->assertNoEvents();
+
+ // Ensure that capture can be enabled from the focus window.
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // Ensure that capture cannot be disabled from a window that does not have capture.
+ mDispatcher->requestPointerCapture(mSecondWindow->getToken(), false);
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+
+ // Ensure that capture can be disabled from the window with capture.
+ requestAndVerifyPointerCapture(mWindow, false);
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, DisablesPointerCaptureAfterWindowLosesFocus) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ setFocusedWindow(mSecondWindow);
+
+ // Ensure that the capture disabled event was sent first.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->consumeFocusEvent(false);
+ mSecondWindow->consumeFocusEvent(true);
+ mFakePolicy->waitForSetPointerCapture(false);
+
+ // Ensure that additional state changes from InputReader are not sent to the window.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+ notifyPointerCaptureChanged(false);
+ mWindow->assertNoEvents();
+ mSecondWindow->assertNoEvents();
+ mFakePolicy->assertSetPointerCaptureNotCalled();
+}
+
+TEST_F(InputDispatcherPointerCaptureTests, UnexpectedStateChangeDisablesPointerCapture) {
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // InputReader unexpectedly disables and enables pointer capture.
+ notifyPointerCaptureChanged(false);
+ notifyPointerCaptureChanged(true);
+
+ // Ensure that Pointer Capture is disabled.
+ mWindow->consumeCaptureEvent(false);
+ mWindow->assertNoEvents();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index c26a389..6a06c9e 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -22,6 +22,7 @@
#include <InputReaderFactory.h>
#include <KeyboardInputMapper.h>
#include <MultiTouchInputMapper.h>
+#include <SensorInputMapper.h>
#include <SingleTouchInputMapper.h>
#include <SwitchInputMapper.h>
#include <TestInputListener.h>
@@ -364,6 +365,11 @@
uint32_t flags;
};
+ struct SensorInfo {
+ InputDeviceSensorType sensorType;
+ int32_t sensorDataIndex;
+ };
+
struct Device {
InputDeviceIdentifier identifier;
Flags<InputDeviceClass> classes;
@@ -377,6 +383,8 @@
KeyedVector<int32_t, KeyInfo> keysByScanCode;
KeyedVector<int32_t, KeyInfo> keysByUsageCode;
KeyedVector<int32_t, bool> leds;
+ std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
+ BitArray<MSC_MAX> mscBitmask;
std::vector<VirtualKeyDefinition> virtualKeys;
bool enabled;
@@ -535,6 +543,22 @@
device->leds.add(led, initialState);
}
+ void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
+ int32_t sensorDataIndex) {
+ Device* device = getDevice(deviceId);
+ SensorInfo info;
+ info.sensorType = sensorType;
+ info.sensorDataIndex = sensorDataIndex;
+ device->sensorsByAbsCode.emplace(absCode, info);
+ }
+
+ void setMscEvent(int32_t deviceId, int32_t mscEvent) {
+ Device* device = getDevice(deviceId);
+ typename BitArray<MSC_MAX>::Buffer buffer;
+ buffer[mscEvent / 32] = 1 << mscEvent % 32;
+ device->mscBitmask.loadFromBuffer(buffer);
+ }
+
bool getLedState(int32_t deviceId, int32_t led) {
Device* device = getDevice(deviceId);
return device->leds.valueFor(led);
@@ -630,6 +654,14 @@
bool hasInputProperty(int32_t, int) const override { return false; }
+ bool hasMscEvent(int32_t deviceId, int mscEvent) const override final {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false;
+ }
+ return false;
+ }
+
status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override {
Device* device = getDevice(deviceId);
@@ -669,6 +701,20 @@
status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; }
+ base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
+ int32_t absCode) {
+ Device* device = getDevice(deviceId);
+ if (!device) {
+ return Errorf("Sensor device not found.");
+ }
+ auto it = device->sensorsByAbsCode.find(absCode);
+ if (it == device->sensorsByAbsCode.end()) {
+ return Errorf("Sensor map not found.");
+ }
+ const SensorInfo& info = it->second;
+ return std::make_pair(info.sensorType, info.sensorDataIndex);
+ }
+
void setExcludedDevices(const std::vector<std::string>& devices) override {
mExcludedDevices = devices;
}
@@ -1435,6 +1481,32 @@
ASSERT_EQ(1U, mReader->getInputDevices().size());
}
+TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) {
+ constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+ constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
+ // Add two subdevices to device
+ std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
+ // Must add at least one mapper or the device will be ignored!
+ device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+
+ // Push same device instance for next device to be added, so they'll have same identifier.
+ mReader->pushNextDevice(device);
+ mReader->pushNextDevice(device);
+ // Sensor device is initially disabled
+ ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1",
+ InputDeviceClass::KEYBOARD | InputDeviceClass::SENSOR,
+ nullptr));
+ // Device is disabled because the only sub device is a sensor device and disabled initially.
+ ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0]));
+ ASSERT_FALSE(device->isEnabled());
+ ASSERT_NO_FATAL_FAILURE(
+ addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr));
+ // The merged device is enabled if any sub device is enabled
+ ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1]));
+ ASSERT_TRUE(device->isEnabled());
+}
+
TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr Flags<InputDeviceClass> deviceClass(InputDeviceClass::KEYBOARD);
@@ -2574,6 +2646,159 @@
ASSERT_TRUE(mapper.isVibrating());
}
+// --- SensorInputMapperTest ---
+
+class SensorInputMapperTest : public InputMapperTest {
+protected:
+ static const int32_t ACCEL_RAW_MIN;
+ static const int32_t ACCEL_RAW_MAX;
+ static const int32_t ACCEL_RAW_FUZZ;
+ static const int32_t ACCEL_RAW_FLAT;
+ static const int32_t ACCEL_RAW_RESOLUTION;
+
+ static const int32_t GYRO_RAW_MIN;
+ static const int32_t GYRO_RAW_MAX;
+ static const int32_t GYRO_RAW_FUZZ;
+ static const int32_t GYRO_RAW_FLAT;
+ static const int32_t GYRO_RAW_RESOLUTION;
+
+ static const float GRAVITY_MS2_UNIT;
+ static const float DEGREE_RADIAN_UNIT;
+
+ void prepareAccelAxes();
+ void prepareGyroAxes();
+ void setAccelProperties();
+ void setGyroProperties();
+ void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::SENSOR); }
+};
+
+const int32_t SensorInputMapperTest::ACCEL_RAW_MIN = -32768;
+const int32_t SensorInputMapperTest::ACCEL_RAW_MAX = 32768;
+const int32_t SensorInputMapperTest::ACCEL_RAW_FUZZ = 16;
+const int32_t SensorInputMapperTest::ACCEL_RAW_FLAT = 0;
+const int32_t SensorInputMapperTest::ACCEL_RAW_RESOLUTION = 8192;
+
+const int32_t SensorInputMapperTest::GYRO_RAW_MIN = -2097152;
+const int32_t SensorInputMapperTest::GYRO_RAW_MAX = 2097152;
+const int32_t SensorInputMapperTest::GYRO_RAW_FUZZ = 16;
+const int32_t SensorInputMapperTest::GYRO_RAW_FLAT = 0;
+const int32_t SensorInputMapperTest::GYRO_RAW_RESOLUTION = 1024;
+
+const float SensorInputMapperTest::GRAVITY_MS2_UNIT = 9.80665f;
+const float SensorInputMapperTest::DEGREE_RADIAN_UNIT = 0.0174533f;
+
+void SensorInputMapperTest::prepareAccelAxes() {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+ ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+ ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Z, ACCEL_RAW_MIN, ACCEL_RAW_MAX, ACCEL_RAW_FUZZ,
+ ACCEL_RAW_FLAT, ACCEL_RAW_RESOLUTION);
+}
+
+void SensorInputMapperTest::prepareGyroAxes() {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RX, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+ GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RY, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+ GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_RZ, GYRO_RAW_MIN, GYRO_RAW_MAX, GYRO_RAW_FUZZ,
+ GYRO_RAW_FLAT, GYRO_RAW_RESOLUTION);
+}
+
+void SensorInputMapperTest::setAccelProperties() {
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 0, InputDeviceSensorType::ACCELEROMETER,
+ /* sensorDataIndex */ 0);
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 1, InputDeviceSensorType::ACCELEROMETER,
+ /* sensorDataIndex */ 1);
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 2, InputDeviceSensorType::ACCELEROMETER,
+ /* sensorDataIndex */ 2);
+ mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+ addConfigurationProperty("sensor.accelerometer.reportingMode", "0");
+ addConfigurationProperty("sensor.accelerometer.maxDelay", "100000");
+ addConfigurationProperty("sensor.accelerometer.minDelay", "5000");
+ addConfigurationProperty("sensor.accelerometer.power", "1.5");
+}
+
+void SensorInputMapperTest::setGyroProperties() {
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 3, InputDeviceSensorType::GYROSCOPE,
+ /* sensorDataIndex */ 0);
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 4, InputDeviceSensorType::GYROSCOPE,
+ /* sensorDataIndex */ 1);
+ mFakeEventHub->addSensorAxis(EVENTHUB_ID, /* absCode */ 5, InputDeviceSensorType::GYROSCOPE,
+ /* sensorDataIndex */ 2);
+ mFakeEventHub->setMscEvent(EVENTHUB_ID, MSC_TIMESTAMP);
+ addConfigurationProperty("sensor.gyroscope.reportingMode", "0");
+ addConfigurationProperty("sensor.gyroscope.maxDelay", "100000");
+ addConfigurationProperty("sensor.gyroscope.minDelay", "5000");
+ addConfigurationProperty("sensor.gyroscope.power", "0.8");
+}
+
+TEST_F(SensorInputMapperTest, GetSources) {
+ SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+
+ ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
+}
+
+TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
+ setAccelProperties();
+ prepareAccelAxes();
+ SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+
+ ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
+ std::chrono::microseconds(10000),
+ std::chrono::microseconds(0)));
+ ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_X, 20000);
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Y, -20000);
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_Z, 40000);
+ process(mapper, ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+ process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+ NotifySensorArgs args;
+ std::vector<float> values = {20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
+ -20000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT,
+ 40000.0f / ACCEL_RAW_RESOLUTION * GRAVITY_MS2_UNIT};
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
+ ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
+ ASSERT_EQ(args.deviceId, DEVICE_ID);
+ ASSERT_EQ(args.sensorType, InputDeviceSensorType::ACCELEROMETER);
+ ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+ ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
+ ASSERT_EQ(args.values, values);
+ mapper.flushSensor(InputDeviceSensorType::ACCELEROMETER);
+}
+
+TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
+ setGyroProperties();
+ prepareGyroAxes();
+ SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+
+ ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
+ std::chrono::microseconds(10000),
+ std::chrono::microseconds(0)));
+ ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(EVENTHUB_ID));
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RX, 20000);
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RY, -20000);
+ process(mapper, ARBITRARY_TIME, EV_ABS, ABS_RZ, 40000);
+ process(mapper, ARBITRARY_TIME, EV_MSC, MSC_TIMESTAMP, 1000);
+ process(mapper, ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+ NotifySensorArgs args;
+ std::vector<float> values = {20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
+ -20000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT,
+ 40000.0f / GYRO_RAW_RESOLUTION * DEGREE_RADIAN_UNIT};
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySensorWasCalled(&args));
+ ASSERT_EQ(args.source, AINPUT_SOURCE_SENSOR);
+ ASSERT_EQ(args.deviceId, DEVICE_ID);
+ ASSERT_EQ(args.sensorType, InputDeviceSensorType::GYROSCOPE);
+ ASSERT_EQ(args.accuracy, InputDeviceSensorAccuracy::ACCURACY_HIGH);
+ ASSERT_EQ(args.hwTimestamp, ARBITRARY_TIME);
+ ASSERT_EQ(args.values, values);
+ mapper.flushSensor(InputDeviceSensorType::GYROSCOPE);
+}
+
// --- KeyboardInputMapperTest ---
class KeyboardInputMapperTest : public InputMapperTest {
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 1050ab8..295c6e3 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -80,6 +80,12 @@
"Expected notifySwitch() to have been called."));
}
+void TestInputListener::assertNotifySensorWasCalled(NotifySensorArgs* outEventArgs) {
+ ASSERT_NO_FATAL_FAILURE(
+ assertCalled<NotifySensorArgs>(outEventArgs,
+ "Expected notifySensor() to have been called."));
+}
+
void TestInputListener::assertNotifyCaptureWasCalled(
NotifyPointerCaptureChangedArgs* outEventArgs) {
ASSERT_NO_FATAL_FAILURE(
@@ -155,4 +161,8 @@
notify<NotifyPointerCaptureChangedArgs>(args);
}
+void TestInputListener::notifySensor(const NotifySensorArgs* args) {
+ notify<NotifySensorArgs>(args);
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 887d4ea..e54350a 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -55,6 +55,7 @@
void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);
void assertNotifyCaptureWasCalled(NotifyPointerCaptureChangedArgs* outEventArgs = nullptr);
+ void assertNotifySensorWasCalled(NotifySensorArgs* outEventArgs = nullptr);
private:
template <class NotifyArgsType>
@@ -76,6 +77,8 @@
virtual void notifySwitch(const NotifySwitchArgs* args) override;
+ virtual void notifySensor(const NotifySensorArgs* args) override;
+
virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
std::mutex mLock;
@@ -88,6 +91,7 @@
std::vector<NotifyKeyArgs>, //
std::vector<NotifyMotionArgs>, //
std::vector<NotifySwitchArgs>, //
+ std::vector<NotifySensorArgs>, //
std::vector<NotifyPointerCaptureChangedArgs>> //
mQueues GUARDED_BY(mLock);
};
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index fdb8aaf..a7cd258 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -3,6 +3,7 @@
cflags: [
"-Wall",
"-Werror",
+ "-Wextra",
"-Wformat",
"-Wthread-safety",
"-Wunused",
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index fa75ffa..6561707 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -389,6 +389,20 @@
mBufferInfo.mFrameLatencyNeeded = true;
}
+bool BufferLayer::frameIsEarly(nsecs_t expectedPresentTime) const {
+ // TODO(b/169901895): kEarlyLatchVsyncThreshold should be based on the
+ // vsync period. We can do this change as soon as ag/13100772 is merged.
+ constexpr static std::chrono::nanoseconds kEarlyLatchVsyncThreshold = 5ms;
+
+ const auto presentTime = nextPredictedPresentTime();
+ if (std::abs(presentTime - expectedPresentTime) >= kEarlyLatchMaxThreshold.count()) {
+ return false;
+ }
+
+ return presentTime >= expectedPresentTime &&
+ presentTime - expectedPresentTime >= kEarlyLatchVsyncThreshold.count();
+}
+
bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
nsecs_t expectedPresentTime) {
ATRACE_CALL();
@@ -421,6 +435,12 @@
return false;
}
+ if (frameIsEarly(expectedPresentTime)) {
+ ATRACE_NAME("frameIsEarly()");
+ mFlinger->signalLayerUpdate();
+ return false;
+ }
+
// If the head buffer's acquire fence hasn't signaled yet, return and
// try again later
if (!fenceHasSignaled()) {
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 63dfe5f..5cd9a7c 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -224,6 +224,18 @@
std::unique_ptr<compositionengine::LayerFECompositionState> mCompositionState;
FloatRect computeSourceBounds(const FloatRect& parentBounds) const override;
+
+ // Returns true if the next frame is considered too early to present
+ // at the given expectedPresentTime
+ bool frameIsEarly(nsecs_t expectedPresentTime) const;
+
+ // Returns the predicted present time of the next frame if available or
+ // 0 otherwise.
+ virtual nsecs_t nextPredictedPresentTime() const = 0;
+
+ // The amount of time SF can delay a frame if it is considered early based
+ // on the VsyncModulator::VsyncConfig::appWorkDuration
+ static constexpr std::chrono::nanoseconds kEarlyLatchMaxThreshold = 100ms;
};
} // namespace android
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 325ecfe..04cec4f 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "BufferQueueLayer"
@@ -222,6 +223,21 @@
return mQueuedFrames > 0;
}
+nsecs_t BufferQueueLayer::nextPredictedPresentTime() const {
+ Mutex::Autolock lock(mQueueItemLock);
+ if (mQueueItems.empty()) {
+ return 0;
+ }
+
+ const auto& bufferData = mQueueItems[0];
+
+ if (!bufferData.item.mIsAutoTimestamp || !bufferData.surfaceFrame) {
+ return 0;
+ }
+
+ return bufferData.surfaceFrame->getPredictions().presentTime;
+}
+
status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
nsecs_t expectedPresentTime) {
// This boolean is used to make sure that SurfaceFlinger's shadow copy
@@ -274,8 +290,8 @@
mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage);
mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber);
if (mQueueItems[0].surfaceFrame) {
- mFlinger->mFrameTimeline->addSurfaceFrame(std::move(mQueueItems[0].surfaceFrame),
- PresentState::Dropped);
+ mQueueItems[0].surfaceFrame->setPresentState(PresentState::Dropped);
+ mFlinger->mFrameTimeline->addSurfaceFrame(mQueueItems[0].surfaceFrame);
}
mQueueItems.erase(mQueueItems.begin());
mQueuedFrames--;
@@ -290,8 +306,8 @@
Mutex::Autolock lock(mQueueItemLock);
for (auto& [item, surfaceFrame] : mQueueItems) {
if (surfaceFrame) {
- mFlinger->mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame),
- PresentState::Dropped);
+ surfaceFrame->setPresentState(PresentState::Dropped);
+ mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame);
}
}
mQueueItems.clear();
@@ -321,8 +337,8 @@
mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage);
mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber);
if (mQueueItems[0].surfaceFrame) {
- mFlinger->mFrameTimeline->addSurfaceFrame(std::move(mQueueItems[0].surfaceFrame),
- PresentState::Dropped);
+ mQueueItems[0].surfaceFrame->setPresentState(PresentState::Dropped);
+ mFlinger->mFrameTimeline->addSurfaceFrame(mQueueItems[0].surfaceFrame);
}
mQueueItems.erase(mQueueItems.begin());
mQueuedFrames--;
@@ -336,8 +352,9 @@
if (mQueueItems[0].surfaceFrame) {
mQueueItems[0].surfaceFrame->setAcquireFenceTime(
mQueueItems[0].item.mFenceTime->getSignalTime());
- mFlinger->mFrameTimeline->addSurfaceFrame(std::move(mQueueItems[0].surfaceFrame),
- PresentState::Presented);
+ mQueueItems[0].surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime);
+ mFlinger->mFrameTimeline->addSurfaceFrame(mQueueItems[0].surfaceFrame);
+ mLastLatchTime = latchTime;
}
mQueueItems.erase(mQueueItems.begin());
}
@@ -436,8 +453,9 @@
}
auto surfaceFrame =
- mFlinger->mFrameTimeline->createSurfaceFrameForToken(mOwnerPid, mOwnerUid, mName,
- mName, mFrameTimelineVsyncId);
+ mFlinger->mFrameTimeline->createSurfaceFrameForToken(mFrameTimelineVsyncId,
+ mOwnerPid, mOwnerUid, mName,
+ mName);
surfaceFrame->setActualQueueTime(systemTime());
mQueueItems.push_back({item, surfaceFrame});
@@ -475,8 +493,9 @@
}
auto surfaceFrame =
- mFlinger->mFrameTimeline->createSurfaceFrameForToken(mOwnerPid, mOwnerUid, mName,
- mName, mFrameTimelineVsyncId);
+ mFlinger->mFrameTimeline->createSurfaceFrameForToken(mFrameTimelineVsyncId,
+ mOwnerPid, mOwnerUid, mName,
+ mName);
surfaceFrame->setActualQueueTime(systemTime());
mQueueItems[mQueueItems.size() - 1].item = item;
mQueueItems[mQueueItems.size() - 1].surfaceFrame = std::move(surfaceFrame);
@@ -643,4 +662,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index 5a6b9bc..2347d8c 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -116,6 +116,8 @@
// Temporary - Used only for LEGACY camera mode.
uint32_t getProducerStickyTransform() const;
+ nsecs_t nextPredictedPresentTime() const override;
+
sp<BufferLayerConsumer> mConsumer;
sp<IGraphicBufferProducer> mProducer;
@@ -146,6 +148,11 @@
// a buffer to correlate the buffer with the vsync id. Can only be accessed
// with the SF state lock held.
std::optional<int64_t> mFrameTimelineVsyncId;
+
+ // Keeps track of the time SF latched the last buffer from this layer.
+ // Used in buffer stuffing analysis in FrameTimeline.
+ // TODO(b/176106798): Find a way to do this for BLASTBufferQueue as well.
+ nsecs_t mLastLatchTime = 0;
};
} // namespace android
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index b6c59cd..bca1c69 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
//#define LOG_NDEBUG 0
#undef LOG_TAG
@@ -341,12 +342,18 @@
}
bool BufferStateLayer::setBuffer(const sp<GraphicBuffer>& buffer, const sp<Fence>& acquireFence,
- nsecs_t postTime, nsecs_t desiredPresentTime,
+ nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp,
const client_cache_t& clientCacheId, uint64_t frameNumber) {
ATRACE_CALL();
if (mCurrentState.buffer) {
mReleasePreviousBuffer = true;
+ if (mCurrentState.buffer != mDrawingState.buffer) {
+ // If mCurrentState has a buffer, and we are about to update again
+ // before swapping to drawing state, then the first buffer will be
+ // dropped and we should decrement the pending buffer count.
+ decrementPendingBufferCount();
+ }
}
mCurrentState.frameNumber = frameNumber;
@@ -359,13 +366,13 @@
const int32_t layerId = getSequence();
mFlinger->mTimeStats->setPostTime(layerId, mCurrentState.frameNumber, getName().c_str(),
mOwnerUid, postTime);
- desiredPresentTime = desiredPresentTime <= 0 ? 0 : desiredPresentTime;
mCurrentState.desiredPresentTime = desiredPresentTime;
+ mCurrentState.isAutoTimestamp = isAutoTimestamp;
- mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime,
+ mFlinger->mScheduler->recordLayerHistory(this, isAutoTimestamp ? 0 : desiredPresentTime,
LayerHistory::LayerUpdateType::Buffer);
- addFrameEvent(acquireFence, postTime, desiredPresentTime);
+ addFrameEvent(acquireFence, postTime, isAutoTimestamp ? 0 : desiredPresentTime);
return true;
}
@@ -527,7 +534,7 @@
return true;
}
- return mCurrentState.desiredPresentTime <= expectedPresentTime;
+ return mCurrentState.isAutoTimestamp || mCurrentState.desiredPresentTime <= expectedPresentTime;
}
bool BufferStateLayer::onPreComposition(nsecs_t refreshStartTime) {
@@ -595,6 +602,14 @@
return mCurrentStateModified && (c.buffer != nullptr || c.bgColorLayer != nullptr);
}
+nsecs_t BufferStateLayer::nextPredictedPresentTime() const {
+ if (!getDrawingState().isAutoTimestamp || !mSurfaceFrame) {
+ return 0;
+ }
+
+ return mSurfaceFrame->getPredictions().presentTime;
+}
+
status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime,
nsecs_t /*expectedPresentTime*/) {
const State& s(getDrawingState());
@@ -629,6 +644,7 @@
if (s.buffer == nullptr) {
return BAD_VALUE;
}
+ decrementPendingBufferCount();
mPreviousBufferId = getCurrentBufferId();
mBufferInfo.mBuffer = s.buffer;
@@ -826,7 +842,33 @@
const Rect layerSize{getBounds()};
return layerSize.width() != bufferWidth || layerSize.height() != bufferHeight;
}
+
+void BufferStateLayer::incrementPendingBufferCount() {
+ mPendingBufferTransactions++;
+ tracePendingBufferCount();
+}
+
+void BufferStateLayer::decrementPendingBufferCount() {
+ mPendingBufferTransactions--;
+ tracePendingBufferCount();
+}
+
+void BufferStateLayer::tracePendingBufferCount() {
+ ATRACE_INT(mBlastTransactionName.c_str(), mPendingBufferTransactions);
+}
+
+uint32_t BufferStateLayer::doTransaction(uint32_t flags) {
+ if (mDrawingState.buffer != nullptr && mDrawingState.buffer != mBufferInfo.mBuffer) {
+ // If we are about to update mDrawingState.buffer but it has not yet latched
+ // then we will drop a buffer and should decrement the pending buffer count.
+ // This logic may not work perfectly in the face of a BufferStateLayer being the
+ // deferred side of a deferred transaction, but we don't expect this use case.
+ decrementPendingBufferCount();
+ }
+ return Layer::doTransaction(flags);
+}
+
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 69b27e4..6414e6b 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -70,8 +70,8 @@
bool setCrop(const Rect& crop) override;
bool setFrame(const Rect& frame) override;
bool setBuffer(const sp<GraphicBuffer>& buffer, const sp<Fence>& acquireFence, nsecs_t postTime,
- nsecs_t desiredPresentTime, const client_cache_t& clientCacheId,
- uint64_t frameNumber) override;
+ nsecs_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& clientCacheId, uint64_t frameNumber) override;
bool setAcquireFence(const sp<Fence>& fence) override;
bool setDataspace(ui::Dataspace dataspace) override;
bool setHdrMetadata(const HdrMetadata& hdrMetadata) override;
@@ -112,6 +112,11 @@
bool onPreComposition(nsecs_t refreshStartTime) override;
uint32_t getEffectiveScalingMode() const override;
+ // See mPendingBufferTransactions
+ void incrementPendingBufferCount() override;
+ void decrementPendingBufferCount();
+ uint32_t doTransaction(uint32_t flags) override;
+
protected:
void gatherBufferInfo() override;
uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const;
@@ -119,6 +124,7 @@
private:
friend class SlotGenerationTest;
+ inline void tracePendingBufferCount();
bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
nsecs_t requestedPresentTime);
@@ -146,6 +152,8 @@
bool bufferNeedsFiltering() const override;
+ nsecs_t nextPredictedPresentTime() const override;
+
static const std::array<float, 16> IDENTITY_MATRIX;
std::unique_ptr<renderengine::Image> mTextureImage;
@@ -163,6 +171,18 @@
std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> mPendingJankClassifications;
+ const std::string mBlastTransactionName{"BufferTX - " + mName};
+ // This integer is incremented everytime a buffer arrives at the server for this layer,
+ // and decremented when a buffer is dropped or latched. When changed the integer is exported
+ // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is
+ // possible to see when a buffer arrived at the server, and in which frame it latched.
+ //
+ // You can understand the trace this way:
+ // - If the integer increases, a buffer arrived at the server.
+ // - If the integer decreases in latchBuffer, that buffer was latched
+ // - If the integer decreases in setBuffer or doTransaction, a buffer was dropped
+ uint64_t mPendingBufferTransactions{0};
+
// TODO(marissaw): support sticky transform for LEGACY camera mode
class HwcSlotGenerator : public ClientCache::ErasedRecipient {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
index 67e6deb..df44e75 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
@@ -22,11 +22,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
index 7eb8eb1..1136e3d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfileCreationArgs.h
@@ -23,11 +23,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <ui/HdrCapabilities.h>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 26f7f68..018a687 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -23,11 +23,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <renderengine/LayerSettings.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <utils/RefBase.h>
#include <utils/Timers.h>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 5a3b9ac..c445d5b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -29,6 +29,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <gui/BufferQueue.h>
#include <ui/GraphicBuffer.h>
@@ -37,7 +38,7 @@
#include "DisplayHardware/Hal.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android::compositionengine {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index aa70ef8..fb19216 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -25,12 +25,13 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include "DisplayHardware/ComposerHal.h"
#include "DisplayHardware/DisplayIdentification.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index 2864c10..aa049a8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
@@ -22,11 +22,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <gui/BufferQueue.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <utils/StrongPointer.h>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 06e6a6f..8f767d3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -23,11 +23,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <compositionengine/ProjectionSpace.h>
#include <ui/Rect.h>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index d2b38d1..9a118d3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -30,11 +30,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include "DisplayHardware/ComposerHal.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 1befbf8..348ec39 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include <cmath>
#include <compositionengine/DisplayColorProfileCreationArgs.h>
@@ -1043,3 +1047,6 @@
} // namespace
} // namespace android::compositionengine
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index 87911cc..9518659 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -27,12 +27,13 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
#include "DisplayHardware/HWC2.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
namespace HWC2 {
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 84c027b..e64a9f1 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -22,11 +22,12 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include "DisplayHardware/HWComposer.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
namespace mock {
diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index 6ce8a6b..cd39733 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include <cstdarg>
#include <cstdint>
@@ -359,3 +363,6 @@
} // namespace
} // namespace android::compositionengine
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 5b66809..c756d65 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -26,6 +26,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <android/hardware/graphics/common/1.1/types.h>
#include <android/hardware/graphics/composer/2.4/IComposer.h>
@@ -38,7 +39,7 @@
#include <utils/StrongPointer.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
namespace android {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index e6bff04..426092d 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -26,18 +26,17 @@
#include "HWC2.h"
+#include <android/configuration.h>
+#include <ftl/future.h>
#include <ui/Fence.h>
#include <ui/FloatRect.h>
#include <ui/GraphicBuffer.h>
-#include <android/configuration.h>
-
-#include <inttypes.h>
#include <algorithm>
+#include <cinttypes>
#include <iterator>
#include <set>
-#include "../Promise.h"
#include "ComposerHal.h"
namespace android {
@@ -647,7 +646,7 @@
}
std::future<Error> Display::setDisplayBrightness(float brightness) {
- return promise::defer([composer = &mComposer, id = mId, brightness] {
+ return ftl::defer([composer = &mComposer, id = mId, brightness] {
const auto intError = composer->setDisplayBrightness(id, brightness);
return static_cast<Error>(intError);
});
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 1548d18..6f3987f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -30,6 +30,7 @@
#include <compositionengine/Output.h>
#include <compositionengine/OutputLayer.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ftl/future.h>
#include <log/log.h>
#include <ui/DebugUtils.h>
#include <ui/GraphicBuffer.h>
@@ -37,7 +38,6 @@
#include <utils/Trace.h>
#include "../Layer.h" // needed only for debugging
-#include "../Promise.h"
#include "../SurfaceFlinger.h"
#include "../SurfaceFlingerProperties.h"
#include "ComposerHal.h"
@@ -74,6 +74,7 @@
namespace hal = android::hardware::graphics::composer::hal;
+namespace android {
namespace {
using android::hardware::Return;
@@ -88,47 +89,46 @@
mSequenceId(sequenceId),
mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
- android::hardware::Return<void> onHotplug(hal::HWDisplayId display,
- hal::Connection conn) override {
+ Return<void> onHotplug(hal::HWDisplayId display, hal::Connection conn) override {
mCallback->onHotplugReceived(mSequenceId, display, conn);
- return android::hardware::Void();
+ return Void();
}
- android::hardware::Return<void> onRefresh(hal::HWDisplayId display) override {
+ Return<void> onRefresh(hal::HWDisplayId display) override {
mCallback->onRefreshReceived(mSequenceId, display);
- return android::hardware::Void();
+ return Void();
}
- android::hardware::Return<void> onVsync(hal::HWDisplayId display, int64_t timestamp) override {
+ Return<void> onVsync(hal::HWDisplayId display, int64_t timestamp) override {
if (!mVsyncSwitchingSupported) {
mCallback->onVsyncReceived(mSequenceId, display, timestamp, std::nullopt);
} else {
ALOGW("Unexpected onVsync callback on composer >= 2.4, ignoring.");
}
- return android::hardware::Void();
+ return Void();
}
- android::hardware::Return<void> onVsync_2_4(hal::HWDisplayId display, int64_t timestamp,
- hal::VsyncPeriodNanos vsyncPeriodNanos) override {
+ Return<void> onVsync_2_4(hal::HWDisplayId display, int64_t timestamp,
+ hal::VsyncPeriodNanos vsyncPeriodNanos) override {
if (mVsyncSwitchingSupported) {
mCallback->onVsyncReceived(mSequenceId, display, timestamp,
std::make_optional(vsyncPeriodNanos));
} else {
ALOGW("Unexpected onVsync_2_4 callback on composer <= 2.3, ignoring.");
}
- return android::hardware::Void();
+ return Void();
}
- android::hardware::Return<void> onVsyncPeriodTimingChanged(
+ Return<void> onVsyncPeriodTimingChanged(
hal::HWDisplayId display,
const hal::VsyncPeriodChangeTimeline& updatedTimeline) override {
mCallback->onVsyncPeriodTimingChangedReceived(mSequenceId, display, updatedTimeline);
- return android::hardware::Void();
+ return Void();
}
- android::hardware::Return<void> onSeamlessPossible(hal::HWDisplayId display) override {
+ Return<void> onSeamlessPossible(hal::HWDisplayId display) override {
mCallback->onSeamlessPossible(mSequenceId, display);
- return android::hardware::Void();
+ return Void();
}
private:
@@ -139,8 +139,6 @@
} // namespace
-namespace android {
-
HWComposer::~HWComposer() = default;
namespace impl {
@@ -295,6 +293,7 @@
hal::DisplayType::PHYSICAL);
newDisplay->setConnected(true);
displayData.hwcDisplay = std::move(newDisplay);
+ displayData.configs = displayData.hwcDisplay->getConfigs();
mPhysicalDisplayIdMap[hwcDisplayId] = displayId;
}
@@ -335,14 +334,9 @@
PhysicalDisplayId displayId) const {
RETURN_IF_INVALID_DISPLAY(displayId, {});
- const auto& displayData = mDisplayData.at(displayId);
- auto configs = displayData.hwcDisplay->getConfigs();
- if (displayData.configMap.empty()) {
- for (size_t i = 0; i < configs.size(); ++i) {
- displayData.configMap[i] = configs[i];
- }
- }
- return configs;
+ // We cache the configs when the DisplayData is created on hotplug. If the configs need to
+ // change HWC will send a hotplug event which will recreate displayData.
+ return mDisplayData.at(displayId).configs;
}
std::shared_ptr<const HWC2::Display::Config> HWComposer::getActiveConfig(
@@ -653,13 +647,13 @@
RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
auto& displayData = mDisplayData[displayId];
- if (displayData.configMap.count(configId) == 0) {
+ if (configId >= displayData.configs.size()) {
LOG_DISPLAY_ERROR(displayId, ("Invalid config " + std::to_string(configId)).c_str());
return BAD_INDEX;
}
auto error =
- displayData.hwcDisplay->setActiveConfigWithConstraints(displayData.configMap[configId],
+ displayData.hwcDisplay->setActiveConfigWithConstraints(displayData.configs[configId],
constraints, outTimeline);
RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
return NO_ERROR;
@@ -792,10 +786,10 @@
std::future<status_t> HWComposer::setDisplayBrightness(PhysicalDisplayId displayId,
float brightness) {
- RETURN_IF_INVALID_DISPLAY(displayId, promise::yield<status_t>(BAD_INDEX));
+ RETURN_IF_INVALID_DISPLAY(displayId, ftl::yield<status_t>(BAD_INDEX));
auto& display = mDisplayData[displayId].hwcDisplay;
- return promise::chain(display->setDisplayBrightness(brightness))
+ return ftl::chain(display->setDisplayBrightness(brightness))
.then([displayId](hal::Error error) -> status_t {
if (error == hal::Error::UNSUPPORTED) {
RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index d8af5bf..7e1da25 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -32,8 +32,9 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
-#pragma clang diagnostic pop
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <utils/StrongPointer.h>
#include <utils/Timers.h>
@@ -183,7 +184,6 @@
virtual nsecs_t getRefreshTimestamp(PhysicalDisplayId) const = 0;
virtual bool isConnected(PhysicalDisplayId) const = 0;
- // Non-const because it can update configMap inside of mDisplayData
virtual std::vector<std::shared_ptr<const HWC2::Display::Config>> getConfigs(
PhysicalDisplayId) const = 0;
@@ -319,7 +319,6 @@
nsecs_t getRefreshTimestamp(PhysicalDisplayId) const override;
bool isConnected(PhysicalDisplayId) const override;
- // Non-const because it can update configMap inside of mDisplayData
std::vector<std::shared_ptr<const HWC2::Display::Config>> getConfigs(
PhysicalDisplayId) const override;
@@ -378,8 +377,7 @@
std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
buffer_handle_t outbufHandle = nullptr;
sp<Fence> outbufAcquireFence = Fence::NO_FENCE;
- mutable std::unordered_map<int32_t,
- std::shared_ptr<const HWC2::Display::Config>> configMap;
+ std::vector<std::shared_ptr<const HWC2::Display::Config>> configs;
bool validateWasSkipped;
hal::Error presentError;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index b09f07a..a8fef04 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -26,7 +26,7 @@
#include <cinttypes>
#include <numeric>
-namespace android::frametimeline::impl {
+namespace android::frametimeline {
using base::StringAppendF;
using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
@@ -60,7 +60,8 @@
StringAppendF(&result, "\t%10.2f\t|",
std::chrono::duration<double, std::milli>(startTime).count());
}
- if (actuals.endTime == 0) {
+ if (actuals.endTime <= 0) {
+ // Animation leashes can send the endTime as -1
StringAppendF(&result, "\t\tN/A\t|");
} else {
std::chrono::nanoseconds endTime(actuals.endTime - baseTime);
@@ -89,124 +90,160 @@
case PredictionState::Expired:
return "Expired";
case PredictionState::None:
- default:
return "None";
}
}
-std::string toString(JankType jankType) {
- switch (jankType) {
- case JankType::None:
- return "None";
- case JankType::Display:
- return "Composer/Display - outside SF and App";
- case JankType::SurfaceFlingerDeadlineMissed:
- return "SurfaceFlinger Deadline Missed";
- case JankType::AppDeadlineMissed:
- return "App Deadline Missed";
- case JankType::PredictionExpired:
- return "Prediction Expired";
- case JankType::SurfaceFlingerEarlyLatch:
- return "SurfaceFlinger Early Latch";
- default:
- return "Unclassified";
- }
-}
-
-std::string jankMetadataBitmaskToString(int32_t jankMetadata) {
- std::vector<std::string> jankInfo;
-
- if (jankMetadata & EarlyStart) {
- jankInfo.emplace_back("Early Start");
- } else if (jankMetadata & LateStart) {
- jankInfo.emplace_back("Late Start");
- }
-
- if (jankMetadata & EarlyFinish) {
- jankInfo.emplace_back("Early Finish");
- } else if (jankMetadata & LateFinish) {
- jankInfo.emplace_back("Late Finish");
- }
-
- if (jankMetadata & EarlyPresent) {
- jankInfo.emplace_back("Early Present");
- } else if (jankMetadata & LatePresent) {
- jankInfo.emplace_back("Late Present");
- }
- // TODO(b/169876734): add GPU composition metadata here
-
- if (jankInfo.empty()) {
+std::string jankTypeBitmaskToString(int32_t jankType) {
+ // TODO(b/175843808): Make this a switch case if jankType becomes an enum class
+ std::vector<std::string> janks;
+ if (jankType == JankType::None) {
return "None";
}
- return std::accumulate(jankInfo.begin(), jankInfo.end(), std::string(),
+ if (jankType & JankType::DisplayHAL) {
+ janks.emplace_back("Display HAL");
+ }
+ if (jankType & JankType::SurfaceFlingerCpuDeadlineMissed) {
+ janks.emplace_back("SurfaceFlinger CPU Deadline Missed");
+ }
+ if (jankType & JankType::SurfaceFlingerGpuDeadlineMissed) {
+ janks.emplace_back("SurfaceFlinger GPU Deadline Missed");
+ }
+ if (jankType & JankType::AppDeadlineMissed) {
+ janks.emplace_back("App Deadline Missed");
+ }
+ if (jankType & JankType::PredictionError) {
+ janks.emplace_back("Prediction Error");
+ }
+ if (jankType & JankType::SurfaceFlingerScheduling) {
+ janks.emplace_back("SurfaceFlinger Scheduling");
+ }
+ if (jankType & JankType::BufferStuffing) {
+ janks.emplace_back("Buffer Stuffing");
+ }
+ if (jankType & JankType::Unknown) {
+ janks.emplace_back("Unknown jank");
+ }
+ return std::accumulate(janks.begin(), janks.end(), std::string(),
[](const std::string& l, const std::string& r) {
return l.empty() ? r : l + ", " + r;
});
}
-FrameTimelineEvent::PresentType presentTypeToProto(int32_t jankMetadata) {
- if (jankMetadata & EarlyPresent) {
- return FrameTimelineEvent::PRESENT_EARLY;
+std::string toString(FramePresentMetadata presentMetadata) {
+ switch (presentMetadata) {
+ case FramePresentMetadata::OnTimePresent:
+ return "On Time Present";
+ case FramePresentMetadata::LatePresent:
+ return "Late Present";
+ case FramePresentMetadata::EarlyPresent:
+ return "Early Present";
+ case FramePresentMetadata::UnknownPresent:
+ return "Unknown Present";
}
- if (jankMetadata & LatePresent) {
- return FrameTimelineEvent::PRESENT_LATE;
- }
- return FrameTimelineEvent::PRESENT_ON_TIME;
}
-FrameTimelineEvent::JankType JankTypeToProto(JankType jankType) {
+std::string toString(FrameReadyMetadata finishMetadata) {
+ switch (finishMetadata) {
+ case FrameReadyMetadata::OnTimeFinish:
+ return "On Time Finish";
+ case FrameReadyMetadata::LateFinish:
+ return "Late Finish";
+ case FrameReadyMetadata::UnknownFinish:
+ return "Unknown Finish";
+ }
+}
+
+std::string toString(FrameStartMetadata startMetadata) {
+ switch (startMetadata) {
+ case FrameStartMetadata::OnTimeStart:
+ return "On Time Start";
+ case FrameStartMetadata::LateStart:
+ return "Late Start";
+ case FrameStartMetadata::EarlyStart:
+ return "Early Start";
+ case FrameStartMetadata::UnknownStart:
+ return "Unknown Start";
+ }
+}
+
+std::string toString(SurfaceFrame::PresentState presentState) {
+ using PresentState = SurfaceFrame::PresentState;
+ switch (presentState) {
+ case PresentState::Presented:
+ return "Presented";
+ case PresentState::Dropped:
+ return "Dropped";
+ case PresentState::Unknown:
+ return "Unknown";
+ }
+}
+
+FrameTimelineEvent::PresentType toProto(FramePresentMetadata presentMetadata) {
+ switch (presentMetadata) {
+ case FramePresentMetadata::EarlyPresent:
+ return FrameTimelineEvent::PRESENT_EARLY;
+ case FramePresentMetadata::LatePresent:
+ return FrameTimelineEvent::PRESENT_LATE;
+ case FramePresentMetadata::OnTimePresent:
+ return FrameTimelineEvent::PRESENT_ON_TIME;
+ case FramePresentMetadata::UnknownPresent:
+ return FrameTimelineEvent::PRESENT_UNSPECIFIED;
+ }
+}
+
+FrameTimelineEvent::JankType jankTypeBitmaskToProto(int32_t jankType) {
+ // TODO(b/175843808): Either make the proto a bitmask or jankType an enum class
switch (jankType) {
case JankType::None:
return FrameTimelineEvent::JANK_NONE;
- case JankType::Display:
+ case JankType::DisplayHAL:
return FrameTimelineEvent::JANK_DISPLAY_HAL;
- case JankType::SurfaceFlingerDeadlineMissed:
+ case JankType::SurfaceFlingerCpuDeadlineMissed:
+ case JankType::SurfaceFlingerGpuDeadlineMissed:
return FrameTimelineEvent::JANK_SF_DEADLINE_MISSED;
case JankType::AppDeadlineMissed:
- case JankType::PredictionExpired:
+ case JankType::PredictionError:
return FrameTimelineEvent::JANK_APP_DEADLINE_MISSED;
+ case JankType::SurfaceFlingerScheduling:
+ return FrameTimelineEvent::JANK_SF_SCHEDULING;
+ case JankType::BufferStuffing:
+ return FrameTimelineEvent::JANK_BUFFER_STUFFING;
default:
+ // TODO(b/175843808): Remove default if JankType becomes an enum class
return FrameTimelineEvent::JANK_UNKNOWN;
}
}
-int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
- ATRACE_CALL();
- std::lock_guard<std::mutex> lock(mMutex);
- const int64_t assignedToken = mCurrentToken++;
- mPredictions[assignedToken] = predictions;
- mTokens.emplace_back(std::make_pair(assignedToken, systemTime()));
- flushTokens(systemTime());
- return assignedToken;
-}
-
-std::optional<TimelineItem> TokenManager::getPredictionsForToken(int64_t token) {
- std::lock_guard<std::mutex> lock(mMutex);
- flushTokens(systemTime());
- auto predictionsIterator = mPredictions.find(token);
- if (predictionsIterator != mPredictions.end()) {
- return predictionsIterator->second;
+// Returns the smallest timestamp from the set of predictions and actuals.
+nsecs_t getMinTime(PredictionState predictionState, TimelineItem predictions,
+ TimelineItem actuals) {
+ nsecs_t minTime = std::numeric_limits<nsecs_t>::max();
+ if (predictionState == PredictionState::Valid) {
+ // Checking start time for predictions is enough because start time is always lesser than
+ // endTime and presentTime.
+ minTime = std::min(minTime, predictions.startTime);
}
- return {};
-}
-void TokenManager::flushTokens(nsecs_t flushTime) {
- for (size_t i = 0; i < mTokens.size(); i++) {
- if (flushTime - mTokens[i].second >= kMaxRetentionTime) {
- mPredictions.erase(mTokens[i].first);
- mTokens.erase(mTokens.begin() + static_cast<int>(i));
- --i;
- } else {
- // Tokens are ordered by time. If i'th token is within the retention time, then the
- // i+1'th token will also be within retention time.
- break;
- }
+ // Need to check startTime, endTime and presentTime for actuals because some frames might not
+ // have them set.
+ if (actuals.startTime != 0) {
+ minTime = std::min(minTime, actuals.startTime);
}
+ if (actuals.endTime != 0) {
+ minTime = std::min(minTime, actuals.endTime);
+ }
+ if (actuals.presentTime != 0) {
+ minTime = std::min(minTime, actuals.endTime);
+ }
+ return minTime;
}
SurfaceFrame::SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
std::string debugName, PredictionState predictionState,
- frametimeline::TimelineItem&& predictions)
+ frametimeline::TimelineItem&& predictions,
+ std::shared_ptr<TimeStats> timeStats,
+ JankClassificationThresholds thresholds)
: mToken(token),
mOwnerPid(ownerPid),
mOwnerUid(ownerUid),
@@ -216,29 +253,8 @@
mPredictionState(predictionState),
mPredictions(predictions),
mActuals({0, 0, 0}),
- mActualQueueTime(0),
- mJankType(JankType::None),
- mJankMetadata(0) {}
-
-void SurfaceFrame::setPresentState(PresentState state) {
- std::lock_guard<std::mutex> lock(mMutex);
- mPresentState = state;
-}
-
-SurfaceFrame::PresentState SurfaceFrame::getPresentState() const {
- std::lock_guard<std::mutex> lock(mMutex);
- return mPresentState;
-}
-
-TimelineItem SurfaceFrame::getActuals() const {
- std::lock_guard<std::mutex> lock(mMutex);
- return mActuals;
-}
-
-nsecs_t SurfaceFrame::getActualQueueTime() const {
- std::lock_guard<std::mutex> lock(mMutex);
- return mActualQueueTime;
-}
+ mTimeStats(timeStats),
+ mJankClassificationThresholds(thresholds) {}
void SurfaceFrame::setActualStartTime(nsecs_t actualStartTime) {
std::lock_guard<std::mutex> lock(mMutex);
@@ -254,18 +270,13 @@
mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
}
-void SurfaceFrame::setActualPresentTime(nsecs_t presentTime) {
+void SurfaceFrame::setPresentState(PresentState presentState, nsecs_t lastLatchTime) {
std::lock_guard<std::mutex> lock(mMutex);
- mActuals.presentTime = presentTime;
+ mPresentState = presentState;
+ mLastLatchTime = lastLatchTime;
}
-void SurfaceFrame::setJankInfo(JankType jankType, int32_t jankMetadata) {
- std::lock_guard<std::mutex> lock(mMutex);
- mJankType = jankType;
- mJankMetadata = jankMetadata;
-}
-
-std::optional<JankType> SurfaceFrame::getJankType() const {
+std::optional<int32_t> SurfaceFrame::getJankType() const {
std::lock_guard<std::mutex> lock(mMutex);
if (mActuals.presentTime == 0) {
return std::nullopt;
@@ -275,31 +286,30 @@
nsecs_t SurfaceFrame::getBaseTime() const {
std::lock_guard<std::mutex> lock(mMutex);
- nsecs_t baseTime = std::numeric_limits<nsecs_t>::max();
- if (mPredictionState == PredictionState::Valid) {
- baseTime = std::min(baseTime, mPredictions.startTime);
- }
- if (mActuals.startTime != 0) {
- baseTime = std::min(baseTime, mActuals.startTime);
- }
- baseTime = std::min(baseTime, mActuals.endTime);
- return baseTime;
+ return getMinTime(mPredictionState, mPredictions, mActuals);
}
-std::string presentStateToString(SurfaceFrame::PresentState presentState) {
- using PresentState = SurfaceFrame::PresentState;
- switch (presentState) {
- case PresentState::Presented:
- return "Presented";
- case PresentState::Dropped:
- return "Dropped";
- case PresentState::Unknown:
- default:
- return "Unknown";
- }
+TimelineItem SurfaceFrame::getActuals() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mActuals;
}
-void SurfaceFrame::dump(std::string& result, const std::string& indent, nsecs_t baseTime) {
+SurfaceFrame::PresentState SurfaceFrame::getPresentState() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mPresentState;
+}
+
+FramePresentMetadata SurfaceFrame::getFramePresentMetadata() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mFramePresentMetadata;
+}
+
+FrameReadyMetadata SurfaceFrame::getFrameReadyMetadata() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mFrameReadyMetadata;
+}
+
+void SurfaceFrame::dump(std::string& result, const std::string& indent, nsecs_t baseTime) const {
std::lock_guard<std::mutex> lock(mMutex);
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Layer - %s", mDebugName.c_str());
@@ -309,21 +319,130 @@
}
StringAppendF(&result, "\n");
StringAppendF(&result, "%s", indent.c_str());
+ StringAppendF(&result, "Token: %" PRId64 "\n", mToken);
+ StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Owner Pid : %d\n", mOwnerPid);
StringAppendF(&result, "%s", indent.c_str());
- StringAppendF(&result, "Present State : %s\n", presentStateToString(mPresentState).c_str());
+ StringAppendF(&result, "Present State : %s\n", toString(mPresentState).c_str());
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Prediction State : %s\n", toString(mPredictionState).c_str());
StringAppendF(&result, "%s", indent.c_str());
- StringAppendF(&result, "Jank Type : %s\n", toString(mJankType).c_str());
+ StringAppendF(&result, "Jank Type : %s\n", jankTypeBitmaskToString(mJankType).c_str());
StringAppendF(&result, "%s", indent.c_str());
- StringAppendF(&result, "Jank Metadata: %s\n",
- jankMetadataBitmaskToString(mJankMetadata).c_str());
+ StringAppendF(&result, "Present Metadata : %s\n", toString(mFramePresentMetadata).c_str());
+ StringAppendF(&result, "%s", indent.c_str());
+ StringAppendF(&result, "Finish Metadata: %s\n", toString(mFrameReadyMetadata).c_str());
+ std::chrono::nanoseconds latchTime(
+ std::max(static_cast<int64_t>(0), mLastLatchTime - baseTime));
+ StringAppendF(&result, "%s", indent.c_str());
+ StringAppendF(&result, "Last latch time: %10f\n",
+ std::chrono::duration<double, std::milli>(latchTime).count());
+ if (mPredictionState == PredictionState::Valid) {
+ nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime;
+ std::chrono::nanoseconds presentDeltaNs(std::abs(presentDelta));
+ StringAppendF(&result, "%s", indent.c_str());
+ StringAppendF(&result, "Present delta: %10f\n",
+ std::chrono::duration<double, std::milli>(presentDeltaNs).count());
+ }
dumpTable(result, mPredictions, mActuals, indent, mPredictionState, baseTime);
}
-void SurfaceFrame::traceSurfaceFrame(int64_t displayFrameToken) {
- using FrameTimelineDataSource = FrameTimeline::FrameTimelineDataSource;
+void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType,
+ nsecs_t vsyncPeriod) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mPresentState != PresentState::Presented) {
+ // No need to update dropped buffers
+ return;
+ }
+
+ mActuals.presentTime = presentTime;
+ // Jank Analysis for SurfaceFrame
+ if (mPredictionState == PredictionState::None) {
+ // Cannot do jank classification on frames that don't have a token.
+ return;
+ }
+ if (mPredictionState == PredictionState::Expired) {
+ // We do not know what happened here to classify this correctly. This could
+ // potentially be AppDeadlineMissed but that's assuming no app will request frames
+ // 120ms apart.
+ mJankType = JankType::Unknown;
+ mFramePresentMetadata = FramePresentMetadata::UnknownPresent;
+ mFrameReadyMetadata = FrameReadyMetadata::UnknownFinish;
+ mTimeStats->incrementJankyFrames(mOwnerUid, mLayerName, mJankType);
+ return;
+ }
+
+ const nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime;
+ const nsecs_t deadlineDelta = mActuals.endTime - mPredictions.endTime;
+ const nsecs_t deltaToVsync = std::abs(presentDelta) % vsyncPeriod;
+
+ if (deadlineDelta > mJankClassificationThresholds.deadlineThreshold) {
+ mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
+ } else {
+ mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
+ }
+
+ if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
+ mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
+ : FramePresentMetadata::EarlyPresent;
+ } else {
+ mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
+ }
+
+ if (mFramePresentMetadata == FramePresentMetadata::OnTimePresent) {
+ // Frames presented on time are not janky.
+ mJankType = JankType::None;
+ } else if (mFramePresentMetadata == FramePresentMetadata::EarlyPresent) {
+ if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
+ // Finish on time, Present early
+ if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+ deltaToVsync >= vsyncPeriod - mJankClassificationThresholds.presentThreshold) {
+ // Delta factor of vsync
+ mJankType = JankType::SurfaceFlingerScheduling;
+ } else {
+ // Delta not a factor of vsync
+ mJankType = JankType::PredictionError;
+ }
+ } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
+ // Finish late, Present early
+ mJankType = JankType::Unknown;
+ }
+ } else {
+ if (mLastLatchTime != 0 && mPredictions.endTime <= mLastLatchTime) {
+ // Buffer Stuffing.
+ mJankType |= JankType::BufferStuffing;
+ }
+ if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
+ // Finish on time, Present late
+ if (displayFrameJankType != JankType::None) {
+ // Propagate displayFrame's jank if it exists
+ mJankType |= displayFrameJankType;
+ } else {
+ if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+ deltaToVsync >= vsyncPeriod - mJankClassificationThresholds.presentThreshold) {
+ // Delta factor of vsync
+ mJankType |= JankType::SurfaceFlingerScheduling;
+ } else {
+ // Delta not a factor of vsync
+ mJankType |= JankType::PredictionError;
+ }
+ }
+ } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
+ // Finish late, Present late
+ if (displayFrameJankType == JankType::None) {
+ // Display frame is not janky, so purely app's fault
+ mJankType |= JankType::AppDeadlineMissed;
+ } else {
+ // Propagate DisplayFrame's jankType if it is janky
+ mJankType |= displayFrameJankType;
+ }
+ }
+ }
+ mTimeStats->incrementJankyFrames(mOwnerUid, mLayerName, mJankType);
+}
+
+void SurfaceFrame::trace(int64_t displayFrameToken) {
+ using FrameTimelineDataSource = impl::FrameTimeline::FrameTimelineDataSource;
FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
std::lock_guard<std::mutex> lock(mMutex);
if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
@@ -349,11 +468,12 @@
} else if (mPresentState == PresentState::Unknown) {
surfaceFrameEvent->set_present_type(FrameTimelineEvent::PRESENT_UNSPECIFIED);
} else {
- surfaceFrameEvent->set_present_type(presentTypeToProto(mJankMetadata));
+ surfaceFrameEvent->set_present_type(toProto(mFramePresentMetadata));
}
- surfaceFrameEvent->set_on_time_finish(!(mJankMetadata & LateFinish));
- surfaceFrameEvent->set_gpu_composition(mJankMetadata & GpuComposition);
- surfaceFrameEvent->set_jank_type(JankTypeToProto(mJankType));
+ surfaceFrameEvent->set_on_time_finish(mFrameReadyMetadata ==
+ FrameReadyMetadata::OnTimeFinish);
+ surfaceFrameEvent->set_gpu_composition(mGpuComposition);
+ surfaceFrameEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
surfaceFrameEvent->set_expected_start_ns(mPredictions.startTime);
surfaceFrameEvent->set_expected_end_ns(mPredictions.endTime);
@@ -366,10 +486,45 @@
});
}
-FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
- : mCurrentDisplayFrame(std::make_shared<DisplayFrame>()),
+namespace impl {
+
+int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
+ ATRACE_CALL();
+ std::lock_guard<std::mutex> lock(mMutex);
+ const int64_t assignedToken = mCurrentToken++;
+ mPredictions[assignedToken] = {systemTime(), predictions};
+ flushTokens(systemTime());
+ return assignedToken;
+}
+
+std::optional<TimelineItem> TokenManager::getPredictionsForToken(int64_t token) const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto predictionsIterator = mPredictions.find(token);
+ if (predictionsIterator != mPredictions.end()) {
+ return predictionsIterator->second.predictions;
+ }
+ return {};
+}
+
+void TokenManager::flushTokens(nsecs_t flushTime) {
+ for (auto it = mPredictions.begin(); it != mPredictions.end();) {
+ if (flushTime - it->second.timestamp >= kMaxRetentionTime) {
+ it = mPredictions.erase(it);
+ } else {
+ // Tokens are ordered by time. If i'th token is within the retention time, then the
+ // i+1'th token will also be within retention time.
+ break;
+ }
+ }
+}
+
+FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
+ JankClassificationThresholds thresholds)
+ : mCurrentDisplayFrame(std::make_shared<DisplayFrame>(timeStats, thresholds)),
mMaxDisplayFrames(kDefaultMaxDisplayFrames),
- mTimeStats(std::move(timeStats)) {}
+ mTimeStats(std::move(timeStats)),
+ mSurfaceFlingerPid(surfaceFlingerPid),
+ mJankClassificationThresholds(thresholds) {}
void FrameTimeline::onBootFinished() {
perfetto::TracingInitArgs args;
@@ -384,67 +539,225 @@
FrameTimelineDataSource::Register(dsd);
}
-FrameTimeline::DisplayFrame::DisplayFrame()
- : surfaceFlingerPredictions(TimelineItem()), surfaceFlingerActuals(TimelineItem()) {
- this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
-}
-
-std::shared_ptr<android::frametimeline::SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
- pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
- std::optional<int64_t> token) {
+std::shared_ptr<SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
+ std::optional<int64_t> token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
+ std::string debugName) {
ATRACE_CALL();
if (!token) {
- return std::make_shared<impl::SurfaceFrame>(ISurfaceComposer::INVALID_VSYNC_ID,ownerPid,
- ownerUid, std::move(layerName),
- std::move(debugName), PredictionState::None,
- TimelineItem());
+ return std::make_shared<SurfaceFrame>(ISurfaceComposer::INVALID_VSYNC_ID, ownerPid,
+ ownerUid, std::move(layerName), std::move(debugName),
+ PredictionState::None, TimelineItem(), mTimeStats,
+ mJankClassificationThresholds);
}
std::optional<TimelineItem> predictions = mTokenManager.getPredictionsForToken(*token);
if (predictions) {
- return std::make_shared<impl::SurfaceFrame>(*token, ownerPid, ownerUid,
- std::move(layerName), std::move(debugName),
- PredictionState::Valid,
- std::move(*predictions));
+ return std::make_shared<SurfaceFrame>(*token, ownerPid, ownerUid, std::move(layerName),
+ std::move(debugName), PredictionState::Valid,
+ std::move(*predictions), mTimeStats,
+ mJankClassificationThresholds);
}
- return std::make_shared<impl::SurfaceFrame>(*token, ownerPid, ownerUid, std::move(layerName),
- std::move(debugName), PredictionState::Expired,
- TimelineItem());
+ return std::make_shared<SurfaceFrame>(*token, ownerPid, ownerUid, std::move(layerName),
+ std::move(debugName), PredictionState::Expired,
+ TimelineItem(), mTimeStats,
+ mJankClassificationThresholds);
}
-void FrameTimeline::addSurfaceFrame(
- std::shared_ptr<android::frametimeline::SurfaceFrame> surfaceFrame,
- SurfaceFrame::PresentState state) {
- ATRACE_CALL();
- surfaceFrame->setPresentState(state);
- std::lock_guard<std::mutex> lock(mMutex);
- mCurrentDisplayFrame->surfaceFrames.push_back(
- std::static_pointer_cast<impl::SurfaceFrame>(surfaceFrame));
+FrameTimeline::DisplayFrame::DisplayFrame(std::shared_ptr<TimeStats> timeStats,
+ JankClassificationThresholds thresholds)
+ : mSurfaceFlingerPredictions(TimelineItem()),
+ mSurfaceFlingerActuals(TimelineItem()),
+ mTimeStats(timeStats),
+ mJankClassificationThresholds(thresholds) {
+ mSurfaceFrames.reserve(kNumSurfaceFramesInitial);
}
-void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime) {
+void FrameTimeline::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
ATRACE_CALL();
- const std::optional<TimelineItem> prediction = mTokenManager.getPredictionsForToken(token);
std::lock_guard<std::mutex> lock(mMutex);
- mCurrentDisplayFrame->token = token;
- if (!prediction) {
- mCurrentDisplayFrame->predictionState = PredictionState::Expired;
- } else {
- mCurrentDisplayFrame->surfaceFlingerPredictions = *prediction;
- mCurrentDisplayFrame->predictionState = PredictionState::Valid;
- }
- mCurrentDisplayFrame->surfaceFlingerActuals.startTime = wakeUpTime;
+ mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
+}
+
+void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, nsecs_t vsyncPeriod) {
+ ATRACE_CALL();
+ std::lock_guard<std::mutex> lock(mMutex);
+ mCurrentDisplayFrame->onSfWakeUp(token, vsyncPeriod,
+ mTokenManager.getPredictionsForToken(token), wakeUpTime);
}
void FrameTimeline::setSfPresent(nsecs_t sfPresentTime,
const std::shared_ptr<FenceTime>& presentFence) {
ATRACE_CALL();
std::lock_guard<std::mutex> lock(mMutex);
- mCurrentDisplayFrame->surfaceFlingerActuals.endTime = sfPresentTime;
+ mCurrentDisplayFrame->setActualEndTime(sfPresentTime);
mPendingPresentFences.emplace_back(std::make_pair(presentFence, mCurrentDisplayFrame));
flushPendingPresentFences();
finalizeCurrentDisplayFrame();
}
+void FrameTimeline::DisplayFrame::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
+ mSurfaceFrames.push_back(surfaceFrame);
+}
+
+void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, nsecs_t vsyncPeriod,
+ std::optional<TimelineItem> predictions,
+ nsecs_t wakeUpTime) {
+ mToken = token;
+ mVsyncPeriod = vsyncPeriod;
+ if (!predictions) {
+ mPredictionState = PredictionState::Expired;
+ } else {
+ mPredictionState = PredictionState::Valid;
+ mSurfaceFlingerPredictions = *predictions;
+ }
+ mSurfaceFlingerActuals.startTime = wakeUpTime;
+}
+
+void FrameTimeline::DisplayFrame::setTokenAndVsyncPeriod(int64_t token, nsecs_t vsyncPeriod) {
+ mToken = token;
+ mVsyncPeriod = vsyncPeriod;
+}
+
+void FrameTimeline::DisplayFrame::setPredictions(PredictionState predictionState,
+ TimelineItem predictions) {
+ mPredictionState = predictionState;
+ mSurfaceFlingerPredictions = predictions;
+}
+
+void FrameTimeline::DisplayFrame::setActualStartTime(nsecs_t actualStartTime) {
+ mSurfaceFlingerActuals.startTime = actualStartTime;
+}
+
+void FrameTimeline::DisplayFrame::setActualEndTime(nsecs_t actualEndTime) {
+ mSurfaceFlingerActuals.endTime = actualEndTime;
+}
+
+void FrameTimeline::DisplayFrame::onPresent(nsecs_t signalTime) {
+ mSurfaceFlingerActuals.presentTime = signalTime;
+ int32_t totalJankReasons = JankType::None;
+
+ // Delta between the expected present and the actual present
+ const nsecs_t presentDelta =
+ mSurfaceFlingerActuals.presentTime - mSurfaceFlingerPredictions.presentTime;
+ // How far off was the presentDelta when compared to the vsyncPeriod. Used in checking if there
+ // was a prediction error or not.
+ nsecs_t deltaToVsync = std::abs(presentDelta) % mVsyncPeriod;
+ if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
+ mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
+ : FramePresentMetadata::EarlyPresent;
+ } else {
+ mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
+ }
+
+ if (mSurfaceFlingerActuals.endTime - mSurfaceFlingerPredictions.endTime >
+ mJankClassificationThresholds.deadlineThreshold) {
+ mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
+ } else {
+ mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
+ }
+
+ if (std::abs(mSurfaceFlingerActuals.startTime - mSurfaceFlingerPredictions.startTime) >
+ mJankClassificationThresholds.startThreshold) {
+ mFrameStartMetadata =
+ mSurfaceFlingerActuals.startTime > mSurfaceFlingerPredictions.startTime
+ ? FrameStartMetadata::LateStart
+ : FrameStartMetadata::EarlyStart;
+ }
+
+ if (mFramePresentMetadata != FramePresentMetadata::OnTimePresent) {
+ // Do jank classification only if present is not on time
+ if (mFramePresentMetadata == FramePresentMetadata::EarlyPresent) {
+ if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
+ // Finish on time, Present early
+ if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+ deltaToVsync >=
+ (mVsyncPeriod - mJankClassificationThresholds.presentThreshold)) {
+ // Delta is a factor of vsync if its within the presentTheshold on either side
+ // of the vsyncPeriod. Example: 0-2ms and 9-11ms are both within the threshold
+ // of the vsyncPeriod if the threshold was 2ms and the vsyncPeriod was 11ms.
+ mJankType = JankType::SurfaceFlingerScheduling;
+ } else {
+ // Delta is not a factor of vsync,
+ mJankType = JankType::PredictionError;
+ }
+ } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
+ // Finish late, Present early
+ mJankType = JankType::SurfaceFlingerScheduling;
+ } else {
+ // Finish time unknown
+ mJankType = JankType::Unknown;
+ }
+ } else if (mFramePresentMetadata == FramePresentMetadata::LatePresent) {
+ if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
+ // Finish on time, Present late
+ if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+ deltaToVsync >=
+ (mVsyncPeriod - mJankClassificationThresholds.presentThreshold)) {
+ // Delta is a factor of vsync if its within the presentTheshold on either side
+ // of the vsyncPeriod. Example: 0-2ms and 9-11ms are both within the threshold
+ // of the vsyncPeriod if the threshold was 2ms and the vsyncPeriod was 11ms.
+ mJankType = JankType::DisplayHAL;
+ } else {
+ // Delta is not a factor of vsync
+ mJankType = JankType::PredictionError;
+ }
+ } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
+ // Finish late, Present late
+ mJankType = JankType::SurfaceFlingerCpuDeadlineMissed;
+ } else {
+ // Finish time unknown
+ mJankType = JankType::Unknown;
+ }
+ } else {
+ // Present unknown
+ mJankType = JankType::Unknown;
+ }
+ }
+ totalJankReasons |= mJankType;
+
+ for (auto& surfaceFrame : mSurfaceFrames) {
+ surfaceFrame->onPresent(signalTime, mJankType, mVsyncPeriod);
+ auto surfaceFrameJankType = surfaceFrame->getJankType();
+ if (surfaceFrameJankType != std::nullopt) {
+ totalJankReasons |= *surfaceFrameJankType;
+ }
+ }
+ mTimeStats->incrementJankyFrames(totalJankReasons);
+}
+
+void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid) const {
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace DisplayFrame with invalid token");
+ return;
+ }
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(systemTime()));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* displayFrameEvent = event->set_display_frame();
+
+ displayFrameEvent->set_token(mToken);
+ displayFrameEvent->set_present_type(toProto(mFramePresentMetadata));
+ displayFrameEvent->set_on_time_finish(mFrameReadyMetadata ==
+ FrameReadyMetadata::OnTimeFinish);
+ displayFrameEvent->set_gpu_composition(mGpuComposition);
+ displayFrameEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+
+ displayFrameEvent->set_expected_start_ns(mSurfaceFlingerPredictions.startTime);
+ displayFrameEvent->set_expected_end_ns(mSurfaceFlingerPredictions.endTime);
+
+ displayFrameEvent->set_actual_start_ns(mSurfaceFlingerActuals.startTime);
+ displayFrameEvent->set_actual_end_ns(mSurfaceFlingerActuals.endTime);
+
+ displayFrameEvent->set_pid(surfaceFlingerPid);
+ });
+
+ for (auto& surfaceFrame : mSurfaceFrames) {
+ surfaceFrame->trace(mToken);
+ }
+}
+
void FrameTimeline::flushPendingPresentFences() {
for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
const auto& pendingPresentFence = mPendingPresentFences[i];
@@ -456,93 +769,9 @@
}
}
if (signalTime != Fence::SIGNAL_TIME_INVALID) {
- int32_t totalJankReasons = JankType::None;
auto& displayFrame = pendingPresentFence.second;
- displayFrame->surfaceFlingerActuals.presentTime = signalTime;
-
- // Jank Analysis for DisplayFrame
- const auto& sfActuals = displayFrame->surfaceFlingerActuals;
- const auto& sfPredictions = displayFrame->surfaceFlingerPredictions;
- if (std::abs(sfActuals.presentTime - sfPredictions.presentTime) > kPresentThreshold) {
- displayFrame->jankMetadata |= sfActuals.presentTime > sfPredictions.presentTime
- ? LatePresent
- : EarlyPresent;
- }
- if (std::abs(sfActuals.endTime - sfPredictions.endTime) > kDeadlineThreshold) {
- if (sfActuals.endTime > sfPredictions.endTime) {
- displayFrame->jankMetadata |= LateFinish;
- } else {
- displayFrame->jankMetadata |= EarlyFinish;
- }
-
- if ((displayFrame->jankMetadata & EarlyFinish) &&
- (displayFrame->jankMetadata & EarlyPresent)) {
- displayFrame->jankType = JankType::SurfaceFlingerEarlyLatch;
- } else if ((displayFrame->jankMetadata & LateFinish) &&
- (displayFrame->jankMetadata & LatePresent)) {
- displayFrame->jankType = JankType::SurfaceFlingerDeadlineMissed;
- } else if (displayFrame->jankMetadata & EarlyPresent ||
- displayFrame->jankMetadata & LatePresent) {
- // Cases where SF finished early but frame was presented late and vice versa
- displayFrame->jankType = JankType::Display;
- }
- }
-
- if (std::abs(sfActuals.startTime - sfPredictions.startTime) > kSFStartThreshold) {
- displayFrame->jankMetadata |=
- sfActuals.startTime > sfPredictions.startTime ? LateStart : EarlyStart;
- }
-
- totalJankReasons |= displayFrame->jankType;
- traceDisplayFrame(*displayFrame);
-
- for (auto& surfaceFrame : displayFrame->surfaceFrames) {
- if (surfaceFrame->getPresentState() == SurfaceFrame::PresentState::Presented) {
- // Only presented SurfaceFrames need to be updated
- surfaceFrame->setActualPresentTime(signalTime);
-
- // Jank Analysis for SurfaceFrame
- const auto& predictionState = surfaceFrame->getPredictionState();
- if (predictionState == PredictionState::Expired) {
- // Jank analysis cannot be done on apps that don't use predictions
- surfaceFrame->setJankInfo(JankType::PredictionExpired, 0);
- } else if (predictionState == PredictionState::Valid) {
- const auto& actuals = surfaceFrame->getActuals();
- const auto& predictions = surfaceFrame->getPredictions();
- int32_t jankMetadata = 0;
- JankType jankType = JankType::None;
- if (std::abs(actuals.endTime - predictions.endTime) > kDeadlineThreshold) {
- jankMetadata |= actuals.endTime > predictions.endTime ? LateFinish
- : EarlyFinish;
- }
- if (std::abs(actuals.presentTime - predictions.presentTime) >
- kPresentThreshold) {
- jankMetadata |= actuals.presentTime > predictions.presentTime
- ? LatePresent
- : EarlyPresent;
- }
- if (jankMetadata & EarlyPresent) {
- jankType = JankType::SurfaceFlingerEarlyLatch;
- } else if (jankMetadata & LatePresent) {
- if (jankMetadata & EarlyFinish) {
- // TODO(b/169890654): Classify this properly
- jankType = JankType::Display;
- } else {
- jankType = JankType::AppDeadlineMissed;
- }
- }
-
- totalJankReasons |= jankType;
- mTimeStats->incrementJankyFrames(surfaceFrame->getOwnerUid(),
- surfaceFrame->getName(),
- jankType | displayFrame->jankType);
- surfaceFrame->setJankInfo(jankType, jankMetadata);
- }
- }
- surfaceFrame->traceSurfaceFrame(displayFrame->token);
- }
-
- mTimeStats->incrementJankyFrames(totalJankReasons);
+ displayFrame->onPresent(signalTime);
+ displayFrame->trace(mSurfaceFlingerPid);
}
mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
@@ -557,16 +786,14 @@
}
mDisplayFrames.push_back(mCurrentDisplayFrame);
mCurrentDisplayFrame.reset();
- mCurrentDisplayFrame = std::make_shared<DisplayFrame>();
+ mCurrentDisplayFrame =
+ std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds);
}
-nsecs_t FrameTimeline::findBaseTime(const std::shared_ptr<DisplayFrame>& displayFrame) {
- nsecs_t baseTime = std::numeric_limits<nsecs_t>::max();
- if (displayFrame->predictionState == PredictionState::Valid) {
- baseTime = std::min(baseTime, displayFrame->surfaceFlingerPredictions.startTime);
- }
- baseTime = std::min(baseTime, displayFrame->surfaceFlingerActuals.startTime);
- for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
+nsecs_t FrameTimeline::DisplayFrame::getBaseTime() const {
+ nsecs_t baseTime =
+ getMinTime(mPredictionState, mSurfaceFlingerPredictions, mSurfaceFlingerActuals);
+ for (const auto& surfaceFrame : mSurfaceFrames) {
nsecs_t surfaceFrameBaseTime = surfaceFrame->getBaseTime();
if (surfaceFrameBaseTime != 0) {
baseTime = std::min(baseTime, surfaceFrameBaseTime);
@@ -575,60 +802,79 @@
return baseTime;
}
-void FrameTimeline::dumpDisplayFrame(std::string& result,
- const std::shared_ptr<DisplayFrame>& displayFrame,
- nsecs_t baseTime) {
- if (displayFrame->jankType != JankType::None) {
+void FrameTimeline::DisplayFrame::dumpJank(std::string& result, nsecs_t baseTime,
+ int displayFrameCount) const {
+ if (mJankType == JankType::None) {
+ // Check if any Surface Frame has been janky
+ bool isJanky = false;
+ for (const auto& surfaceFrame : mSurfaceFrames) {
+ if (surfaceFrame->getJankType() != JankType::None) {
+ isJanky = true;
+ break;
+ }
+ }
+ if (!isJanky) {
+ return;
+ }
+ }
+ StringAppendF(&result, "Display Frame %d", displayFrameCount);
+ dump(result, baseTime);
+}
+
+void FrameTimeline::DisplayFrame::dumpAll(std::string& result, nsecs_t baseTime) const {
+ dump(result, baseTime);
+}
+
+void FrameTimeline::DisplayFrame::dump(std::string& result, nsecs_t baseTime) const {
+ if (mJankType != JankType::None) {
// Easily identify a janky Display Frame in the dump
StringAppendF(&result, " [*] ");
}
StringAppendF(&result, "\n");
- StringAppendF(&result, "Prediction State : %s\n",
- toString(displayFrame->predictionState).c_str());
- StringAppendF(&result, "Jank Type : %s\n", toString(displayFrame->jankType).c_str());
- StringAppendF(&result, "Jank Metadata: %s\n",
- jankMetadataBitmaskToString(displayFrame->jankMetadata).c_str());
- dumpTable(result, displayFrame->surfaceFlingerPredictions, displayFrame->surfaceFlingerActuals,
- "", displayFrame->predictionState, baseTime);
+ StringAppendF(&result, "Prediction State : %s\n", toString(mPredictionState).c_str());
+ StringAppendF(&result, "Jank Type : %s\n", jankTypeBitmaskToString(mJankType).c_str());
+ StringAppendF(&result, "Present Metadata : %s\n", toString(mFramePresentMetadata).c_str());
+ StringAppendF(&result, "Finish Metadata: %s\n", toString(mFrameReadyMetadata).c_str());
+ StringAppendF(&result, "Start Metadata: %s\n", toString(mFrameStartMetadata).c_str());
+ std::chrono::nanoseconds vsyncPeriod(mVsyncPeriod);
+ StringAppendF(&result, "Vsync Period: %10f\n",
+ std::chrono::duration<double, std::milli>(vsyncPeriod).count());
+ nsecs_t presentDelta =
+ mSurfaceFlingerActuals.presentTime - mSurfaceFlingerPredictions.presentTime;
+ std::chrono::nanoseconds presentDeltaNs(std::abs(presentDelta));
+ StringAppendF(&result, "Present delta: %10f\n",
+ std::chrono::duration<double, std::milli>(presentDeltaNs).count());
+ std::chrono::nanoseconds deltaToVsync(std::abs(presentDelta) % mVsyncPeriod);
+ StringAppendF(&result, "Present delta %% refreshrate: %10f\n",
+ std::chrono::duration<double, std::milli>(deltaToVsync).count());
+ dumpTable(result, mSurfaceFlingerPredictions, mSurfaceFlingerActuals, "", mPredictionState,
+ baseTime);
StringAppendF(&result, "\n");
std::string indent = " "; // 4 spaces
- for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
+ for (const auto& surfaceFrame : mSurfaceFrames) {
surfaceFrame->dump(result, indent, baseTime);
}
StringAppendF(&result, "\n");
}
+
void FrameTimeline::dumpAll(std::string& result) {
std::lock_guard<std::mutex> lock(mMutex);
StringAppendF(&result, "Number of display frames : %d\n", (int)mDisplayFrames.size());
- nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : findBaseTime(mDisplayFrames[0]);
+ nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : mDisplayFrames[0]->getBaseTime();
for (size_t i = 0; i < mDisplayFrames.size(); i++) {
StringAppendF(&result, "Display Frame %d", static_cast<int>(i));
- dumpDisplayFrame(result, mDisplayFrames[i], baseTime);
+ mDisplayFrames[i]->dumpAll(result, baseTime);
}
}
void FrameTimeline::dumpJank(std::string& result) {
std::lock_guard<std::mutex> lock(mMutex);
- nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : findBaseTime(mDisplayFrames[0]);
+ nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : mDisplayFrames[0]->getBaseTime();
for (size_t i = 0; i < mDisplayFrames.size(); i++) {
- const auto& displayFrame = mDisplayFrames[i];
- if (displayFrame->jankType == JankType::None) {
- // Check if any Surface Frame has been janky
- bool isJanky = false;
- for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
- if (surfaceFrame->getJankType() != JankType::None) {
- isJanky = true;
- break;
- }
- }
- if (!isJanky) {
- continue;
- }
- }
- StringAppendF(&result, "Display Frame %d", static_cast<int>(i));
- dumpDisplayFrame(result, displayFrame, baseTime);
+ mDisplayFrames[i]->dumpJank(result, baseTime, static_cast<int>(i));
}
}
+
void FrameTimeline::parseArgs(const Vector<String16>& args, std::string& result) {
ATRACE_CALL();
std::unordered_map<std::string, bool> argsMap;
@@ -656,31 +902,5 @@
setMaxDisplayFrames(kDefaultMaxDisplayFrames);
}
-void FrameTimeline::traceDisplayFrame(const DisplayFrame& displayFrame) {
- FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
- if (displayFrame.token == ISurfaceComposer::INVALID_VSYNC_ID) {
- ALOGD("Cannot trace DisplayFrame with invalid token");
- return;
- }
- auto packet = ctx.NewTracePacket();
- packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
- packet->set_timestamp(static_cast<uint64_t>(systemTime()));
-
- auto* event = packet->set_frame_timeline_event();
- auto* displayFrameEvent = event->set_display_frame();
-
- displayFrameEvent->set_token(displayFrame.token);
- displayFrameEvent->set_present_type(presentTypeToProto(displayFrame.jankMetadata));
- displayFrameEvent->set_on_time_finish(!(displayFrame.jankMetadata & LateFinish));
- displayFrameEvent->set_gpu_composition(displayFrame.jankMetadata & GpuComposition);
- displayFrameEvent->set_jank_type(JankTypeToProto(displayFrame.jankType));
-
- displayFrameEvent->set_expected_start_ns(displayFrame.surfaceFlingerPredictions.startTime);
- displayFrameEvent->set_expected_end_ns(displayFrame.surfaceFlingerPredictions.endTime);
-
- displayFrameEvent->set_actual_start_ns(displayFrame.surfaceFlingerActuals.startTime);
- displayFrameEvent->set_actual_end_ns(displayFrame.surfaceFlingerActuals.endTime);
- });
-}
-
-} // namespace android::frametimeline::impl
+} // namespace impl
+} // namespace android::frametimeline
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 084935b..f5239aa 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -32,24 +32,44 @@
namespace android::frametimeline {
-enum JankMetadata {
- // Frame was presented earlier than expected
- EarlyPresent = 0x1,
- // Frame was presented later than expected
- LatePresent = 0x2,
- // App/SF started earlier than expected
- EarlyStart = 0x4,
- // App/SF started later than expected
- LateStart = 0x8,
- // App/SF finished work earlier than the deadline
- EarlyFinish = 0x10,
- // App/SF finished work later than the deadline
- LateFinish = 0x20,
- // SF was in GPU composition
- GpuComposition = 0x40,
+class FrameTimelineTest;
+
+using namespace std::chrono_literals;
+
+// Metadata indicating how the frame was presented w.r.t expected present time.
+enum class FramePresentMetadata : int8_t {
+ // Frame was presented on time
+ OnTimePresent,
+ // Frame was presented late
+ LatePresent,
+ // Frame was presented early
+ EarlyPresent,
+ // Unknown/initial state
+ UnknownPresent,
};
-class FrameTimelineTest;
+// Metadata comparing the frame's actual finish time to the expected deadline.
+enum class FrameReadyMetadata : int8_t {
+ // App/SF finished on time. Early finish is treated as on time since the goal of any component
+ // is to finish before the deadline.
+ OnTimeFinish,
+ // App/SF finished work later than expected
+ LateFinish,
+ // Unknown/initial state
+ UnknownFinish,
+};
+
+// Metadata comparing the frame's actual start time to the expected start time.
+enum class FrameStartMetadata : int8_t {
+ // App/SF started on time
+ OnTimeStart,
+ // App/SF started later than expected
+ LateStart,
+ // App/SF started earlier than expected
+ EarlyStart,
+ // Unknown/initial state
+ UnknownStart,
+};
/*
* Collection of timestamps that can be used for both predictions and actual times.
@@ -71,6 +91,19 @@
bool operator!=(const TimelineItem& other) const { return !(*this == other); }
};
+struct TokenManagerPrediction {
+ nsecs_t timestamp = 0;
+ TimelineItem predictions;
+};
+
+struct JankClassificationThresholds {
+ // The various thresholds for App and SF. If the actual timestamp falls within the threshold
+ // compared to prediction, we treat it as on time.
+ nsecs_t presentThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+ nsecs_t deadlineThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+ nsecs_t startThreshold = std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
+};
+
/*
* TokenManager generates a running number token for a set of predictions made by VsyncPredictor. It
* saves these predictions for a short period of time and returns the predictions for a given token,
@@ -83,6 +116,9 @@
// Generates a token for the given set of predictions. Stores the predictions for 120ms and
// destroys it later.
virtual int64_t generateTokenForPredictions(TimelineItem&& prediction) = 0;
+
+ // Returns the stored predictions for a given token, if the predictions haven't expired.
+ virtual std::optional<TimelineItem> getPredictionsForToken(int64_t token) const = 0;
};
enum class PredictionState {
@@ -91,10 +127,6 @@
None, // Predictions are either not present or didn't come from TokenManager
};
-/*
- * Stores a set of predictions and the corresponding actual timestamps pertaining to a single frame
- * from the app
- */
class SurfaceFrame {
public:
enum class PresentState {
@@ -103,29 +135,76 @@
Unknown, // Initial state, SurfaceFlinger hasn't seen this buffer yet
};
- virtual ~SurfaceFrame() = default;
+ // Only FrameTimeline can construct a SurfaceFrame as it provides Predictions(through
+ // TokenManager), Thresholds and TimeStats pointer.
+ SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
+ std::string debugName, PredictionState predictionState, TimelineItem&& predictions,
+ std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds);
+ ~SurfaceFrame() = default;
- virtual TimelineItem getPredictions() const = 0;
- virtual TimelineItem getActuals() const = 0;
- virtual nsecs_t getActualQueueTime() const = 0;
- virtual PresentState getPresentState() const = 0;
- virtual PredictionState getPredictionState() const = 0;
- virtual pid_t getOwnerPid() const = 0;
+ // Returns std::nullopt if the frame hasn't been classified yet.
+ // Used by both SF and FrameTimeline.
+ std::optional<int32_t> getJankType() const;
- virtual void setPresentState(PresentState state) = 0;
-
+ // Functions called by SF
+ int64_t getToken() const { return mToken; };
+ TimelineItem getPredictions() const { return mPredictions; };
// Actual timestamps of the app are set individually at different functions.
// Start time (if the app provides) and Queue time are accessible after queueing the frame,
// whereas Acquire Fence time is available only during latch.
- virtual void setActualStartTime(nsecs_t actualStartTime) = 0;
- virtual void setActualQueueTime(nsecs_t actualQueueTime) = 0;
- virtual void setAcquireFenceTime(nsecs_t acquireFenceTime) = 0;
+ void setActualStartTime(nsecs_t actualStartTime);
+ void setActualQueueTime(nsecs_t actualQueueTime);
+ void setAcquireFenceTime(nsecs_t acquireFenceTime);
+ void setPresentState(PresentState presentState, nsecs_t lastLatchTime = 0);
- // Retrieves jank classification, if it's already been classified.
- virtual std::optional<JankType> getJankType() const = 0;
+ // Functions called by FrameTimeline
+ // BaseTime is the smallest timestamp in this SurfaceFrame.
+ // Used for dumping all timestamps relative to the oldest, making it easy to read.
+ nsecs_t getBaseTime() const;
+ // Sets the actual present time, appropriate metadata and classifies the jank.
+ void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, nsecs_t vsyncPeriod);
+ // All the timestamps are dumped relative to the baseTime
+ void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
+ // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
+ // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
+ // DisplayFrame at the trace processor side.
+ void trace(int64_t displayFrameToken);
- // Token identifying the frame.
- virtual int64_t getToken() const = 0;
+ // Getter functions used only by FrameTimelineTests
+ TimelineItem getActuals() const;
+ pid_t getOwnerPid() const { return mOwnerPid; };
+ PredictionState getPredictionState() const { return mPredictionState; };
+ PresentState getPresentState() const;
+ FrameReadyMetadata getFrameReadyMetadata() const;
+ FramePresentMetadata getFramePresentMetadata() const;
+
+private:
+ const int64_t mToken;
+ const pid_t mOwnerPid;
+ const uid_t mOwnerUid;
+ const std::string mLayerName;
+ const std::string mDebugName;
+ PresentState mPresentState GUARDED_BY(mMutex);
+ const PredictionState mPredictionState;
+ const TimelineItem mPredictions;
+ TimelineItem mActuals GUARDED_BY(mMutex);
+ std::shared_ptr<TimeStats> mTimeStats;
+ const JankClassificationThresholds mJankClassificationThresholds;
+ nsecs_t mActualQueueTime GUARDED_BY(mMutex) = 0;
+ mutable std::mutex mMutex;
+ // Bitmask for the type of jank
+ int32_t mJankType GUARDED_BY(mMutex) = JankType::None;
+ // Indicates if this frame was composited by the GPU or not
+ bool mGpuComposition GUARDED_BY(mMutex) = false;
+ // Enum for the type of present
+ FramePresentMetadata mFramePresentMetadata GUARDED_BY(mMutex) =
+ FramePresentMetadata::UnknownPresent;
+ // Enum for the type of finish
+ FrameReadyMetadata mFrameReadyMetadata GUARDED_BY(mMutex) = FrameReadyMetadata::UnknownFinish;
+ // Time when the previous buffer from the same layer was latched by SF. This is used in checking
+ // for BufferStuffing where the current buffer is expected to be ready but the previous buffer
+ // was latched instead.
+ nsecs_t mLastLatchTime GUARDED_BY(mMutex) = 0;
};
/*
@@ -142,20 +221,19 @@
virtual void onBootFinished() = 0;
// Create a new surface frame, set the predictions based on a token and return it to the caller.
- // Sets the PredictionState of SurfaceFrame.
// Debug name is the human-readable debugging string for dumpsys.
- virtual std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(
- pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
- std::optional<int64_t> token) = 0;
+ virtual std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(std::optional<int64_t> token,
+ pid_t ownerPid, uid_t ownerUid,
+ std::string layerName,
+ std::string debugName) = 0;
// Adds a new SurfaceFrame to the current DisplayFrame. Frames from multiple layers can be
// composited into one display frame.
- virtual void addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame,
- SurfaceFrame::PresentState state) = 0;
+ virtual void addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) = 0;
// The first function called by SF for the current DisplayFrame. Fetches SF predictions based on
// the token and sets the actualSfWakeTime for the current DisplayFrame.
- virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime) = 0;
+ virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, nsecs_t vsyncPeriod) = 0;
// Sets the sfPresentTime and finalizes the current DisplayFrame. Tracks the given present fence
// until it's signaled, and updates the present timestamps of all presented SurfaceFrames in
@@ -178,15 +256,13 @@
namespace impl {
-using namespace std::chrono_literals;
-
class TokenManager : public android::frametimeline::TokenManager {
public:
TokenManager() : mCurrentToken(ISurfaceComposer::INVALID_VSYNC_ID + 1) {}
~TokenManager() = default;
int64_t generateTokenForPredictions(TimelineItem&& predictions) override;
- std::optional<TimelineItem> getPredictionsForToken(int64_t token);
+ std::optional<TimelineItem> getPredictionsForToken(int64_t token) const override;
private:
// Friend class for testing
@@ -194,64 +270,13 @@
void flushTokens(nsecs_t flushTime) REQUIRES(mMutex);
- std::unordered_map<int64_t, TimelineItem> mPredictions GUARDED_BY(mMutex);
- std::vector<std::pair<int64_t, nsecs_t>> mTokens GUARDED_BY(mMutex);
+ std::map<int64_t, TokenManagerPrediction> mPredictions GUARDED_BY(mMutex);
int64_t mCurrentToken GUARDED_BY(mMutex);
- std::mutex mMutex;
+ mutable std::mutex mMutex;
static constexpr nsecs_t kMaxRetentionTime =
std::chrono::duration_cast<std::chrono::nanoseconds>(120ms).count();
};
-class SurfaceFrame : public android::frametimeline::SurfaceFrame {
-public:
- SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
- std::string debugName, PredictionState predictionState,
- TimelineItem&& predictions);
- ~SurfaceFrame() = default;
-
- TimelineItem getPredictions() const override { return mPredictions; };
- TimelineItem getActuals() const override;
- nsecs_t getActualQueueTime() const override;
- PresentState getPresentState() const override;
- PredictionState getPredictionState() const override { return mPredictionState; };
- pid_t getOwnerPid() const override { return mOwnerPid; };
- std::optional<JankType> getJankType() const override;
- int64_t getToken() const override { return mToken; };
- nsecs_t getBaseTime() const;
- uid_t getOwnerUid() const { return mOwnerUid; };
- const std::string& getName() const { return mLayerName; };
-
- void setActualStartTime(nsecs_t actualStartTime) override;
- void setActualQueueTime(nsecs_t actualQueueTime) override;
- void setAcquireFenceTime(nsecs_t acquireFenceTime) override;
- void setPresentState(PresentState state) override;
- void setActualPresentTime(nsecs_t presentTime);
- void setJankInfo(JankType jankType, int32_t jankMetadata);
-
- // All the timestamps are dumped relative to the baseTime
- void dump(std::string& result, const std::string& indent, nsecs_t baseTime);
-
- // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
- // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
- // DisplayFrame at the trace processor side.
- void traceSurfaceFrame(int64_t displayFrameToken);
-
-private:
- const int64_t mToken;
- const pid_t mOwnerPid;
- const uid_t mOwnerUid;
- const std::string mLayerName;
- const std::string mDebugName;
- PresentState mPresentState GUARDED_BY(mMutex);
- const PredictionState mPredictionState;
- const TimelineItem mPredictions;
- TimelineItem mActuals GUARDED_BY(mMutex);
- nsecs_t mActualQueueTime GUARDED_BY(mMutex);
- mutable std::mutex mMutex;
- JankType mJankType GUARDED_BY(mMutex); // Enum for the type of jank
- int32_t mJankMetadata GUARDED_BY(mMutex); // Additional details about the jank
-};
-
class FrameTimeline : public android::frametimeline::FrameTimeline {
public:
class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
@@ -260,16 +285,94 @@
void OnStop(const StopArgs&) override{};
};
- FrameTimeline(std::shared_ptr<TimeStats> timeStats);
+ /*
+ * DisplayFrame should be used only internally within FrameTimeline. All members and methods are
+ * guarded by FrameTimeline's mMutex.
+ */
+ class DisplayFrame {
+ public:
+ DisplayFrame(std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds);
+ virtual ~DisplayFrame() = default;
+ // Dumpsys interface - dumps only if the DisplayFrame itself is janky or is at least one
+ // SurfaceFrame is janky.
+ void dumpJank(std::string& result, nsecs_t baseTime, int displayFrameCount) const;
+ // Dumpsys interface - dumps all data irrespective of jank
+ void dumpAll(std::string& result, nsecs_t baseTime) const;
+ // Emits a packet for perfetto tracing. The function body will be executed only if tracing
+ // is enabled.
+ void trace(pid_t surfaceFlingerPid) const;
+ // Sets the token, vsyncPeriod, predictions and SF start time.
+ void onSfWakeUp(int64_t token, nsecs_t vsyncPeriod, std::optional<TimelineItem> predictions,
+ nsecs_t wakeUpTime);
+ // Sets the appropriate metadata, classifies the jank and returns the classified jankType.
+ void onPresent(nsecs_t signalTime);
+ // Adds the provided SurfaceFrame to the current display frame.
+ void addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame);
+
+ void setTokenAndVsyncPeriod(int64_t token, nsecs_t vsyncPeriod);
+ void setPredictions(PredictionState predictionState, TimelineItem predictions);
+ void setActualStartTime(nsecs_t actualStartTime);
+ void setActualEndTime(nsecs_t actualEndTime);
+
+ // BaseTime is the smallest timestamp in a DisplayFrame.
+ // Used for dumping all timestamps relative to the oldest, making it easy to read.
+ nsecs_t getBaseTime() const;
+
+ // Functions to be used only in testing.
+ TimelineItem getActuals() const { return mSurfaceFlingerActuals; };
+ TimelineItem getPredictions() const { return mSurfaceFlingerPredictions; };
+ FramePresentMetadata getFramePresentMetadata() const { return mFramePresentMetadata; };
+ FrameReadyMetadata getFrameReadyMetadata() const { return mFrameReadyMetadata; };
+ int32_t getJankType() const { return mJankType; }
+ const std::vector<std::shared_ptr<SurfaceFrame>>& getSurfaceFrames() const {
+ return mSurfaceFrames;
+ }
+
+ private:
+ void dump(std::string& result, nsecs_t baseTime) const;
+
+ int64_t mToken = ISurfaceComposer::INVALID_VSYNC_ID;
+
+ /* Usage of TimelineItem w.r.t SurfaceFlinger
+ * startTime Time when SurfaceFlinger wakes up to handle transactions and buffer updates
+ * endTime Time when SurfaceFlinger sends a composited frame to Display
+ * presentTime Time when the composited frame was presented on screen
+ */
+ TimelineItem mSurfaceFlingerPredictions;
+ TimelineItem mSurfaceFlingerActuals;
+ std::shared_ptr<TimeStats> mTimeStats;
+ const JankClassificationThresholds mJankClassificationThresholds;
+
+ // Collection of predictions and actual values sent over by Layers
+ std::vector<std::shared_ptr<SurfaceFrame>> mSurfaceFrames;
+
+ PredictionState mPredictionState = PredictionState::None;
+ // Bitmask for the type of jank
+ int32_t mJankType = JankType::None;
+ // Indicates if this frame was composited by the GPU or not
+ bool mGpuComposition = false;
+ // Enum for the type of present
+ FramePresentMetadata mFramePresentMetadata = FramePresentMetadata::UnknownPresent;
+ // Enum for the type of finish
+ FrameReadyMetadata mFrameReadyMetadata = FrameReadyMetadata::UnknownFinish;
+ // Enum for the type of start
+ FrameStartMetadata mFrameStartMetadata = FrameStartMetadata::UnknownStart;
+ // The refresh rate (vsync period) in nanoseconds as seen by SF during this DisplayFrame's
+ // timeline
+ nsecs_t mVsyncPeriod = 0;
+ };
+
+ FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
+ JankClassificationThresholds thresholds = {});
~FrameTimeline() = default;
frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
- std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForToken(
- pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
- std::optional<int64_t> token) override;
- void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame,
- SurfaceFrame::PresentState state) override;
- void setSfWakeUp(int64_t token, nsecs_t wakeupTime) override;
+ std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(std::optional<int64_t> token,
+ pid_t ownerPid, uid_t ownerUid,
+ std::string layerName,
+ std::string debugName) override;
+ void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
+ void setSfWakeUp(int64_t token, nsecs_t wakeupTime, nsecs_t vsyncPeriod) override;
void setSfPresent(nsecs_t sfPresentTime,
const std::shared_ptr<FenceTime>& presentFence) override;
void parseArgs(const Vector<String16>& args, std::string& result) override;
@@ -288,67 +391,28 @@
// Friend class for testing
friend class android::frametimeline::FrameTimelineTest;
- /*
- * DisplayFrame should be used only internally within FrameTimeline.
- */
- struct DisplayFrame {
- DisplayFrame();
-
- int64_t token = ISurfaceComposer::INVALID_VSYNC_ID;
-
- /* Usage of TimelineItem w.r.t SurfaceFlinger
- * startTime Time when SurfaceFlinger wakes up to handle transactions and buffer updates
- * endTime Time when SurfaceFlinger sends a composited frame to Display
- * presentTime Time when the composited frame was presented on screen
- */
- TimelineItem surfaceFlingerPredictions;
- TimelineItem surfaceFlingerActuals;
-
- // Collection of predictions and actual values sent over by Layers
- std::vector<std::shared_ptr<SurfaceFrame>> surfaceFrames;
-
- PredictionState predictionState = PredictionState::None;
- JankType jankType = JankType::None; // Enum for the type of jank
- int32_t jankMetadata = 0x0; // Additional details about the jank
- };
-
void flushPendingPresentFences() REQUIRES(mMutex);
void finalizeCurrentDisplayFrame() REQUIRES(mMutex);
- // BaseTime is the smallest timestamp in a DisplayFrame.
- // Used for dumping all timestamps relative to the oldest, making it easy to read.
- nsecs_t findBaseTime(const std::shared_ptr<DisplayFrame>&) REQUIRES(mMutex);
- void dumpDisplayFrame(std::string& result, const std::shared_ptr<DisplayFrame>&,
- nsecs_t baseTime) REQUIRES(mMutex);
void dumpAll(std::string& result);
void dumpJank(std::string& result);
- // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
- // enabled.
- void traceDisplayFrame(const DisplayFrame& displayFrame) REQUIRES(mMutex);
-
// Sliding window of display frames. TODO(b/168072834): compare perf with fixed size array
std::deque<std::shared_ptr<DisplayFrame>> mDisplayFrames GUARDED_BY(mMutex);
std::vector<std::pair<std::shared_ptr<FenceTime>, std::shared_ptr<DisplayFrame>>>
mPendingPresentFences GUARDED_BY(mMutex);
std::shared_ptr<DisplayFrame> mCurrentDisplayFrame GUARDED_BY(mMutex);
TokenManager mTokenManager;
- std::mutex mMutex;
+ mutable std::mutex mMutex;
uint32_t mMaxDisplayFrames;
std::shared_ptr<TimeStats> mTimeStats;
+ const pid_t mSurfaceFlingerPid;
+ const JankClassificationThresholds mJankClassificationThresholds;
static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
// The initial container size for the vector<SurfaceFrames> inside display frame. Although
// this number doesn't represent any bounds on the number of surface frames that can go in a
// display frame, this is a good starting size for the vector so that we can avoid the
// internal vector resizing that happens with push_back.
static constexpr uint32_t kNumSurfaceFramesInitial = 10;
- // The various thresholds for App and SF. If the actual timestamp falls within the threshold
- // compared to prediction, we don't treat it as a jank.
- static constexpr nsecs_t kPresentThreshold =
- std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
- static constexpr nsecs_t kDeadlineThreshold =
- std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
- static constexpr nsecs_t kSFStartThreshold =
- std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count();
};
} // namespace impl
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d5b599a..a545d18 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -656,6 +656,8 @@
layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
layerSettings.blurRegions = getBlurRegions();
}
+ // Record the name of the layer for debugging further down the stack.
+ layerSettings.name = getName();
return layerSettings;
}
@@ -686,6 +688,7 @@
shadowLayer.source.buffer.fence = nullptr;
shadowLayer.frameNumber = 0;
shadowLayer.bufferId = 0;
+ shadowLayer.name = getName();
if (shadowLayer.shadow.ambientColor.a <= 0.f && shadowLayer.shadow.spotColor.a <= 0.f) {
return {};
@@ -721,6 +724,7 @@
// If layer is blacked out, force alpha to 1 so that we draw a black color layer.
layerSettings.alpha = blackout ? 1.0f : 0.0f;
+ layerSettings.name = getName();
}
std::vector<compositionengine::LayerFE::LayerSettings> Layer::prepareClientCompositionList(
@@ -909,9 +913,8 @@
: std::make_optional(stateToCommit->frameTimelineVsyncId);
mSurfaceFrame =
- mFlinger->mFrameTimeline->createSurfaceFrameForToken(getOwnerPid(), getOwnerUid(),
- mName, mTransactionName,
- vsyncId);
+ mFlinger->mFrameTimeline->createSurfaceFrameForToken(vsyncId, mOwnerPid, mOwnerUid,
+ mName, mTransactionName);
mSurfaceFrame->setActualQueueTime(stateToCommit->postTime);
// For transactions we set the acquire fence time to the post time as we
// don't have a buffer. For BufferStateLayer it is overridden in
@@ -1064,7 +1067,8 @@
void Layer::commitTransaction(const State& stateToCommit) {
mDrawingState = stateToCommit;
- mFlinger->mFrameTimeline->addSurfaceFrame(mSurfaceFrame, PresentState::Presented);
+ mSurfaceFrame->setPresentState(PresentState::Presented);
+ mFlinger->mFrameTimeline->addSurfaceFrame(mSurfaceFrame);
}
uint32_t Layer::getTransactionFlags(uint32_t flags) {
@@ -2185,7 +2189,10 @@
}
int32_t Layer::getBackgroundBlurRadius() const {
- return getDrawingState().backgroundBlurRadius;
+ const auto& p = mDrawingParent.promote();
+
+ half parentAlpha = (p != nullptr) ? p->getAlpha() : 1.0_hf;
+ return parentAlpha * getDrawingState().backgroundBlurRadius;
}
const std::vector<BlurRegion>& Layer::getBlurRegions() const {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 2cfdba3..d6023b6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -275,7 +275,8 @@
// recent callback handle.
std::deque<sp<CallbackHandle>> callbackHandles;
bool colorSpaceAgnostic;
- nsecs_t desiredPresentTime = -1;
+ nsecs_t desiredPresentTime = 0;
+ bool isAutoTimestamp = true;
// Length of the cast shadow. If the radius is > 0, a shadow of length shadowRadius will
// be rendered around the layer.
@@ -444,7 +445,8 @@
virtual bool setFrame(const Rect& /*frame*/) { return false; };
virtual bool setBuffer(const sp<GraphicBuffer>& /*buffer*/, const sp<Fence>& /*acquireFence*/,
nsecs_t /*postTime*/, nsecs_t /*desiredPresentTime*/,
- const client_cache_t& /*clientCacheId*/, uint64_t /* frameNumber */) {
+ bool /*isAutoTimestamp*/, const client_cache_t& /*clientCacheId*/,
+ uint64_t /* frameNumber */) {
return false;
};
virtual bool setAcquireFence(const sp<Fence>& /*fence*/) { return false; };
@@ -481,6 +483,8 @@
virtual void useSurfaceDamage() {}
virtual void useEmptyDamage() {}
+ virtual void incrementPendingBufferCount() {}
+
/*
* isOpaque - true if this surface is opaque
*
@@ -745,7 +749,7 @@
* doTransaction - process the transaction. This is a good place to figure
* out which attributes of the surface have changed.
*/
- uint32_t doTransaction(uint32_t transactionFlags);
+ virtual uint32_t doTransaction(uint32_t transactionFlags);
/*
* Remove relative z for the layer if its relative parent is not part of the
@@ -879,7 +883,7 @@
*/
bool hasInputInfo() const;
- uid_t getOwnerUid() { return mOwnerUid; }
+ virtual uid_t getOwnerUid() const { return mOwnerUid; }
pid_t getOwnerPid() { return mOwnerPid; }
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 59fad9b..b1db6d3 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include "LayerProtoHelper.h"
@@ -173,4 +174,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/Promise.h b/services/surfaceflinger/Promise.h
deleted file mode 100644
index a80d441..0000000
--- a/services/surfaceflinger/Promise.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <future>
-#include <type_traits>
-#include <utility>
-
-namespace android::promise {
-namespace impl {
-
-template <typename T>
-struct FutureResult {
- using Type = T;
-};
-
-template <typename T>
-struct FutureResult<std::future<T>> {
- using Type = T;
-};
-
-} // namespace impl
-
-template <typename T>
-using FutureResult = typename impl::FutureResult<T>::Type;
-
-template <typename... Args>
-inline auto defer(Args... args) {
- return std::async(std::launch::deferred, std::forward<Args>(args)...);
-}
-
-template <typename T>
-inline std::future<T> yield(T&& v) {
- return defer([](T&& v) { return std::forward<T>(v); }, std::forward<T>(v));
-}
-
-template <typename T>
-struct Chain {
- Chain(std::future<T>&& f) : future(std::move(f)) {}
- operator std::future<T>&&() && { return std::move(future); }
-
- T get() && { return future.get(); }
-
- template <typename F, typename R = std::invoke_result_t<F, T>>
- auto then(F&& op) && -> Chain<FutureResult<R>> {
- return defer(
- [](auto&& f, F&& op) {
- R r = op(f.get());
- if constexpr (std::is_same_v<R, FutureResult<R>>) {
- return r;
- } else {
- return r.get();
- }
- },
- std::move(future), std::forward<F>(op));
- }
-
- std::future<T> future;
-};
-
-template <typename T>
-inline Chain<T> chain(std::future<T>&& f) {
- return std::move(f);
-}
-
-} // namespace android::promise
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index f99d54a..6a511a8 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -17,6 +17,9 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
+
+#include <algorithm>
#include "RefreshRateOverlay.h"
#include "Client.h"
@@ -172,7 +175,7 @@
RefreshRateOverlay::RefreshRateOverlay(SurfaceFlinger& flinger, bool showSpinner)
: mFlinger(flinger), mClient(new Client(&mFlinger)), mShowSpinner(showSpinner) {
createLayer();
- primeCache();
+ reset();
}
bool RefreshRateOverlay::createLayer() {
@@ -202,26 +205,15 @@
return true;
}
-void RefreshRateOverlay::primeCache() {
- auto& allRefreshRates = mFlinger.mRefreshRateConfigs->getAllRefreshRates();
- if (allRefreshRates.size() == 1) {
- int fps = allRefreshRates.begin()->second->getFps().getIntValue();
- half4 color = {LOW_FPS_COLOR, ALPHA};
- mBufferCache.emplace(fps, SevenSegmentDrawer::drawNumber(fps, color, mShowSpinner));
- return;
- }
-
- std::vector<uint32_t> supportedFps;
- supportedFps.reserve(allRefreshRates.size());
- for (auto& [ignored, refreshRate] : allRefreshRates) {
- supportedFps.push_back(refreshRate->getFps().getIntValue());
- }
-
- std::sort(supportedFps.begin(), supportedFps.end());
- const auto mLowFps = supportedFps[0];
- const auto mHighFps = supportedFps[supportedFps.size() - 1];
- for (auto fps : supportedFps) {
- const auto fpsScale = float(fps - mLowFps) / (mHighFps - mLowFps);
+const std::vector<sp<GraphicBuffer>>& RefreshRateOverlay::getOrCreateBuffers(uint32_t fps) {
+ if (mBufferCache.find(fps) == mBufferCache.end()) {
+ // Ensure the range is > 0, so we don't divide by 0.
+ const auto rangeLength = std::max(1u, mHighFps - mLowFps);
+ // Clip values outside the range [mLowFps, mHighFps]. The current fps may be outside
+ // of this range if the display has changed its set of supported refresh rates.
+ fps = std::max(fps, mLowFps);
+ fps = std::min(fps, mHighFps);
+ const auto fpsScale = static_cast<float>(fps - mLowFps) / rangeLength;
half4 color;
color.r = HIGH_FPS_COLOR.r * fpsScale + LOW_FPS_COLOR.r * (1 - fpsScale);
color.g = HIGH_FPS_COLOR.g * fpsScale + LOW_FPS_COLOR.g * (1 - fpsScale);
@@ -229,6 +221,8 @@
color.a = ALPHA;
mBufferCache.emplace(fps, SevenSegmentDrawer::drawNumber(fps, color, mShowSpinner));
}
+
+ return mBufferCache[fps];
}
void RefreshRateOverlay::setViewport(ui::Size viewport) {
@@ -241,8 +235,8 @@
void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
mCurrentFps = refreshRate.getFps().getIntValue();
- auto buffer = mBufferCache[*mCurrentFps][mFrame];
- mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {},
+ auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
+ mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */));
mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
@@ -251,16 +245,22 @@
void RefreshRateOverlay::onInvalidate() {
if (!mCurrentFps.has_value()) return;
- const auto& buffers = mBufferCache[*mCurrentFps];
+ const auto& buffers = getOrCreateBuffers(*mCurrentFps);
mFrame = (mFrame + 1) % buffers.size();
auto buffer = buffers[mFrame];
- mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {},
+ mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */));
mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
}
+void RefreshRateOverlay::reset() {
+ mBufferCache.clear();
+ mLowFps = mFlinger.mRefreshRateConfigs->getMinRefreshRate().getFps().getIntValue();
+ mHighFps = mFlinger.mRefreshRateConfigs->getMaxRefreshRate().getFps().getIntValue();
+}
+
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 1a8938f..4ca1337 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -43,6 +43,7 @@
void setViewport(ui::Size);
void changeRefreshRate(const RefreshRate&);
void onInvalidate();
+ void reset();
private:
class SevenSegmentDrawer {
@@ -71,7 +72,7 @@
};
bool createLayer();
- void primeCache();
+ const std::vector<sp<GraphicBuffer>>& getOrCreateBuffers(uint32_t fps);
SurfaceFlinger& mFlinger;
const sp<Client> mClient;
@@ -87,6 +88,10 @@
const half3 HIGH_FPS_COLOR = half3(0.0f, 1.0f, 0.0f);
const bool mShowSpinner;
+
+ // Interpolate the colors between these values.
+ uint32_t mLowFps;
+ uint32_t mHighFps;
};
} // namespace android
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 2511eb3..19b3d6e 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
//#define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -28,6 +29,7 @@
#include <compositionengine/Display.h>
#include <compositionengine/impl/OutputCompositionState.h>
#include <cutils/properties.h>
+#include <ftl/future.h>
#include <gui/IRegionSamplingListener.h>
#include <gui/SyncScreenCaptureListener.h>
#include <ui/DisplayStatInfo.h>
@@ -38,7 +40,6 @@
#include "DisplayDevice.h"
#include "DisplayRenderArea.h"
#include "Layer.h"
-#include "Promise.h"
#include "Scheduler/VsyncController.h"
#include "SurfaceFlinger.h"
@@ -250,8 +251,7 @@
// If there is relatively little time left for surfaceflinger
// until the next vsync deadline, defer this sampling work
// to a later frame, when hopefully there will be more time.
- DisplayStatInfo stats;
- mScheduler.getDisplayStatInfo(&stats, systemTime());
+ const DisplayStatInfo stats = mScheduler.getDisplayStatInfo(systemTime());
if (std::chrono::nanoseconds(stats.vsyncTime) - now < timeForRegionSampling) {
ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
mDiscardedFrames++;
@@ -389,7 +389,7 @@
const Rect sampledBounds = sampleRegion.bounds();
- SurfaceFlinger::RenderAreaFuture renderAreaFuture = promise::defer([=] {
+ SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] {
return DisplayRenderArea::create(displayWeak, screencapRegion.bounds(),
sampledBounds.getSize(), ui::Dataspace::V0_SRGB,
orientation);
@@ -501,4 +501,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 499daad..170933d 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -86,9 +86,8 @@
LayerHistory::~LayerHistory() = default;
-void LayerHistory::registerLayer(Layer* layer, Fps highRefreshRate, LayerVoteType type) {
- const nsecs_t highRefreshRatePeriod = highRefreshRate.getPeriodNsecs();
- auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
+void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) {
+ auto info = std::make_unique<LayerInfo>(layer->getName(), type);
std::lock_guard lock(mLock);
mLayerInfos.emplace_back(layer, std::move(info));
}
@@ -143,8 +142,8 @@
const float layerArea = transformed.getWidth() * transformed.getHeight();
float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
- summary.push_back(
- {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
+ summary.push_back({strong->getName(), strong->getOwnerUid(), vote.type, vote.fps,
+ vote.seamlessness, weight, layerFocused});
if (CC_UNLIKELY(mTraceEnabled)) {
trace(layer, *info, vote.type, vote.fps.getIntValue());
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 4214bab..bae9b5a 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -46,7 +46,7 @@
~LayerHistory();
// Layers are unregistered when the weak reference expires.
- void registerLayer(Layer*, Fps highRefreshRate, LayerVoteType type);
+ void registerLayer(Layer*, LayerVoteType type);
// Sets the display size. Client is responsible for synchronization.
void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 1c0065c..0fa71f1 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
// #define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -33,10 +37,8 @@
const RefreshRateConfigs* LayerInfo::sRefreshRateConfigs = nullptr;
bool LayerInfo::sTraceEnabled = false;
-LayerInfo::LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote)
+LayerInfo::LayerInfo(const std::string& name, LayerHistory::LayerVoteType defaultVote)
: mName(name),
- mHighRefreshRatePeriod(highRefreshRatePeriod),
mDefaultVote(defaultVote),
mLayerVote({defaultVote, Fps(0.0f)}),
mRefreshRateHistory(name) {}
@@ -133,7 +135,7 @@
}
totalQueueTimeDeltas +=
- std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+ std::max(((it + 1)->queueTime - it->queueTime), kMinPeriodBetweenFrames);
numFrames++;
if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
@@ -147,7 +149,7 @@
}
totalPresentTimeDeltas +=
- std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+ std::max(((it + 1)->presetTime - it->presetTime), kMinPeriodBetweenFrames);
}
// Calculate the average frame time based on presentation timestamps. If those
@@ -298,3 +300,6 @@
}
} // namespace android::scheduler
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 9304e62..427cc9e 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -70,8 +70,7 @@
sRefreshRateConfigs = &refreshRateConfigs;
}
- LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote);
+ LayerInfo(const std::string& name, LayerHistory::LayerVoteType defaultVote);
LayerInfo(const LayerInfo&) = delete;
LayerInfo& operator=(const LayerInfo&) = delete;
@@ -194,8 +193,9 @@
const std::string mName;
- // Used for sanitizing the heuristic data
- const nsecs_t mHighRefreshRatePeriod;
+ // Used for sanitizing the heuristic data. If two frames are less than
+ // this period apart from each other they'll be considered as duplicates.
+ static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(120.f).getPeriodNsecs();
LayerHistory::LayerVoteType mDefaultVote;
LayerVote mLayerVote;
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 47a4f42..7ff0ddf 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -69,17 +69,33 @@
// TODO(b/169865816): refactor VSyncInjections to use MessageQueue directly
// and remove the EventThread from MessageQueue
-void MessageQueue::setEventConnection(const sp<EventThreadConnection>& connection) {
- if (mEventTube.getFd() >= 0) {
- mLooper->removeFd(mEventTube.getFd());
+void MessageQueue::setInjector(sp<EventThreadConnection> connection) {
+ auto& tube = mInjector.tube;
+
+ if (const int fd = tube.getFd(); fd >= 0) {
+ mLooper->removeFd(fd);
}
- mEvents = connection;
- if (mEvents) {
- mEvents->stealReceiveChannel(&mEventTube);
- mLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,
- this);
+ if (connection) {
+ // The EventThreadConnection is retained when disabling injection, so avoid subsequently
+ // stealing invalid FDs. Note that the stolen FDs are kept open.
+ if (tube.getFd() < 0) {
+ connection->stealReceiveChannel(&tube);
+ } else {
+ ALOGW("Recycling channel for VSYNC injection.");
+ }
+
+ mLooper->addFd(
+ tube.getFd(), 0, Looper::EVENT_INPUT,
+ [](int, int, void* data) {
+ reinterpret_cast<MessageQueue*>(data)->injectorCallback();
+ return 1; // Keep registration.
+ },
+ this);
}
+
+ std::lock_guard lock(mInjector.mutex);
+ mInjector.connection = std::move(connection);
}
void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) {
@@ -149,29 +165,31 @@
void MessageQueue::invalidate() {
ATRACE_CALL();
- if (mEvents) {
- mEvents->requestNextVsync();
- } else {
- std::lock_guard lock(mVsync.mutex);
- mVsync.mScheduled = true;
- mVsync.registration->schedule({mVsync.workDuration.get().count(), /*readyDuration=*/0,
- mVsync.lastCallbackTime.count()});
+
+ {
+ std::lock_guard lock(mInjector.mutex);
+ if (CC_UNLIKELY(mInjector.connection)) {
+ ALOGD("%s while injecting VSYNC", __FUNCTION__);
+ mInjector.connection->requestNextVsync();
+ return;
+ }
}
+
+ std::lock_guard lock(mVsync.mutex);
+ mVsync.mScheduled = true;
+ mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
+ .readyDuration = 0,
+ .earliestVsync = mVsync.lastCallbackTime.count()});
}
void MessageQueue::refresh() {
mHandler->dispatchRefresh();
}
-int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {
- MessageQueue* queue = reinterpret_cast<MessageQueue*>(data);
- return queue->eventReceiver(fd, events);
-}
-
-int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {
+void MessageQueue::injectorCallback() {
ssize_t n;
DisplayEventReceiver::Event buffer[8];
- while ((n = DisplayEventReceiver::getEvents(&mEventTube, buffer, 8)) > 0) {
+ while ((n = DisplayEventReceiver::getEvents(&mInjector.tube, buffer, 8)) > 0) {
for (int i = 0; i < n; i++) {
if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
mHandler->dispatchInvalidate(buffer[i].vsync.vsyncId,
@@ -180,8 +198,6 @@
}
}
}
- return 1;
}
} // namespace android::impl
-
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 99ce3a6..2934af0 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -21,12 +21,11 @@
#include <type_traits>
#include <utility>
-#include <utils/Looper.h>
-#include <utils/Timers.h>
-#include <utils/threads.h>
-
+#include <android-base/thread_annotations.h>
#include <gui/IDisplayEventConnection.h>
#include <private/gui/BitTube.h>
+#include <utils/Looper.h>
+#include <utils/Timers.h>
#include "EventThread.h"
#include "TracedOrdinal.h"
@@ -68,7 +67,7 @@
virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) = 0;
virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
- virtual void setEventConnection(const sp<EventThreadConnection>& connection) = 0;
+ virtual void setInjector(sp<EventThreadConnection>) = 0;
virtual void waitMessage() = 0;
virtual void postMessage(sp<MessageHandler>&&) = 0;
virtual void invalidate() = 0;
@@ -99,7 +98,6 @@
sp<SurfaceFlinger> mFlinger;
sp<Looper> mLooper;
- sp<EventThreadConnection> mEvents;
struct Vsync {
frametimeline::TokenManager* tokenManager = nullptr;
@@ -113,14 +111,19 @@
TracedOrdinal<int> value = {"VSYNC-sf", 0};
};
- Vsync mVsync;
+ struct Injector {
+ gui::BitTube tube;
+ std::mutex mutex;
+ sp<EventThreadConnection> connection GUARDED_BY(mutex);
+ };
- gui::BitTube mEventTube;
+ Vsync mVsync;
+ Injector mInjector;
+
sp<Handler> mHandler;
- static int cb_eventReceiver(int fd, int events, void* data);
- int eventReceiver(int fd, int events);
void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime);
+ void injectorCallback();
public:
~MessageQueue() override = default;
@@ -128,7 +131,7 @@
void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) override;
void setDuration(std::chrono::nanoseconds workDuration) override;
- void setEventConnection(const sp<EventThreadConnection>& connection) override;
+ void setInjector(sp<EventThreadConnection>) override;
void waitMessage() override;
void postMessage(sp<MessageHandler>&&) override;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 4b7251b..35b382e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -17,6 +17,10 @@
// #define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include "RefreshRateConfigs.h"
#include <android-base/stringprintf.h>
#include <utils/Trace.h>
@@ -71,16 +75,84 @@
std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
nsecs_t displayPeriod) const {
- auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
- if (displayFramesRem <= MARGIN_FOR_PERIOD_CALCULATION ||
- std::abs(displayFramesRem - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
- displayFramesQuot++;
- displayFramesRem = 0;
+ auto [quotient, remainder] = std::div(layerPeriod, displayPeriod);
+ if (remainder <= MARGIN_FOR_PERIOD_CALCULATION ||
+ std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
+ quotient++;
+ remainder = 0;
}
- return {displayFramesQuot, displayFramesRem};
+ return {quotient, remainder};
}
+float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
+ const RefreshRate& refreshRate,
+ bool isSeamlessSwitch) const {
+ // Slightly prefer seamless switches.
+ constexpr float kSeamedSwitchPenalty = 0.95f;
+ const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
+
+ // If the layer wants Max, give higher score to the higher refresh rate
+ if (layer.vote == LayerVoteType::Max) {
+ const auto ratio =
+ refreshRate.fps.getValue() / mAppRequestRefreshRates.back()->fps.getValue();
+ // use ratio^2 to get a lower score the more we get further from peak
+ return ratio * ratio;
+ }
+
+ const auto displayPeriod = refreshRate.getVsyncPeriod();
+ const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
+ if (layer.vote == LayerVoteType::ExplicitDefault) {
+ // Find the actual rate the layer will render, assuming
+ // that layerPeriod is the minimal time to render a frame
+ auto actualLayerPeriod = displayPeriod;
+ int multiplier = 1;
+ while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
+ multiplier++;
+ actualLayerPeriod = displayPeriod * multiplier;
+ }
+ return std::min(1.0f,
+ static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
+ }
+
+ if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
+ layer.vote == LayerVoteType::Heuristic) {
+ // Calculate how many display vsyncs we need to present a single frame for this
+ // layer
+ const auto [displayFramesQuotient, displayFramesRemainder] =
+ getDisplayFrames(layerPeriod, displayPeriod);
+ static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
+ if (displayFramesRemainder == 0) {
+ // Layer desired refresh rate matches the display rate.
+ return 1.0f * seamlessness;
+ }
+
+ if (displayFramesQuotient == 0) {
+ // Layer desired refresh rate is higher than the display rate.
+ return (static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod)) *
+ (1.0f / (MAX_FRAMES_TO_FIT + 1));
+ }
+
+ // Layer desired refresh rate is lower than the display rate. Check how well it fits
+ // the cadence.
+ auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder));
+ int iter = 2;
+ while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
+ diff = diff - (displayPeriod - diff);
+ iter++;
+ }
+
+ return (1.0f / iter) * seamlessness;
+ }
+
+ return 0;
+}
+
+struct RefreshRateScore {
+ const RefreshRate* refreshRate;
+ float score;
+};
+
const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
GlobalSignals* outSignalsConsidered) const {
@@ -165,11 +237,11 @@
}
// Find the best refresh rate based on score
- std::vector<std::pair<const RefreshRate*, float>> scores;
+ std::vector<RefreshRateScore> scores;
scores.reserve(mAppRequestRefreshRates.size());
for (const auto refreshRate : mAppRequestRefreshRates) {
- scores.emplace_back(refreshRate, 0.0f);
+ scores.emplace_back(RefreshRateScore{refreshRate, 0.0f});
}
const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig);
@@ -184,12 +256,13 @@
auto weight = layer.weight;
for (auto i = 0u; i < scores.size(); i++) {
- const bool isSeamlessSwitch =
- scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup();
+ const bool isSeamlessSwitch = scores[i].refreshRate->getConfigGroup() ==
+ mCurrentRefreshRate->getConfigGroup();
if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
ALOGV("%s ignores %s to avoid non-seamless switch. Current config = %s",
- formatLayerInfo(layer, weight).c_str(), scores[i].first->toString().c_str(),
+ formatLayerInfo(layer, weight).c_str(),
+ scores[i].refreshRate->toString().c_str(),
mCurrentRefreshRate->toString().c_str());
continue;
}
@@ -198,7 +271,8 @@
!layer.focused) {
ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
" Current config = %s",
- formatLayerInfo(layer, weight).c_str(), scores[i].first->toString().c_str(),
+ formatLayerInfo(layer, weight).c_str(),
+ scores[i].refreshRate->toString().c_str(),
mCurrentRefreshRate->toString().c_str());
continue;
}
@@ -209,18 +283,20 @@
// from the default, this means a layer with seamlessness=SeamedAndSeamless has just
// disappeared.
const bool isInPolicyForDefault = seamedLayers > 0
- ? scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup()
- : scores[i].first->getConfigGroup() == defaultConfig->getConfigGroup();
+ ? scores[i].refreshRate->getConfigGroup() ==
+ mCurrentRefreshRate->getConfigGroup()
+ : scores[i].refreshRate->getConfigGroup() == defaultConfig->getConfigGroup();
if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault &&
!layer.focused) {
ALOGV("%s ignores %s. Current config = %s", formatLayerInfo(layer, weight).c_str(),
- scores[i].first->toString().c_str(), mCurrentRefreshRate->toString().c_str());
+ scores[i].refreshRate->toString().c_str(),
+ mCurrentRefreshRate->toString().c_str());
continue;
}
- bool inPrimaryRange =
- scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);
+ bool inPrimaryRange = scores[i].refreshRate->inPolicy(policy->primaryRange.min,
+ policy->primaryRange.max);
if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
!(layer.focused && layer.vote == LayerVoteType::ExplicitDefault)) {
// Only focused layers with ExplicitDefault frame rate settings are allowed to score
@@ -228,81 +304,11 @@
continue;
}
- // If the layer wants Max, give higher score to the higher refresh rate
- if (layer.vote == LayerVoteType::Max) {
- const auto ratio =
- scores[i].first->fps.getValue() / scores.back().first->fps.getValue();
- // use ratio^2 to get a lower score the more we get further from peak
- const auto layerScore = ratio * ratio;
- ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
- scores[i].first->getName().c_str(), layerScore);
- scores[i].second += weight * layerScore;
- continue;
- }
-
- const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();
- const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
- if (layer.vote == LayerVoteType::ExplicitDefault) {
- const auto layerScore = [&]() {
- // Find the actual rate the layer will render, assuming
- // that layerPeriod is the minimal time to render a frame
- auto actualLayerPeriod = displayPeriod;
- int multiplier = 1;
- while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
- multiplier++;
- actualLayerPeriod = displayPeriod * multiplier;
- }
- return std::min(1.0f,
- static_cast<float>(layerPeriod) /
- static_cast<float>(actualLayerPeriod));
- }();
-
- ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
- scores[i].first->getName().c_str(), layerScore);
- scores[i].second += weight * layerScore;
- continue;
- }
-
- if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
- layer.vote == LayerVoteType::Heuristic) {
- const auto layerScore = [&] {
- // Calculate how many display vsyncs we need to present a single frame for this
- // layer
- const auto [displayFramesQuot, displayFramesRem] =
- getDisplayFrames(layerPeriod, displayPeriod);
- static constexpr size_t MAX_FRAMES_TO_FIT =
- 10; // Stop calculating when score < 0.1
- if (displayFramesRem == 0) {
- // Layer desired refresh rate matches the display rate.
- return 1.0f;
- }
-
- if (displayFramesQuot == 0) {
- // Layer desired refresh rate is higher the display rate.
- return (static_cast<float>(layerPeriod) /
- static_cast<float>(displayPeriod)) *
- (1.0f / (MAX_FRAMES_TO_FIT + 1));
- }
-
- // Layer desired refresh rate is lower the display rate. Check how well it fits
- // the cadence
- auto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));
- int iter = 2;
- while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
- diff = diff - (displayPeriod - diff);
- iter++;
- }
-
- return 1.0f / iter;
- }();
- // Slightly prefer seamless switches.
- constexpr float kSeamedSwitchPenalty = 0.95f;
- const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
- ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
- scores[i].first->getName().c_str(), layerScore);
- scores[i].second += weight * layerScore * seamlessness;
- continue;
- }
+ const auto layerScore =
+ calculateLayerScoreLocked(layer, *scores[i].refreshRate, isSeamlessSwitch);
+ ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
+ scores[i].refreshRate->getName().c_str(), layerScore);
+ scores[i].score += weight * layerScore;
}
}
@@ -317,7 +323,7 @@
// If we never scored any layers, then choose the rate from the primary
// range instead of picking a random score from the app range.
if (std::all_of(scores.begin(), scores.end(),
- [](std::pair<const RefreshRate*, float> p) { return p.second == 0; })) {
+ [](RefreshRateScore score) { return score.score == 0; })) {
ALOGV("layers not scored - choose %s",
getMaxRefreshRateByPolicyLocked().getName().c_str());
return getMaxRefreshRateByPolicyLocked();
@@ -342,11 +348,111 @@
return *bestRefreshRate;
}
+std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
+groupLayersByUid(const std::vector<RefreshRateConfigs::LayerRequirement>& layers) {
+ std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>> layersByUid;
+ for (const auto& layer : layers) {
+ auto iter = layersByUid.emplace(layer.ownerUid,
+ std::vector<const RefreshRateConfigs::LayerRequirement*>());
+ auto& layersWithSameUid = iter.first->second;
+ layersWithSameUid.push_back(&layer);
+ }
+
+ // Remove uids that can't have a frame rate override
+ for (auto iter = layersByUid.begin(); iter != layersByUid.end();) {
+ const auto& layersWithSameUid = iter->second;
+ bool skipUid = false;
+ for (const auto& layer : layersWithSameUid) {
+ if (layer->vote == RefreshRateConfigs::LayerVoteType::Max ||
+ layer->vote == RefreshRateConfigs::LayerVoteType::Heuristic) {
+ skipUid = true;
+ break;
+ }
+ }
+ if (skipUid) {
+ iter = layersByUid.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ return layersByUid;
+}
+
+std::vector<RefreshRateScore> initializeScoresForAllRefreshRates(
+ const AllRefreshRatesMapType& refreshRates) {
+ std::vector<RefreshRateScore> scores;
+ scores.reserve(refreshRates.size());
+ for (const auto& [ignored, refreshRate] : refreshRates) {
+ scores.emplace_back(RefreshRateScore{refreshRate.get(), 0.0f});
+ }
+ std::sort(scores.begin(), scores.end(),
+ [](const auto& a, const auto& b) { return *a.refreshRate < *b.refreshRate; });
+ return scores;
+}
+
+RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides(
+ const std::vector<LayerRequirement>& layers, Fps displayFrameRate) const {
+ ATRACE_CALL();
+ if (!mSupportsFrameRateOverride) return {};
+
+ ALOGV("getFrameRateOverrides %zu layers", layers.size());
+ std::lock_guard lock(mLock);
+ std::vector<RefreshRateScore> scores = initializeScoresForAllRefreshRates(mRefreshRates);
+ std::unordered_map<uid_t, std::vector<const LayerRequirement*>> layersByUid =
+ groupLayersByUid(layers);
+ UidToFrameRateOverride frameRateOverrides;
+ for (const auto& [uid, layersWithSameUid] : layersByUid) {
+ for (auto& score : scores) {
+ score.score = 0;
+ }
+
+ for (const auto& layer : layersWithSameUid) {
+ if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) {
+ continue;
+ }
+
+ LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
+ layer->vote != LayerVoteType::ExplicitExactOrMultiple);
+ for (RefreshRateScore& score : scores) {
+ const auto layerScore = calculateLayerScoreLocked(*layer, *score.refreshRate,
+ /*isSeamlessSwitch*/ true);
+ score.score += layer->weight * layerScore;
+ }
+ }
+
+ // We just care about the refresh rates which are a divider of the
+ // display refresh rate
+ auto iter =
+ std::remove_if(scores.begin(), scores.end(), [&](const RefreshRateScore& score) {
+ return getFrameRateDivider(displayFrameRate, score.refreshRate->getFps()) == 0;
+ });
+ scores.erase(iter, scores.end());
+
+ // If we never scored any layers, we don't have a preferred frame rate
+ if (std::all_of(scores.begin(), scores.end(),
+ [](const RefreshRateScore& score) { return score.score == 0; })) {
+ continue;
+ }
+
+ // Now that we scored all the refresh rates we need to pick the one that got the highest
+ // score.
+ const RefreshRate* bestRefreshRate = getBestRefreshRate(scores.begin(), scores.end());
+
+ // If the nest refresh rate is the current one, we don't have an override
+ if (!bestRefreshRate->getFps().equalsWithMargin(displayFrameRate)) {
+ frameRateOverrides.emplace(uid, bestRefreshRate->getFps());
+ }
+ }
+
+ return frameRateOverrides;
+}
+
template <typename Iter>
const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
constexpr auto EPSILON = 0.001f;
- const RefreshRate* bestRefreshRate = begin->first;
- float max = begin->second;
+ const RefreshRate* bestRefreshRate = begin->refreshRate;
+ float max = begin->score;
for (auto i = begin; i != end; ++i) {
const auto [refreshRate, score] = *i;
ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
@@ -362,10 +468,6 @@
return bestRefreshRate;
}
-const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
- return mRefreshRates;
-}
-
const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
std::lock_guard lock(mLock);
return getMinRefreshRateByPolicyLocked();
@@ -431,6 +533,7 @@
HwcConfigIndexType currentConfigId)
: mKnownFrameRates(constructKnownFrameRates(configs)) {
LOG_ALWAYS_FATAL_IF(configs.empty());
+ LOG_ALWAYS_FATAL_IF(currentConfigId.value() < 0);
LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
@@ -450,6 +553,16 @@
mDisplayManagerPolicy.defaultConfig = currentConfigId;
mMinSupportedRefreshRate = sortedConfigs.front();
mMaxSupportedRefreshRate = sortedConfigs.back();
+
+ mSupportsFrameRateOverride = false;
+ for (const auto& config1 : sortedConfigs) {
+ for (const auto& config2 : sortedConfigs) {
+ if (getFrameRateDivider(config1->getFps(), config2->getFps()) >= 2) {
+ mSupportsFrameRateOverride = true;
+ break;
+ }
+ }
+ }
constructAvailableRefreshRates();
}
@@ -649,50 +762,22 @@
return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
}
-void RefreshRateConfigs::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
- if (frameRateOverride.frameRateHz > 0 && frameRateOverride.frameRateHz < 1) {
- return;
- }
-
- std::lock_guard lock(mLock);
- if (frameRateOverride.frameRateHz != 0) {
- mPreferredRefreshRateForUid[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
- } else {
- mPreferredRefreshRateForUid.erase(frameRateOverride.uid);
- }
-}
-
-int RefreshRateConfigs::getRefreshRateDividerForUid(uid_t uid) const {
- std::lock_guard lock(mLock);
-
- const auto iter = mPreferredRefreshRateForUid.find(uid);
- if (iter == mPreferredRefreshRateForUid.end()) {
- return 1;
- }
-
+int RefreshRateConfigs::getFrameRateDivider(Fps displayFrameRate, Fps layerFrameRate) {
// This calculation needs to be in sync with the java code
// in DisplayManagerService.getDisplayInfoForFrameRateOverride
constexpr float kThreshold = 0.1f;
- const auto refreshRateHz = iter->second;
- const auto numPeriods = mCurrentRefreshRate->getFps().getValue() / refreshRateHz.getValue();
+ const auto numPeriods = displayFrameRate.getValue() / layerFrameRate.getValue();
const auto numPeriodsRounded = std::round(numPeriods);
if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
- return 1;
+ return 0;
}
return static_cast<int>(numPeriodsRounded);
}
-std::vector<FrameRateOverride> RefreshRateConfigs::getFrameRateOverrides() {
+int RefreshRateConfigs::getRefreshRateDivider(Fps frameRate) const {
std::lock_guard lock(mLock);
- std::vector<FrameRateOverride> overrides;
- overrides.reserve(mPreferredRefreshRateForUid.size());
-
- for (const auto [uid, frameRate] : mPreferredRefreshRateForUid) {
- overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
- }
-
- return overrides;
+ return getFrameRateDivider(mCurrentRefreshRate->getFps(), frameRate);
}
void RefreshRateConfigs::dump(std::string& result) const {
@@ -714,7 +799,12 @@
base::StringAppendF(&result, "\t%s\n", refreshRate->toString().c_str());
}
+ base::StringAppendF(&result, "Supports Frame Rate Override: %s\n",
+ mSupportsFrameRateOverride ? "yes" : "no");
result.append("\n");
}
} // namespace android::scheduler
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index ec7ffe5..e4bbf7f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -221,6 +221,8 @@
struct LayerRequirement {
// Layer's name. Used for debugging purposes.
std::string name;
+ // Layer's owner uid
+ uid_t ownerUid = static_cast<uid_t>(-1);
// Layer vote type.
LayerVoteType vote = LayerVoteType::NoVote;
// Layer's desired refresh rate, if applicable.
@@ -261,9 +263,6 @@
GlobalSignals* outSignalsConsidered = nullptr) const
EXCLUDES(mLock);
- // Returns all the refresh rates supported by the device. This won't change at runtime.
- const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
-
// Returns the lowest refresh rate supported by the device. This won't change at runtime.
const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
@@ -319,18 +318,18 @@
// refresh rates.
KernelIdleTimerAction getIdleTimerAction() const;
- // Stores the preferred refresh rate that an app should run at.
- // FrameRateOverride.refreshRateHz == 0 means no preference.
- void setPreferredRefreshRateForUid(FrameRateOverride) EXCLUDES(mLock);
+ bool supportsFrameRateOverride() const { return mSupportsFrameRateOverride; }
// Returns a divider for the current refresh rate
- int getRefreshRateDividerForUid(uid_t) const EXCLUDES(mLock);
+ int getRefreshRateDivider(Fps frameRate) const EXCLUDES(mLock);
+
+ // Returns the frame rate override for each uid
+ using UidToFrameRateOverride = std::map<uid_t, Fps>;
+ UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>& layers,
+ Fps displayFrameRate) const EXCLUDES(mLock);
void dump(std::string& result) const EXCLUDES(mLock);
- // Returns the current frame rate overrides
- std::vector<FrameRateOverride> getFrameRateOverrides() EXCLUDES(mLock);
-
private:
friend class RefreshRateConfigsTest;
@@ -367,6 +366,16 @@
const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
bool isPolicyValid(const Policy& policy);
+ // Return the display refresh rate divider to match the layer
+ // frame rate, or 0 if the display refresh rate is not a multiple of the
+ // layer refresh rate.
+ static int getFrameRateDivider(Fps displayFrameRate, Fps layerFrameRate);
+
+ // calculates a score for a layer. Used to determine the display refresh rate
+ // and the frame rate override for certains applications.
+ float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&,
+ bool isSeamlessSwitch) const REQUIRES(mLock);
+
// The list of refresh rates, indexed by display config ID. This must not change after this
// object is initialized.
AllRefreshRatesMapType mRefreshRates;
@@ -388,10 +397,6 @@
Policy mDisplayManagerPolicy GUARDED_BY(mLock);
std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
- // A mapping between a UID and a preferred refresh rate that this app would
- // run at.
- std::unordered_map<uid_t, Fps> mPreferredRefreshRateForUid GUARDED_BY(mLock);
-
// The min and max refresh rates supported by the device.
// This will not change at runtime.
const RefreshRate* mMinSupportedRefreshRate;
@@ -402,6 +407,8 @@
// A sorted list of known frame rates that a Heuristic layer will choose
// from based on the closest value.
const std::vector<Fps> mKnownFrameRates;
+
+ bool mSupportsFrameRateOverride;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 07411b0..18c899b 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -206,8 +206,36 @@
readyDuration, traceVsync, name);
}
+std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ {
+ const auto iter = mFrameRateOverridesFromBackdoor.find(uid);
+ if (iter != mFrameRateOverridesFromBackdoor.end()) {
+ return std::make_optional<Fps>(iter->second);
+ }
+ }
+
+ {
+ const auto iter = mFrameRateOverridesByContent.find(uid);
+ if (iter != mFrameRateOverridesByContent.end()) {
+ return std::make_optional<Fps>(iter->second);
+ }
+ }
+
+ return std::nullopt;
+}
+
bool Scheduler::isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const {
- const auto divider = mRefreshRateConfigs.getRefreshRateDividerForUid(uid);
+ if (!mRefreshRateConfigs.supportsFrameRateOverride()) {
+ return true;
+ }
+
+ const auto frameRate = getFrameRateOverride(uid);
+ if (!frameRate.has_value()) {
+ return true;
+ }
+
+ const auto divider = mRefreshRateConfigs.getRefreshRateDivider(*frameRate);
if (divider <= 1) {
return true;
}
@@ -215,14 +243,22 @@
return mVsyncSchedule.tracker->isVSyncInPhase(expectedVsyncTimestamp, divider);
}
+impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
+ if (!mRefreshRateConfigs.supportsFrameRateOverride()) {
+ return {};
+ }
+
+ return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
+ return !isVsyncValid(expectedVsyncTimestamp, uid);
+ };
+}
+
Scheduler::ConnectionHandle Scheduler::createConnection(
const char* connectionName, frametimeline::TokenManager* tokenManager,
std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
impl::EventThread::InterceptVSyncsCallback interceptCallback) {
auto vsyncSource = makePrimaryDispSyncSource(connectionName, workDuration, readyDuration);
- auto throttleVsync = [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
- return !isVsyncValid(expectedVsyncTimestamp, uid);
- };
+ auto throttleVsync = makeThrottleVsyncCallback();
auto eventThread = std::make_unique<impl::EventThread>(std::move(vsyncSource), tokenManager,
std::move(interceptCallback),
std::move(throttleVsync));
@@ -290,11 +326,22 @@
thread->onScreenReleased();
}
-void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
- std::vector<FrameRateOverride> overrides) {
+void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
+ std::vector<FrameRateOverride> overrides;
+ {
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
+ overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+ }
+ for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
+ if (mFrameRateOverridesFromBackdoor.count(uid) == 0) {
+ overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+ }
+ }
+ }
android::EventThread* thread;
{
- std::lock_guard<std::mutex> lock(mConnectionsLock);
+ std::lock_guard lock(mConnectionsLock);
RETURN_IF_INVALID_HANDLE(handle);
thread = mConnections[handle].thread.get();
}
@@ -303,9 +350,11 @@
void Scheduler::onPrimaryDisplayConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
- std::lock_guard<std::mutex> lock(mFeatureStateLock);
- // Cache the last reported config for primary display.
- mFeatures.cachedConfigChangedParams = {handle, displayId, configId, vsyncPeriod};
+ {
+ std::lock_guard<std::mutex> lock(mFeatureStateLock);
+ // Cache the last reported config for primary display.
+ mFeatures.cachedConfigChangedParams = {handle, displayId, configId, vsyncPeriod};
+ }
onNonPrimaryDisplayConfigChanged(handle, displayId, configId, vsyncPeriod);
}
@@ -377,9 +426,10 @@
thread->setDuration(workDuration, readyDuration);
}
-void Scheduler::getDisplayStatInfo(DisplayStatInfo* stats, nsecs_t now) {
- stats->vsyncTime = mVsyncSchedule.tracker->nextAnticipatedVSyncTimeFrom(now);
- stats->vsyncPeriod = mVsyncSchedule.tracker->currentPeriod();
+DisplayStatInfo Scheduler::getDisplayStatInfo(nsecs_t now) {
+ const auto vsyncTime = mVsyncSchedule.tracker->nextAnticipatedVSyncTimeFrom(now);
+ const auto vsyncPeriod = mVsyncSchedule.tracker->currentPeriod();
+ return DisplayStatInfo{.vsyncTime = vsyncTime, .vsyncPeriod = vsyncPeriod};
}
Scheduler::ConnectionHandle Scheduler::enableVSyncInjection(bool enable) {
@@ -399,6 +449,10 @@
impl::EventThread::InterceptVSyncsCallback(),
impl::EventThread::ThrottleVsyncCallback());
+ // EventThread does not dispatch VSYNC unless the display is connected and powered on.
+ eventThread->onHotplugReceived(PhysicalDisplayId::fromPort(0), true);
+ eventThread->onScreenAcquired();
+
mInjectorConnectionHandle = createConnection(std::move(eventThread));
}
@@ -510,23 +564,19 @@
void Scheduler::registerLayer(Layer* layer) {
if (!mLayerHistory) return;
- const auto maxFps = mRefreshRateConfigs.getMaxRefreshRate().getFps();
-
if (layer->getWindowType() == InputWindowInfo::Type::STATUS_BAR) {
- mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::NoVote);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::NoVote);
} else if (!mOptions.useContentDetection) {
// If the content detection feature is off, all layers are registered at Max. We still keep
// the layer history, since we use it for other features (like Frame Rate API), so layers
// still need to be registered.
- mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::Max);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::Max);
} else {
if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
// Running Wallpaper at Min is considered as part of content detection.
- mLayerHistory->registerLayer(layer, maxFps,
- scheduler::LayerHistory::LayerVoteType::Min);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::Min);
} else {
- mLayerHistory->registerLayer(layer, maxFps,
- scheduler::LayerHistory::LayerVoteType::Heuristic);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::Heuristic);
}
}
}
@@ -550,7 +600,10 @@
ATRACE_CALL();
scheduler::LayerHistory::Summary summary = mLayerHistory->summarize(systemTime());
+ scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
HwcConfigIndexType newConfigId;
+ bool frameRateChanged;
+ bool frameRateOverridesChanged;
{
std::lock_guard<std::mutex> lock(mFeatureStateLock);
if (mFeatures.contentRequirements == summary) {
@@ -558,22 +611,32 @@
}
mFeatures.contentRequirements = summary;
- scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
+ auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+ frameRateOverridesChanged =
+ updateFrameRateOverrides(consideredSignals, newRefreshRate.getFps());
+
if (mFeatures.configId == newConfigId) {
// We don't need to change the config, but we might need to send an event
// about a config change, since it was suppressed due to a previous idleConsidered
if (!consideredSignals.idle) {
dispatchCachedReportedConfig();
}
- return;
+ frameRateChanged = false;
+ } else {
+ mFeatures.configId = newConfigId;
+ frameRateChanged = true;
}
- mFeatures.configId = newConfigId;
+ }
+ if (frameRateChanged) {
auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
mSchedulerCallback.changeRefreshRate(newRefreshRate,
consideredSignals.idle ? ConfigEvent::None
: ConfigEvent::Changed);
}
+ if (frameRateOverridesChanged) {
+ mSchedulerCallback.triggerOnFrameRateOverridesChanged();
+ }
}
void Scheduler::resetIdleTimer() {
@@ -666,6 +729,21 @@
StringAppendF(&result, "+ Content detection: %s %s\n\n",
toContentDetectionString(mOptions.useContentDetection),
mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
+
+ {
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ StringAppendF(&result, "Frame Rate Overrides (backdoor): {");
+ for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
+ StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+ }
+ StringAppendF(&result, "}\n");
+
+ StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
+ for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
+ StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+ }
+ StringAppendF(&result, "}\n");
+ }
}
void Scheduler::dumpVsync(std::string& s) const {
@@ -677,9 +755,41 @@
mVsyncSchedule.dispatch->dump(s);
}
+bool Scheduler::updateFrameRateOverrides(
+ scheduler::RefreshRateConfigs::GlobalSignals consideredSignals, Fps displayRefreshRate) {
+ if (!mRefreshRateConfigs.supportsFrameRateOverride()) {
+ return false;
+ }
+
+ if (consideredSignals.touch) {
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ const bool changed = !mFrameRateOverridesByContent.empty();
+ mFrameRateOverridesByContent.clear();
+ return changed;
+ }
+
+ if (!consideredSignals.idle) {
+ const auto frameRateOverrides =
+ mRefreshRateConfigs.getFrameRateOverrides(mFeatures.contentRequirements,
+ displayRefreshRate);
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ if (!std::equal(mFrameRateOverridesByContent.begin(), mFrameRateOverridesByContent.end(),
+ frameRateOverrides.begin(), frameRateOverrides.end(),
+ [](const std::pair<uid_t, Fps>& a, const std::pair<uid_t, Fps>& b) {
+ return a.first == b.first && a.second.equalsWithMargin(b.second);
+ })) {
+ mFrameRateOverridesByContent = frameRateOverrides;
+ return true;
+ }
+ }
+ return false;
+}
+
template <class T>
bool Scheduler::handleTimerStateChanged(T* currentState, T newState) {
HwcConfigIndexType newConfigId;
+ bool refreshRateChanged = false;
+ bool frameRateOverridesChanged;
scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
{
std::lock_guard<std::mutex> lock(mFeatureStateLock);
@@ -688,20 +798,32 @@
}
*currentState = newState;
newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
+ const RefreshRate& newRefreshRate =
+ mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+ frameRateOverridesChanged =
+ updateFrameRateOverrides(consideredSignals, newRefreshRate.getFps());
if (mFeatures.configId == newConfigId) {
// We don't need to change the config, but we might need to send an event
// about a config change, since it was suppressed due to a previous idleConsidered
if (!consideredSignals.idle) {
dispatchCachedReportedConfig();
}
- return consideredSignals.touch;
+ } else {
+ mFeatures.configId = newConfigId;
+ refreshRateChanged = true;
}
- mFeatures.configId = newConfigId;
}
- const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
- mSchedulerCallback.changeRefreshRate(newRefreshRate,
- consideredSignals.idle ? ConfigEvent::None
- : ConfigEvent::Changed);
+ if (refreshRateChanged) {
+ const RefreshRate& newRefreshRate =
+ mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+
+ mSchedulerCallback.changeRefreshRate(newRefreshRate,
+ consideredSignals.idle ? ConfigEvent::None
+ : ConfigEvent::Changed);
+ }
+ if (frameRateOverridesChanged) {
+ mSchedulerCallback.triggerOnFrameRateOverridesChanged();
+ }
return consideredSignals.touch;
}
@@ -775,4 +897,17 @@
}
}
+void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
+ if (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f) {
+ return;
+ }
+
+ std::lock_guard lock(mFrameRateOverridesMutex);
+ if (frameRateOverride.frameRateHz != 0.f) {
+ mFrameRateOverridesFromBackdoor[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
+ } else {
+ mFrameRateOverridesFromBackdoor.erase(frameRateOverride.uid);
+ }
+}
+
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index f16e1f9..cae3fe7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -26,8 +26,9 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <ui/GraphicTypes.h>
-#pragma clang diagnostic pop
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include "EventThread.h"
#include "LayerHistory.h"
@@ -60,6 +61,7 @@
scheduler::RefreshRateConfigEvent) = 0;
virtual void repaintEverythingForHWC() = 0;
virtual void kernelTimerChanged(bool expired) = 0;
+ virtual void triggerOnFrameRateOverridesChanged() = 0;
protected:
~ISchedulerCallback() = default;
@@ -93,14 +95,14 @@
void onScreenAcquired(ConnectionHandle);
void onScreenReleased(ConnectionHandle);
- void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId,
- std::vector<FrameRateOverride>);
+ void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
+ EXCLUDES(mFrameRateOverridesMutex) EXCLUDES(mConnectionsLock);
// Modifies work duration in the event thread.
void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration);
- void getDisplayStatInfo(DisplayStatInfo* stats, nsecs_t now);
+ DisplayStatInfo getDisplayStatInfo(nsecs_t now);
// Returns injector handle if injection has toggled, or an invalid handle otherwise.
ConnectionHandle enableVSyncInjection(bool enable);
@@ -144,7 +146,8 @@
// Returns true if a given vsync timestamp is considered valid vsync
// for a given uid
- bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const;
+ bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const
+ EXCLUDES(mFrameRateOverridesMutex);
void dump(std::string&) const;
void dump(ConnectionHandle, std::string&) const;
@@ -169,6 +172,10 @@
std::chrono::nanoseconds readyDuration,
bool traceVsync = true);
+ // Stores the preferred refresh rate that an app should run at.
+ // FrameRateOverride.refreshRateHz == 0 means no preference.
+ void setPreferredRefreshRateForUid(FrameRateOverride) EXCLUDES(mFrameRateOverridesMutex);
+
private:
friend class TestableScheduler;
@@ -226,6 +233,12 @@
REQUIRES(mFeatureStateLock);
void dispatchCachedReportedConfig() REQUIRES(mFeatureStateLock);
+ bool updateFrameRateOverrides(scheduler::RefreshRateConfigs::GlobalSignals consideredSignals,
+ Fps displayRefreshRate) REQUIRES(mFeatureStateLock)
+ EXCLUDES(mFrameRateOverridesMutex);
+
+ std::optional<Fps> getFrameRateOverride(uid_t uid) const EXCLUDES(mFrameRateOverridesMutex);
+ impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const;
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
struct Connection {
@@ -264,7 +277,7 @@
// In order to make sure that the features don't override themselves, we need a state machine
// to keep track which feature requested the config change.
- std::mutex mFeatureStateLock;
+ mutable std::mutex mFeatureStateLock;
struct {
TimerState idleTimer = TimerState::Reset;
@@ -295,6 +308,17 @@
static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
const std::unique_ptr<PredictedVsyncTracer> mPredictedVsyncTracer;
+
+ // The frame rate override lists need their own mutex as they are being read
+ // by SurfaceFlinger, Scheduler and EventThread (as a callback) to prevent deadlocks
+ mutable std::mutex mFrameRateOverridesMutex;
+
+ // mappings between a UID and a preferred refresh rate that this app would
+ // run at.
+ scheduler::RefreshRateConfigs::UidToFrameRateOverride mFrameRateOverridesByContent
+ GUARDED_BY(mFrameRateOverridesMutex);
+ scheduler::RefreshRateConfigs::UidToFrameRateOverride mFrameRateOverridesFromBackdoor
+ GUARDED_BY(mFrameRateOverridesMutex);
};
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index a6f9372..7cca206 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
//#define LOG_NDEBUG 0
#include "VSyncPredictor.h"
@@ -339,3 +343,5 @@
} // namespace android::scheduler
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index 8431323..cb57aea 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -16,14 +16,19 @@
#include "VsyncConfiguration.h"
-#include <cutils/properties.h>
-
+#include <chrono>
+#include <cinttypes>
#include <optional>
+#include <cutils/properties.h>
+#include <log/log.h>
+
#include "SurfaceFlingerProperties.h"
namespace {
+using namespace std::chrono_literals;
+
std::optional<nsecs_t> getProperty(const char* name) {
char value[PROPERTY_VALUE_MAX];
property_get(name, value, "-1");
@@ -31,19 +36,6 @@
return std::nullopt;
}
-std::vector<android::Fps> getRefreshRatesFromConfigs(
- const android::scheduler::RefreshRateConfigs& refreshRateConfigs) {
- const auto& allRefreshRates = refreshRateConfigs.getAllRefreshRates();
- std::vector<android::Fps> refreshRates;
- refreshRates.reserve(allRefreshRates.size());
-
- for (const auto& [ignored, refreshRate] : allRefreshRates) {
- refreshRates.emplace_back(refreshRate->getFps());
- }
-
- return refreshRates;
-}
-
} // namespace
namespace android::scheduler::impl {
@@ -51,25 +43,19 @@
VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {}
PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const {
- const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(),
- [&fps](const std::pair<Fps, VsyncConfigSet>& candidateFps) {
- return fps.equalsWithMargin(candidateFps.first);
- });
+ std::lock_guard lock(mLock);
+ return getConfigsForRefreshRateLocked(fps);
+}
- if (iter != mOffsets.end()) {
+PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const {
+ const auto iter = mOffsetsCache.find(fps);
+ if (iter != mOffsetsCache.end()) {
return iter->second;
}
- // Unknown refresh rate. This might happen if we get a hotplug event for an external display.
- // In this case just construct the offset.
- ALOGW("Can't find offset for %s", to_string(fps).c_str());
- return constructOffsets(fps.getPeriodNsecs());
-}
-
-void VsyncConfiguration::initializeOffsets(const std::vector<Fps>& refreshRates) {
- for (const auto fps : refreshRates) {
- mOffsets.emplace(fps, constructOffsets(fps.getPeriodNsecs()));
- }
+ const auto offset = constructOffsets(fps.getPeriodNsecs());
+ mOffsetsCache[fps] = offset;
+ return offset;
}
void VsyncConfiguration::dump(std::string& result) const {
@@ -98,10 +84,8 @@
earlyGpu.appWorkDuration.count(), earlyGpu.sfWorkDuration.count());
}
-PhaseOffsets::PhaseOffsets(const scheduler::RefreshRateConfigs& refreshRateConfigs)
- : PhaseOffsets(getRefreshRatesFromConfigs(refreshRateConfigs),
- refreshRateConfigs.getCurrentRefreshRate().getFps(),
- sysprop::vsync_event_phase_offset_ns(1000000),
+PhaseOffsets::PhaseOffsets(Fps currentRefreshRate)
+ : PhaseOffsets(currentRefreshRate, sysprop::vsync_event_phase_offset_ns(1000000),
sysprop::vsync_sf_event_phase_offset_ns(1000000),
getProperty("debug.sf.early_phase_offset_ns"),
getProperty("debug.sf.early_gl_phase_offset_ns"),
@@ -121,15 +105,17 @@
getProperty("debug.sf.phase_offset_threshold_for_next_vsync_ns")
.value_or(std::numeric_limits<nsecs_t>::max())) {}
-PhaseOffsets::PhaseOffsets(
- const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
- nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
- std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
- std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
- nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
- std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
- std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
- std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs, nsecs_t thresholdForNextVsync)
+PhaseOffsets::PhaseOffsets(Fps currentFps, nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
+ std::optional<nsecs_t> earlySfOffsetNs,
+ std::optional<nsecs_t> earlyGpuSfOffsetNs,
+ std::optional<nsecs_t> earlyAppOffsetNs,
+ std::optional<nsecs_t> earlyGpuAppOffsetNs,
+ nsecs_t highFpsVsyncPhaseOffsetNs, nsecs_t highFpsSfVSyncPhaseOffsetNs,
+ std::optional<nsecs_t> highFpsEarlySfOffsetNs,
+ std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
+ std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
+ std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
+ nsecs_t thresholdForNextVsync)
: VsyncConfiguration(currentFps),
mVSyncPhaseOffsetNs(vsyncPhaseOffsetNs),
mSfVSyncPhaseOffsetNs(sfVSyncPhaseOffsetNs),
@@ -143,9 +129,7 @@
mHighFpsEarlyGpuSfOffsetNs(highFpsEarlyGpuSfOffsetNs),
mHighFpsEarlyAppOffsetNs(highFpsEarlyAppOffsetNs),
mHighFpsEarlyGpuAppOffsetNs(highFpsEarlyGpuAppOffsetNs),
- mThresholdForNextVsync(thresholdForNextVsync) {
- initializeOffsets(refreshRates);
-}
+ mThresholdForNextVsync(thresholdForNextVsync) {}
PhaseOffsets::VsyncConfigSet PhaseOffsets::constructOffsets(nsecs_t vsyncDuration) const {
if (vsyncDuration < std::chrono::nanoseconds(15ms).count()) {
@@ -361,10 +345,8 @@
};
}
-WorkDuration::WorkDuration(const scheduler::RefreshRateConfigs& refreshRateConfigs)
- : WorkDuration(getRefreshRatesFromConfigs(refreshRateConfigs),
- refreshRateConfigs.getCurrentRefreshRate().getFps(),
- getProperty("debug.sf.late.sf.duration").value_or(-1),
+WorkDuration::WorkDuration(Fps currentRefreshRate)
+ : WorkDuration(currentRefreshRate, getProperty("debug.sf.late.sf.duration").value_or(-1),
getProperty("debug.sf.late.app.duration").value_or(-1),
getProperty("debug.sf.early.sf.duration").value_or(mSfDuration),
getProperty("debug.sf.early.app.duration").value_or(mAppDuration),
@@ -373,17 +355,15 @@
validateSysprops();
}
-WorkDuration::WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
- nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+WorkDuration::WorkDuration(Fps currentRefreshRate, nsecs_t sfDuration, nsecs_t appDuration,
+ nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration)
- : VsyncConfiguration(currentFps),
+ : VsyncConfiguration(currentRefreshRate),
mSfDuration(sfDuration),
mAppDuration(appDuration),
mSfEarlyDuration(sfEarlyDuration),
mAppEarlyDuration(appEarlyDuration),
mSfEarlyGpuDuration(sfEarlyGpuDuration),
- mAppEarlyGpuDuration(appEarlyGpuDuration) {
- initializeOffsets(refreshRates);
-}
+ mAppEarlyGpuDuration(appEarlyGpuDuration) {}
} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index a120e97..d9d206d 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -16,12 +16,14 @@
#pragma once
+#include <mutex>
+#include <type_traits>
#include <unordered_map>
+#include <vector>
#include <utils/Timers.h>
#include "Fps.h"
-#include "RefreshRateConfigs.h"
#include "VsyncModulator.h"
namespace android::scheduler {
@@ -39,9 +41,9 @@
virtual ~VsyncConfiguration() = default;
virtual VsyncConfigSet getCurrentConfigs() const = 0;
virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0;
+ virtual void reset() = 0;
virtual void setRefreshRateFps(Fps fps) = 0;
-
virtual void dump(std::string& result) const = 0;
};
@@ -57,26 +59,39 @@
explicit VsyncConfiguration(Fps currentFps);
// Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
- VsyncConfigSet getConfigsForRefreshRate(Fps fps) const override;
+ VsyncConfigSet getConfigsForRefreshRate(Fps fps) const override EXCLUDES(mLock);
// Returns early, early GL, and late offsets for Apps and SF.
- VsyncConfigSet getCurrentConfigs() const override {
- return getConfigsForRefreshRate(mRefreshRateFps);
+ VsyncConfigSet getCurrentConfigs() const override EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
+ return getConfigsForRefreshRateLocked(mRefreshRateFps);
+ }
+
+ // Cleans the internal cache.
+ void reset() override EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
+ mOffsetsCache.clear();
}
// This function should be called when the device is switching between different
// refresh rates, to properly update the offsets.
- void setRefreshRateFps(Fps fps) override { mRefreshRateFps = fps; }
+ void setRefreshRateFps(Fps fps) override EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
+ mRefreshRateFps = fps;
+ }
// Returns current offsets in human friendly format.
void dump(std::string& result) const override;
protected:
- void initializeOffsets(const std::vector<Fps>& refreshRates);
virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0;
- std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets> mOffsets;
- std::atomic<Fps> mRefreshRateFps;
+ VsyncConfigSet getConfigsForRefreshRateLocked(Fps fps) const REQUIRES(mLock);
+
+ mutable std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets>
+ mOffsetsCache GUARDED_BY(mLock);
+ std::atomic<Fps> mRefreshRateFps GUARDED_BY(mLock);
+ mutable std::mutex mLock;
};
/*
@@ -85,13 +100,13 @@
*/
class PhaseOffsets : public VsyncConfiguration {
public:
- explicit PhaseOffsets(const scheduler::RefreshRateConfigs&);
+ explicit PhaseOffsets(Fps currentRefreshRate);
protected:
// Used for unit tests
- PhaseOffsets(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
- nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
- std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
+ PhaseOffsets(Fps currentRefreshRate, nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
+ std::optional<nsecs_t> earlySfOffsetNs, std::optional<nsecs_t> earlyGpuSfOffsetNs,
+ std::optional<nsecs_t> earlyAppOffsetNs,
std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
@@ -128,13 +143,12 @@
*/
class WorkDuration : public VsyncConfiguration {
public:
- explicit WorkDuration(const scheduler::RefreshRateConfigs&);
+ explicit WorkDuration(Fps currentRefrshRate);
protected:
// Used for unit tests
- WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
- nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
- nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration);
+ WorkDuration(Fps currentFps, nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
+ nsecs_t appEarlyDuration, nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration);
private:
VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const override;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e9416d6..6967f69 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
//#define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -47,8 +48,7 @@
#include <configstore/Utils.h>
#include <cutils/compiler.h>
#include <cutils/properties.h>
-#include <dlfcn.h>
-#include <errno.h>
+#include <ftl/future.h>
#include <gui/BufferQueue.h>
#include <gui/DebugEGLImageTracker.h>
#include <gui/IDisplayEventConnection.h>
@@ -81,6 +81,7 @@
#include <utils/misc.h>
#include <algorithm>
+#include <cerrno>
#include <cinttypes>
#include <cmath>
#include <cstdint>
@@ -112,7 +113,6 @@
#include "LayerVector.h"
#include "MonitoredProducer.h"
#include "NativeWindowSurface.h"
-#include "Promise.h"
#include "RefreshRateOverlay.h"
#include "RegionSamplingThread.h"
#include "Scheduler/DispSyncSource.h"
@@ -340,7 +340,7 @@
mInterceptor(mFactory.createSurfaceInterceptor()),
mTimeStats(std::make_shared<impl::TimeStats>()),
mFrameTracer(mFactory.createFrameTracer()),
- mFrameTimeline(mFactory.createFrameTimeline(mTimeStats)),
+ mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, getpid())),
mEventQueue(mFactory.createMessageQueue()),
mCompositionEngine(mFactory.createCompositionEngine()),
mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
@@ -711,16 +711,17 @@
// Sending maxFrameBufferAcquiredBuffers as the cache size is tightly tuned to single-display.
mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(
renderengine::RenderEngineCreationArgs::Builder()
- .setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
- .setImageCacheSize(maxFrameBufferAcquiredBuffers)
- .setUseColorManagerment(useColorManagement)
- .setEnableProtectedContext(enable_protected_contents(false))
- .setPrecacheToneMapperShaderOnly(false)
- .setSupportsBackgroundBlur(mSupportsBlur)
- .setContextPriority(useContextPriority
- ? renderengine::RenderEngine::ContextPriority::HIGH
- : renderengine::RenderEngine::ContextPriority::MEDIUM)
- .build()));
+ .setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
+ .setImageCacheSize(maxFrameBufferAcquiredBuffers)
+ .setUseColorManagerment(useColorManagement)
+ .setEnableProtectedContext(enable_protected_contents(false))
+ .setPrecacheToneMapperShaderOnly(false)
+ .setSupportsBackgroundBlur(mSupportsBlur)
+ .setContextPriority(
+ useContextPriority
+ ? renderengine::RenderEngine::ContextPriority::REALTIME
+ : renderengine::RenderEngine::ContextPriority::MEDIUM)
+ .build()));
mCompositionEngine->setTimeStats(mTimeStats);
mCompositionEngine->setHwComposer(getFactory().createHWComposer(getBE().mHwcServiceName));
mCompositionEngine->getHwComposer().setConfiguration(this, getBE().mComposerSequenceId);
@@ -963,7 +964,7 @@
return BAD_VALUE;
}
- mScheduler->getDisplayStatInfo(stats, systemTime());
+ *stats = mScheduler->getDisplayStatInfo(systemTime());
return NO_ERROR;
}
@@ -1431,8 +1432,7 @@
Mutex::Autolock lock(mStateLock);
if (const auto handle = mScheduler->enableVSyncInjection(enable)) {
- mEventQueue->setEventConnection(enable ? mScheduler->getEventConnection(handle)
- : nullptr);
+ mEventQueue->setInjector(enable ? mScheduler->getEventConnection(handle) : nullptr);
}
}).wait();
@@ -1441,7 +1441,8 @@
status_t SurfaceFlinger::injectVSync(nsecs_t when) {
Mutex::Autolock lock(mStateLock);
- const auto expectedPresent = calculateExpectedPresentTime(when);
+ const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(when);
+ const auto expectedPresent = calculateExpectedPresentTime(stats);
return mScheduler->injectVSync(when, /*expectedVSyncTime=*/expectedPresent,
/*deadlineTimestamp=*/expectedPresent)
? NO_ERROR
@@ -1512,12 +1513,12 @@
return BAD_VALUE;
}
- return promise::chain(schedule([=]() MAIN_THREAD {
+ return ftl::chain(schedule([=]() MAIN_THREAD {
if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
return getHwComposer().setDisplayBrightness(*displayId, brightness);
} else {
ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
- return promise::yield<status_t>(NAME_NOT_FOUND);
+ return ftl::yield<status_t>(NAME_NOT_FOUND);
}
}))
.then([](std::future<status_t> task) { return task; })
@@ -1720,9 +1721,7 @@
return fence->getSignalTime();
}
-nsecs_t SurfaceFlinger::calculateExpectedPresentTime(nsecs_t now) const {
- DisplayStatInfo stats;
- mScheduler->getDisplayStatInfo(&stats, now);
+nsecs_t SurfaceFlinger::calculateExpectedPresentTime(DisplayStatInfo stats) const {
// Inflate the expected present time if we're targetting the next vsync.
return mVsyncModulator->getVsyncConfig().sfOffset > 0 ? stats.vsyncTime
: stats.vsyncTime + stats.vsyncPeriod;
@@ -1770,8 +1769,7 @@
// Add some slop to correct for drift. This should generally be
// smaller than a typical frame duration, but should not be so small
// that it reports reasonable drift as a missed frame.
- DisplayStatInfo stats;
- mScheduler->getDisplayStatInfo(&stats, systemTime());
+ const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(systemTime());
const nsecs_t frameMissedSlop = stats.vsyncPeriod / 2;
const nsecs_t previousPresentTime = previousFramePresentTime();
const TracedOrdinal<bool> frameMissed = {"PrevFrameMissed",
@@ -1872,7 +1870,7 @@
const bool tracePreComposition = mTracingEnabled && !mTracePostComposition;
ConditionalLockGuard<std::mutex> lock(mTracingLock, tracePreComposition);
- mFrameTimeline->setSfWakeUp(vsyncId, frameStart);
+ mFrameTimeline->setSfWakeUp(vsyncId, frameStart, stats.vsyncPeriod);
refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
@@ -2148,8 +2146,7 @@
auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFences[0]);
getBE().mDisplayTimeline.push(presentFenceTime);
- DisplayStatInfo stats;
- mScheduler->getDisplayStatInfo(&stats, systemTime());
+ const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(systemTime());
// We use the CompositionEngine::getLastFrameRefreshTimestamp() which might
// be sampled a little later than when we started doing work for this frame,
@@ -2619,6 +2616,17 @@
if (currentState.physical) {
const auto display = getDisplayDeviceLocked(displayToken);
setPowerModeInternal(display, hal::PowerMode::ON);
+
+ // TODO(b/175678251) Call a listener instead.
+ if (currentState.physical->hwcDisplayId == getHwComposer().getInternalHwcDisplayId()) {
+ const auto displayId = currentState.physical->id;
+ const auto configs = getHwComposer().getConfigs(displayId);
+ mVsyncConfiguration->reset();
+ updatePhaseConfiguration(mRefreshRateConfigs->getCurrentRefreshRate());
+ if (mRefreshRateOverlay) {
+ mRefreshRateOverlay->reset();
+ }
+ }
}
return;
}
@@ -2880,12 +2888,21 @@
Scheduler::ConfigEvent event) {
// If this is called from the main thread mStateLock must be locked before
// Currently the only way to call this function from the main thread is from
- // Sheduler::chooseRefreshRateForContent
+ // Scheduler::chooseRefreshRateForContent
ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
changeRefreshRateLocked(refreshRate, event);
}
+void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
+ PhysicalDisplayId displayId = [&]() {
+ ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
+ return getDefaultDisplayDeviceLocked()->getPhysicalId();
+ }();
+
+ mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
+}
+
void SurfaceFlinger::initScheduler(PhysicalDisplayId primaryDisplayId) {
if (mScheduler) {
// In practice it's not allowed to hotplug in/out the primary display once it's been
@@ -2904,7 +2921,7 @@
std::make_unique<scheduler::RefreshRateStats>(*mTimeStats, currRefreshRate.getFps(),
hal::PowerMode::OFF);
- mVsyncConfiguration = getFactory().createVsyncConfiguration(*mRefreshRateConfigs);
+ mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate.getFps());
mVsyncModulator.emplace(mVsyncConfiguration->getCurrentConfigs());
// start the EventThread
@@ -3243,8 +3260,9 @@
applyTransactionState(transaction.frameTimelineVsyncId, transaction.states,
transaction.displays, transaction.flags,
mPendingInputWindowCommands, transaction.desiredPresentTime,
- transaction.buffer, transaction.postTime,
- transaction.privileged, transaction.hasListenerCallbacks,
+ transaction.isAutoTimestamp, transaction.buffer,
+ transaction.postTime, transaction.privileged,
+ transaction.hasListenerCallbacks,
transaction.listenerCallbacks, transaction.originPid,
transaction.originUid, transaction.id, /*isMainThread*/ true);
transactionQueue.pop();
@@ -3268,14 +3286,16 @@
bool SurfaceFlinger::transactionIsReadyToBeApplied(int64_t desiredPresentTime,
- const Vector<ComposerState>& states) {
+ const Vector<ComposerState>& states,
+ bool updateTransactionCounters) {
const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
+ bool ready = true;
// Do not present if the desiredPresentTime has not passed unless it is more than one second
// in the future. We ignore timestamps more than 1 second in the future for stability reasons.
- if (desiredPresentTime >= 0 && desiredPresentTime >= expectedPresentTime &&
+ if (desiredPresentTime > 0 && desiredPresentTime >= expectedPresentTime &&
desiredPresentTime < expectedPresentTime + s2ns(1)) {
- return false;
+ ready = false;
}
for (const ComposerState& state : states) {
@@ -3284,17 +3304,33 @@
continue;
}
if (s.acquireFence && s.acquireFence->getStatus() == Fence::Status::Unsignaled) {
- return false;
+ ready = false;
+ }
+ sp<Layer> layer = nullptr;
+ if (s.surface) {
+ layer = fromHandleLocked(s.surface).promote();
+ } else {
+ ALOGW("Transaction with buffer, but no Layer?");
+ continue;
+ }
+ if (layer && !mScheduler->isVsyncValid(expectedPresentTime, layer->getOwnerUid())) {
+ ATRACE_NAME("!isVsyncValidForUid");
+ ready = false;
+ }
+ if (updateTransactionCounters) {
+ // See BufferStateLayer::mPendingBufferTransactions
+ if (layer) layer->incrementPendingBufferCount();
+
}
}
- return true;
+ return ready;
}
status_t SurfaceFlinger::setTransactionState(
int64_t frameTimelineVsyncId, const Vector<ComposerState>& states,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
ATRACE_CALL();
@@ -3324,24 +3360,35 @@
const bool pendingTransactions = itr != mTransactionQueues.end();
// Expected present time is computed and cached on invalidate, so it may be stale.
if (!pendingTransactions) {
- mExpectedPresentTime = calculateExpectedPresentTime(systemTime());
+ const auto now = systemTime();
+ const bool nextVsyncPending = now < mExpectedPresentTime.load();
+ const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(now);
+ mExpectedPresentTime = calculateExpectedPresentTime(stats);
+ // The transaction might arrive just before the next vsync but after
+ // invalidate was called. In that case we need to get the next vsync
+ // afterwards.
+ if (nextVsyncPending) {
+ mExpectedPresentTime += stats.vsyncPeriod;
+ }
}
IPCThreadState* ipc = IPCThreadState::self();
const int originPid = ipc->getCallingPid();
const int originUid = ipc->getCallingUid();
- if (pendingTransactions || !transactionIsReadyToBeApplied(desiredPresentTime, states)) {
+ if (pendingTransactions ||
+ !transactionIsReadyToBeApplied(isAutoTimestamp ? 0 : desiredPresentTime, states, true)) {
mTransactionQueues[applyToken].emplace(frameTimelineVsyncId, states, displays, flags,
- desiredPresentTime, uncacheBuffer, postTime,
- privileged, hasListenerCallbacks, listenerCallbacks,
- originPid, originUid, transactionId);
+ desiredPresentTime, isAutoTimestamp, uncacheBuffer,
+ postTime, privileged, hasListenerCallbacks,
+ listenerCallbacks, originPid, originUid,
+ transactionId);
setTransactionFlags(eTransactionFlushNeeded);
return NO_ERROR;
}
applyTransactionState(frameTimelineVsyncId, states, displays, flags, inputWindowCommands,
- desiredPresentTime, uncacheBuffer, postTime, privileged,
+ desiredPresentTime, isAutoTimestamp, uncacheBuffer, postTime, privileged,
hasListenerCallbacks, listenerCallbacks, originPid, originUid,
transactionId, /*isMainThread*/ false);
return NO_ERROR;
@@ -3351,9 +3398,10 @@
int64_t frameTimelineVsyncId, const Vector<ComposerState>& states,
const Vector<DisplayState>& displays, uint32_t flags,
const InputWindowCommands& inputWindowCommands, const int64_t desiredPresentTime,
- const client_cache_t& uncacheBuffer, const int64_t postTime, bool privileged,
- bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
- int originPid, int originUid, uint64_t transactionId, bool isMainThread) {
+ bool isAutoTimestamp, const client_cache_t& uncacheBuffer, const int64_t postTime,
+ bool privileged, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, int originPid, int originUid,
+ uint64_t transactionId, bool isMainThread) {
uint32_t transactionFlags = 0;
if (flags & eAnimation) {
@@ -3387,12 +3435,13 @@
std::unordered_set<ListenerCallbacks, ListenerCallbacksHash> listenerCallbacksWithSurfaces;
uint32_t clientStateFlags = 0;
for (const ComposerState& state : states) {
- clientStateFlags |=
- setClientStateLocked(frameTimelineVsyncId, state, desiredPresentTime, postTime,
- privileged, listenerCallbacksWithSurfaces);
+ clientStateFlags |= setClientStateLocked(frameTimelineVsyncId, state, desiredPresentTime,
+ isAutoTimestamp, postTime, privileged,
+ listenerCallbacksWithSurfaces);
if ((flags & eAnimation) && state.state.surface) {
if (const auto layer = fromHandleLocked(state.state.surface).promote(); layer) {
- mScheduler->recordLayerHistory(layer.get(), desiredPresentTime,
+ mScheduler->recordLayerHistory(layer.get(),
+ isAutoTimestamp ? 0 : desiredPresentTime,
LayerHistory::LayerUpdateType::AnimationTX);
}
}
@@ -3567,7 +3616,7 @@
uint32_t SurfaceFlinger::setClientStateLocked(
int64_t frameTimelineVsyncId, const ComposerState& composerState,
- int64_t desiredPresentTime, int64_t postTime, bool privileged,
+ int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, bool privileged,
std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& listenerCallbacks) {
const layer_state_t& s = composerState.state;
@@ -3878,8 +3927,8 @@
? s.frameNumber
: layer->getHeadFrameNumber(-1 /* expectedPresentTime */) + 1;
- if (layer->setBuffer(buffer, s.acquireFence, postTime, desiredPresentTime, s.cachedBuffer,
- frameNumber)) {
+ if (layer->setBuffer(buffer, s.acquireFence, postTime, desiredPresentTime, isAutoTimestamp,
+ s.cachedBuffer, frameNumber)) {
flags |= eTraversalNeeded;
}
}
@@ -4157,7 +4206,7 @@
d.height = 0;
displays.add(d);
setTransactionState(ISurfaceComposer::INVALID_VSYNC_ID, state, displays, 0, nullptr,
- mPendingInputWindowCommands, -1, {}, false, {},
+ mPendingInputWindowCommands, systemTime(), true, {}, false, {},
0 /* Undefined transactionId */);
setPowerModeInternal(display, hal::PowerMode::ON);
@@ -4910,10 +4959,11 @@
case CAPTURE_LAYERS:
case CAPTURE_DISPLAY:
case SET_DISPLAY_BRIGHTNESS:
- case SET_FRAME_TIMELINE_VSYNC: {
+ case SET_FRAME_TIMELINE_VSYNC:
+ // This is not sensitive information, so should not require permission control.
+ case GET_GPU_CONTEXT_PRIORITY: {
return OK;
}
-
case ADD_REGION_SAMPLING_LISTENER:
case REMOVE_REGION_SAMPLING_LISTENER: {
// codes that require permission check
@@ -5262,12 +5312,17 @@
return NO_ERROR;
}
case 1035: {
- n = data.readInt32();
+ const int newConfigId = data.readInt32();
mDebugDisplayConfigSetByBackdoor = false;
- const auto numConfigs = mRefreshRateConfigs->getAllRefreshRates().size();
- if (n >= 0 && n < numConfigs) {
+ const auto displayId = getInternalDisplayId();
+ if (!displayId) {
+ ALOGE("No internal display found.");
+ return NO_ERROR;
+ }
+ const auto numConfigs = getHwComposer().getConfigs(*displayId).size();
+ if (newConfigId >= 0 && newConfigId < numConfigs) {
const auto displayToken = getInternalDisplayToken();
- status_t result = setActiveConfig(displayToken, n);
+ status_t result = setActiveConfig(displayToken, newConfigId);
if (result != NO_ERROR) {
return result;
}
@@ -5321,13 +5376,10 @@
auto inUid = static_cast<uid_t>(data.readInt32());
const auto refreshRate = data.readFloat();
- mRefreshRateConfigs->setPreferredRefreshRateForUid(
- FrameRateOverride{inUid, refreshRate});
- const auto mappings = mRefreshRateConfigs->getFrameRateOverrides();
- mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId,
- std::move(mappings));
- }
+ mScheduler->setPreferredRefreshRateForUid(FrameRateOverride{inUid, refreshRate});
+ mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
return NO_ERROR;
+ }
}
}
return err;
@@ -5519,7 +5571,7 @@
}
}
- RenderAreaFuture renderAreaFuture = promise::defer([=] {
+ RenderAreaFuture renderAreaFuture = ftl::defer([=] {
return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, dataspace,
args.useIdentityTransform, args.captureSecureLayers);
});
@@ -5553,7 +5605,7 @@
pickDataspaceFromColorMode(display->getCompositionDisplay()->getState().colorMode);
}
- RenderAreaFuture renderAreaFuture = promise::defer([=] {
+ RenderAreaFuture renderAreaFuture = ftl::defer([=] {
return DisplayRenderArea::create(displayWeak, Rect(), size, dataspace,
false /* useIdentityTransform */,
false /* captureSecureLayers */);
@@ -5657,7 +5709,7 @@
}
bool childrenOnly = args.childrenOnly;
- RenderAreaFuture renderAreaFuture = promise::defer([=]() -> std::unique_ptr<RenderArea> {
+ RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
childrenOnly, layerStackSpaceRect,
captureSecureLayers);
@@ -6335,6 +6387,10 @@
return NO_ERROR;
}
+int SurfaceFlinger::getGPUContextPriority() {
+ return getRenderEngine().getContextPriority();
+}
+
} // namespace android
#if defined(__gl_h_)
@@ -6346,4 +6402,4 @@
#endif
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 0509247..9f0f2ea 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -341,7 +341,7 @@
virtual uint32_t setClientStateLocked(
int64_t frameTimelineVsyncId, const ComposerState& composerState,
- int64_t desiredPresentTime, int64_t postTime, bool privileged,
+ int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, bool privileged,
std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& listenerCallbacks)
REQUIRES(mStateLock);
virtual void commitTransactionLocked();
@@ -434,8 +434,9 @@
struct TransactionState {
TransactionState(int64_t frameTimelineVsyncId, const Vector<ComposerState>& composerStates,
const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
- int64_t desiredPresentTime, const client_cache_t& uncacheBuffer,
- int64_t postTime, bool privileged, bool hasListenerCallbacks,
+ int64_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& uncacheBuffer, int64_t postTime, bool privileged,
+ bool hasListenerCallbacks,
std::vector<ListenerCallbacks> listenerCallbacks, int originPid,
int originUid, uint64_t transactionId)
: frameTimelineVsyncId(frameTimelineVsyncId),
@@ -443,6 +444,7 @@
displays(displayStates),
flags(transactionFlags),
desiredPresentTime(desiredPresentTime),
+ isAutoTimestamp(isAutoTimestamp),
buffer(uncacheBuffer),
postTime(postTime),
privileged(privileged),
@@ -457,6 +459,7 @@
Vector<DisplayState> displays;
uint32_t flags;
const int64_t desiredPresentTime;
+ const bool isAutoTimestamp;
client_cache_t buffer;
const int64_t postTime;
bool privileged;
@@ -520,8 +523,8 @@
const Vector<DisplayState>& displays, uint32_t flags,
const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
- int64_t desiredPresentTime, const client_cache_t& uncacheBuffer,
- bool hasListenerCallbacks,
+ int64_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) override;
void bootFinished() override;
@@ -608,6 +611,8 @@
status_t addTransactionTraceListener(
const sp<gui::ITransactionTraceListener>& listener) override;
+ int getGPUContextPriority() override;
+
// Implements IBinder::DeathRecipient.
void binderDied(const wp<IBinder>& who) override;
@@ -639,6 +644,8 @@
void repaintEverythingForHWC() override;
// Called when kernel idle timer has expired. Used to update the refresh rate overlay.
void kernelTimerChanged(bool expired) override;
+ // Called when the frame rate override list changed to trigger an event.
+ void triggerOnFrameRateOverridesChanged() override;
// Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
void toggleKernelIdleTimer();
// Keeps track of whether the kernel idle timer is currently enabled, so we don't have to
@@ -719,7 +726,7 @@
void applyTransactionState(int64_t frameTimelineVsyncId, const Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags,
const InputWindowCommands& inputWindowCommands,
- const int64_t desiredPresentTime,
+ const int64_t desiredPresentTime, bool isAutoTimestamp,
const client_cache_t& uncacheBuffer, const int64_t postTime,
bool privileged, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -743,7 +750,8 @@
void commitTransaction() REQUIRES(mStateLock);
void commitOffscreenLayers();
bool transactionIsReadyToBeApplied(int64_t desiredPresentTime,
- const Vector<ComposerState>& states);
+ const Vector<ComposerState>& states,
+ bool updateTransactionCounters = false) REQUIRES(mStateLock);
uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
REQUIRES(mStateLock);
@@ -857,26 +865,12 @@
/*
* H/W composer
*/
-
- // The current hardware composer interface.
- //
- // The following thread safety rules apply when accessing mHwc, either
- // directly or via getHwComposer():
- //
- // 1. When recreating mHwc, acquire mStateLock. Recreating mHwc must only be
- // done on the main thread.
- //
- // 2. When accessing mHwc on the main thread, it's not necessary to acquire
- // mStateLock.
- //
- // 3. When accessing mHwc on a thread other than the main thread, we always
+ // The following thread safety rules apply when accessing HWComposer:
+ // 1. When reading display state from HWComposer on the main thread, it's not necessary to
+ // acquire mStateLock.
+ // 2. When accessing HWComposer on a thread other than the main thread, we always
// need to acquire mStateLock. This is because the main thread could be
- // in the process of destroying the current mHwc instance.
- //
- // The above thread safety rules only apply to SurfaceFlinger.cpp. In
- // SurfaceFlinger_hwc1.cpp we create mHwc at surface flinger init and never
- // destroy it, so it's always safe to access mHwc from any thread without
- // acquiring mStateLock.
+ // in the process of writing display state, e.g. creating or destroying a display.
HWComposer& getHwComposer() const;
/*
@@ -942,7 +936,7 @@
// Calculates the expected present time for this frame. For negative offsets, performs a
// correction using the predicted vsync for the next frame instead.
- nsecs_t calculateExpectedPresentTime(nsecs_t now) const;
+ nsecs_t calculateExpectedPresentTime(DisplayStatInfo) const;
/*
* Display identification
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index bc487ac..4a75180 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -56,11 +56,11 @@
}
std::unique_ptr<scheduler::VsyncConfiguration> DefaultFactory::createVsyncConfiguration(
- const scheduler::RefreshRateConfigs& refreshRateConfigs) {
+ Fps currentRefreshRate) {
if (property_get_bool("debug.sf.use_phase_offsets_as_durations", false)) {
- return std::make_unique<scheduler::impl::WorkDuration>(refreshRateConfigs);
+ return std::make_unique<scheduler::impl::WorkDuration>(currentRefreshRate);
} else {
- return std::make_unique<scheduler::impl::PhaseOffsets>(refreshRateConfigs);
+ return std::make_unique<scheduler::impl::PhaseOffsets>(currentRefreshRate);
}
}
@@ -136,8 +136,8 @@
}
std::unique_ptr<frametimeline::FrameTimeline> DefaultFactory::createFrameTimeline(
- std::shared_ptr<TimeStats> timeStats) {
- return std::make_unique<frametimeline::impl::FrameTimeline>(timeStats);
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) {
+ return std::make_unique<frametimeline::impl::FrameTimeline>(timeStats, surfaceFlingerPid);
}
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index fc45fa9..24148dd 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,7 @@
std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
std::unique_ptr<MessageQueue> createMessageQueue() override;
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
- const scheduler::RefreshRateConfigs&) override;
+ Fps currentRefreshRate) override;
std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
ISchedulerCallback&) override;
sp<SurfaceInterceptor> createSurfaceInterceptor() override;
@@ -56,7 +56,7 @@
sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) override;
std::unique_ptr<FrameTracer> createFrameTracer() override;
std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
- std::shared_ptr<TimeStats> timeStats) override;
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
};
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index deb1b52..885297f 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -16,6 +16,8 @@
#pragma once
+#include "Fps.h"
+
#include <cutils/compiler.h>
#include <utils/StrongPointer.h>
@@ -76,7 +78,7 @@
virtual std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) = 0;
virtual std::unique_ptr<MessageQueue> createMessageQueue() = 0;
virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
- const scheduler::RefreshRateConfigs&) = 0;
+ Fps currentRefreshRate) = 0;
virtual std::unique_ptr<Scheduler> createScheduler(const scheduler::RefreshRateConfigs&,
ISchedulerCallback&) = 0;
virtual sp<SurfaceInterceptor> createSurfaceInterceptor() = 0;
@@ -108,7 +110,7 @@
virtual sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) = 0;
virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
- std::shared_ptr<TimeStats> timeStats) = 0;
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
protected:
~Factory() = default;
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 2405884..f4a0319 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -680,19 +680,18 @@
static void updateJankPayload(T& t, int32_t reasons) {
t.jankPayload.totalFrames++;
- static const constexpr int32_t kValidJankyReason =
- JankType::SurfaceFlingerDeadlineMissed |
- JankType::SurfaceFlingerGpuDeadlineMissed |
- JankType::AppDeadlineMissed | JankType::Display;
+ static const constexpr int32_t kValidJankyReason = JankType::SurfaceFlingerCpuDeadlineMissed |
+ JankType::SurfaceFlingerGpuDeadlineMissed | JankType::AppDeadlineMissed |
+ JankType::DisplayHAL;
if (reasons & kValidJankyReason) {
t.jankPayload.totalJankyFrames++;
- if ((reasons & JankType::SurfaceFlingerDeadlineMissed) != 0) {
+ if ((reasons & JankType::SurfaceFlingerCpuDeadlineMissed) != 0) {
t.jankPayload.totalSFLongCpu++;
}
if ((reasons & JankType::SurfaceFlingerGpuDeadlineMissed) != 0) {
t.jankPayload.totalSFLongGpu++;
}
- if ((reasons & JankType::Display) != 0) {
+ if ((reasons & JankType::DisplayHAL) != 0) {
t.jankPayload.totalSFUnattributed++;
}
if ((reasons & JankType::AppDeadlineMissed) != 0) {
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index a83b2bf..8fac8e9 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -20,11 +20,12 @@
#include <cstdint>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <android/hardware/graphics/composer/2.4/IComposerClient.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <stats_event.h>
#include <stats_pull_atom_callback.h>
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
index fae4e94..9481cac 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/Android.bp
@@ -42,3 +42,16 @@
type: "full",
},
}
+
+// ==== java device library for timestats proto ===========================
+// Note timestats is deprecated and is only used for legacy tests
+java_library {
+ name: "timestats-proto",
+ srcs: [
+ "timestats.proto",
+ ],
+ proto: {
+ type: "lite",
+ },
+ sdk_version: "current",
+}
diff --git a/services/surfaceflinger/tests/DetachChildren_test.cpp b/services/surfaceflinger/tests/DetachChildren_test.cpp
index 9c7b1fc..abf8b1a 100644
--- a/services/surfaceflinger/tests/DetachChildren_test.cpp
+++ b/services/surfaceflinger/tests/DetachChildren_test.cpp
@@ -371,4 +371,7 @@
}
}
-} // namespace android
\ No newline at end of file
+} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 3a8b40f..55b3173 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include <gtest/gtest.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
@@ -143,3 +147,6 @@
}
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/InvalidHandles_test.cpp b/services/surfaceflinger/tests/InvalidHandles_test.cpp
index 152d2d2..58b039e 100644
--- a/services/surfaceflinger/tests/InvalidHandles_test.cpp
+++ b/services/surfaceflinger/tests/InvalidHandles_test.cpp
@@ -70,3 +70,6 @@
} // namespace
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index b87c734..6758518 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -19,6 +19,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <gtest/gtest.h>
#include <gui/ISurfaceComposer.h>
@@ -306,4 +307,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index c57ad43..782a364 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -377,6 +377,72 @@
40 /* tolerance */);
}
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurAffectedByParentAlpha) {
+ char value[PROPERTY_VALUE_MAX];
+ property_get("ro.surface_flinger.supports_background_blur", value, "0");
+ if (!atoi(value)) {
+ // This device doesn't support blurs, no-op.
+ return;
+ }
+
+ property_get("debug.renderengine.backend", value, "");
+ if (strcmp(value, "skiagl") != 0) {
+ // This device isn't using Skia render engine, no-op.
+ return;
+ }
+
+ sp<SurfaceControl> left;
+ sp<SurfaceControl> right;
+ sp<SurfaceControl> blur;
+ sp<SurfaceControl> blurParent;
+
+ const auto size = 32;
+ ASSERT_NO_FATAL_FAILURE(left = createLayer("Left", size, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(left, Color::BLUE, size, size));
+ ASSERT_NO_FATAL_FAILURE(right = createLayer("Right", size, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(right, Color::RED, size, size));
+
+ Transaction()
+ .setLayer(left, mLayerZBase + 1)
+ .setFrame(left, {0, 0, size, size})
+ .setLayer(right, mLayerZBase + 2)
+ .setPosition(right, size, 0)
+ .setFrame(right, {size, 0, size * 2, size})
+ .apply();
+
+ {
+ auto shot = getScreenCapture();
+ shot->expectColor(Rect(0, 0, size, size), Color::BLUE);
+ shot->expectColor(Rect(size, 0, size * 2, size), Color::RED);
+ }
+
+ ASSERT_NO_FATAL_FAILURE(blur = createLayer("BackgroundBlur", size * 2, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(blur, Color::TRANSPARENT, size * 2, size));
+ ASSERT_NO_FATAL_FAILURE(blurParent = createLayer("BackgroundBlurParent", size * 2, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(blurParent, Color::TRANSPARENT, size * 2, size));
+
+ Transaction()
+ .setLayer(blurParent, mLayerZBase + 3)
+ .setAlpha(blurParent, 0.5)
+ .setLayer(blur, mLayerZBase + 4)
+ .setBackgroundBlurRadius(blur, size) // set the blur radius to the size of one rect
+ .setFrame(blur, {0, 0, size * 2, size})
+ .reparent(blur, blurParent)
+ .apply();
+
+ {
+ auto shot = getScreenCapture();
+ // assert that outer sides of the red and blue rects are not blended with the other color;
+ // if the blur didn't take into account parent alpha, the outer sides would have traces of
+ // the other color
+ shot->expectColor(Rect(0, 0, size / 2, size), Color::BLUE);
+ shot->expectColor(Rect(size + size / 2, 0, size * 2, size), Color::RED);
+ // assert that middle line has blended red and blur color; adding a tolerance of 10 to
+ // account for future blur algorithm changes
+ shot->expectColor(Rect(size, 0, size + 1, size), {136, 0, 119, 255}, 10);
+ }
+}
+
TEST_P(LayerTypeAndRenderTypeTransactionTest, SetColorWithBuffer) {
sp<SurfaceControl> bufferLayer;
ASSERT_NO_FATAL_FAILURE(bufferLayer = createLayer("test", 32, 32));
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 7df3711..214a0cd 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -837,3 +837,6 @@
}
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
index 81e648a..8dc9a12 100644
--- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
+++ b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
@@ -1043,4 +1044,4 @@
}
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
index 5cbf2ef..b38032d 100644
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
+++ b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp
@@ -907,6 +907,7 @@
}
void FakeComposerClient::onSurfaceFlingerStop() {
+ mSurfaceComposer->enableVSyncInjections(false);
mSurfaceComposer->dispose();
mSurfaceComposer.clear();
}
diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
index 538b10d..bd49728 100644
--- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
+++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
// #define LOG_NDEBUG 0
#undef LOG_TAG
@@ -27,6 +28,7 @@
#include "FakeComposerUtils.h"
#include "MockComposerHal.h"
+#include <binder/Parcel.h>
#include <gui/DisplayEventReceiver.h>
#include <gui/ISurfaceComposer.h>
#include <gui/LayerDebugInfo.h>
@@ -870,6 +872,25 @@
using DisplayTest_2_1 = DisplayTest<FakeComposerService_2_1>;
+// Tests that VSYNC injection can be safely toggled while invalidating.
+TEST_F(DisplayTest_2_1, VsyncInjection) {
+ const auto flinger = ComposerService::getComposerService();
+ bool enable = true;
+
+ for (int i = 0; i < 100; i++) {
+ flinger->enableVSyncInjections(enable);
+ enable = !enable;
+
+ constexpr uint32_t kForceInvalidate = 1004;
+ android::Parcel data, reply;
+ data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+ EXPECT_EQ(NO_ERROR,
+ android::IInterface::asBinder(flinger)->transact(kForceInvalidate, data, &reply));
+
+ std::this_thread::sleep_for(5ms);
+ }
+}
+
TEST_F(DisplayTest_2_1, HotplugOneConfig) {
Test_HotplugOneConfig();
}
@@ -1997,4 +2018,4 @@
}
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index a00e959..13c7c8b 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -51,7 +51,6 @@
"LayerHistoryTest.cpp",
"LayerMetadataTest.cpp",
"MessageQueueTest.cpp",
- "PromiseTest.cpp",
"SurfaceFlinger_CreateDisplayTest.cpp",
"SurfaceFlinger_DestroyDisplayTest.cpp",
"SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp
index 1b8c76d..6bc2318 100644
--- a/services/surfaceflinger/tests/unittests/CachingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CachingTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "CachingTest"
@@ -97,4 +98,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index be9d336..83e3ba4 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "CompositionTest"
@@ -1533,4 +1534,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdGeneratorTest.cpp b/services/surfaceflinger/tests/unittests/DisplayIdGeneratorTest.cpp
index be7609a..77a3e14 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdGeneratorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayIdGeneratorTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include <gtest/gtest.h>
#include <vector>
@@ -80,3 +84,6 @@
}
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
index 02ce079..dc04b6d 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include <functional>
#include <string_view>
@@ -409,3 +413,6 @@
}
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 01cdb28..9ec2d16 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -19,6 +19,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <type_traits>
#include "DisplayIdentificationTest.h"
@@ -364,7 +365,7 @@
static constexpr DisplayType HWC_DISPLAY_TYPE = hwcDisplayType;
// The HWC active configuration id
- static constexpr int HWC_ACTIVE_CONFIG_ID = 2001;
+ static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001;
static constexpr PowerMode INIT_POWER_MODE = hal::PowerMode::ON;
static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) {
@@ -415,6 +416,45 @@
ceDisplayArgs);
}
+ static void setupHwcGetConfigsCallExpectations(DisplayTransactionTest* test) {
+ if (HWC_DISPLAY_TYPE == DisplayType::PHYSICAL) {
+ EXPECT_CALL(*test->mComposer, getDisplayConfigs(HWC_DISPLAY_ID, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{
+ HWC_ACTIVE_CONFIG_ID}),
+ Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::WIDTH, _))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<3>(DisplayVariant::WIDTH), Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::HEIGHT, _))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<3>(DisplayVariant::HEIGHT), Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::VSYNC_PERIOD, _))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<3>(DEFAULT_REFRESH_RATE), Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::DPI_X, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::DPI_Y, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
+ EXPECT_CALL(*test->mComposer,
+ getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
+ IComposerClient::Attribute::CONFIG_GROUP, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(-1), Return(Error::NONE)));
+ } else {
+ EXPECT_CALL(*test->mComposer, getDisplayConfigs(_, _)).Times(0);
+ EXPECT_CALL(*test->mComposer, getDisplayAttribute(_, _, _, _)).Times(0);
+ }
+ }
+
static void setupHwcHotplugCallExpectations(DisplayTransactionTest* test) {
constexpr auto CONNECTION_TYPE =
PhysicalDisplay::CONNECTION_TYPE == DisplayConnectionType::Internal
@@ -426,33 +466,8 @@
EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_))
.WillOnce(Return(hal::Error::NONE));
- EXPECT_CALL(*test->mComposer, getDisplayConfigs(HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(std::vector<unsigned>{HWC_ACTIVE_CONFIG_ID}),
- Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::WIDTH, _))
- .WillOnce(DoAll(SetArgPointee<3>(DisplayVariant::WIDTH), Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::HEIGHT, _))
- .WillOnce(DoAll(SetArgPointee<3>(DisplayVariant::HEIGHT), Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::VSYNC_PERIOD, _))
- .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_REFRESH_RATE), Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::DPI_X, _))
- .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::DPI_Y, _))
- .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer,
- getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
- IComposerClient::Attribute::CONFIG_GROUP, _))
- .WillOnce(DoAll(SetArgPointee<3>(-1), Return(Error::NONE)));
+
+ setupHwcGetConfigsCallExpectations(test);
if (PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
@@ -559,6 +574,11 @@
ceDisplayArgs);
}
+ static void setupHwcGetConfigsCallExpectations(DisplayTransactionTest* test) {
+ EXPECT_CALL(*test->mComposer, getDisplayConfigs(_, _)).Times(0);
+ EXPECT_CALL(*test->mComposer, getDisplayAttribute(_, _, _, _)).Times(0);
+ }
+
static void setupHwcGetActiveConfigCallExpectations(DisplayTransactionTest* test) {
EXPECT_CALL(*test->mComposer, getActiveConfig(_, _)).Times(0);
}
@@ -753,4 +773,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index ee56178..0cd50ce 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -623,3 +627,6 @@
} // namespace
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
index 36e24d2..e890a62 100644
--- a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
+++ b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
@@ -37,6 +37,7 @@
FAKE_DURATION_OFFSET_NS}};
}
+ void reset() override {}
void setRefreshRateFps(Fps) override {}
void dump(std::string&) const override {}
};
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 43b5afe..169698b 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#include "gmock/gmock-spec-builders.h"
#include "mock/MockTimeStats.h"
#undef LOG_TAG
@@ -26,6 +30,7 @@
#include <cinttypes>
using namespace std::chrono_literals;
+using testing::AtLeast;
using testing::Contains;
using FrameTimelineEvent = perfetto::protos::FrameTimelineEvent;
using ProtoDisplayFrame = perfetto::protos::FrameTimelineEvent_DisplayFrame;
@@ -62,7 +67,8 @@
void SetUp() override {
mTimeStats = std::make_shared<mock::TimeStats>();
- mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats);
+ mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, mSurfaceFlingerPid,
+ kTestThresholds);
mFrameTimeline->registerDataSource();
mTokenManager = &mFrameTimeline->mTokenManager;
maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
@@ -110,7 +116,8 @@
SurfaceFrame& getSurfaceFrame(size_t displayFrameIdx, size_t surfaceFrameIdx) {
std::lock_guard<std::mutex> lock(mFrameTimeline->mMutex);
- return *(mFrameTimeline->mDisplayFrames[displayFrameIdx]->surfaceFrames[surfaceFrameIdx]);
+ return *(mFrameTimeline->mDisplayFrames[displayFrameIdx]
+ ->getSurfaceFrames()[surfaceFrameIdx]);
}
std::shared_ptr<impl::FrameTimeline::DisplayFrame> getDisplayFrame(size_t idx) {
@@ -123,7 +130,7 @@
a.presentTime == b.presentTime;
}
- const std::unordered_map<int64_t, TimelineItem>& getPredictions() {
+ const std::map<int64_t, TokenManagerPrediction>& getPredictions() {
return mTokenManager->mPredictions;
}
@@ -138,6 +145,16 @@
FenceToFenceTimeMap fenceFactory;
uint32_t* maxDisplayFrames;
nsecs_t maxTokenRetentionTime;
+ pid_t mSurfaceFlingerPid = 666;
+ static constexpr nsecs_t kPresentThreshold =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(2ns).count();
+ static constexpr nsecs_t kDeadlineThreshold =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(2ns).count();
+ static constexpr nsecs_t kStartThreshold =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(2ns).count();
+ static constexpr JankClassificationThresholds kTestThresholds{kPresentThreshold,
+ kDeadlineThreshold,
+ kStartThreshold};
};
static const std::string sLayerNameOne = "layer1";
@@ -162,55 +179,58 @@
}
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_getOwnerPidReturnsCorrectPid) {
- auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
- auto surfaceFrame2 = mFrameTimeline->createSurfaceFrameForToken(sPidTwo, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ auto surfaceFrame2 = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidTwo, sUidOne,
+ sLayerNameOne, sLayerNameOne);
EXPECT_EQ(surfaceFrame1->getOwnerPid(), sPidOne);
EXPECT_EQ(surfaceFrame2->getOwnerPid(), sPidTwo);
}
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_noToken) {
- auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::None);
}
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) {
int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
flushTokens(systemTime() + maxTokenRetentionTime);
- auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, token1);
+ auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Expired);
}
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validToken) {
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
- auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, token1);
+ auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Valid);
EXPECT_EQ(compareTimelineItems(surfaceFrame->getPredictions(), TimelineItem(10, 20, 30)), true);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_droppedFramesNotUpdated) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
- auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, token1);
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20);
- mFrameTimeline->addSurfaceFrame(surfaceFrame1, SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->setSfWakeUp(token1, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
// Trigger a flush by calling setSfPresent for the next frame
- mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfWakeUp(token2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
auto& droppedSurfaceFrame = getSurfaceFrame(0, 0);
@@ -219,48 +239,50 @@
}
TEST_F(FrameTimelineTest, presentFenceSignaled_presentedFramesUpdated) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_)).Times(2);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 60});
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken1);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameTwo,
- sLayerNameTwo, surfaceFrameToken2);
- mFrameTimeline->setSfWakeUp(sfToken1, 22);
- mFrameTimeline->addSurfaceFrame(surfaceFrame1,
- SurfaceFrame::PresentState::Presented);
- mFrameTimeline->addSurfaceFrame(surfaceFrame2,
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameTwo, sLayerNameTwo);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
mFrameTimeline->setSfPresent(26, presentFence1);
auto displayFrame = getDisplayFrame(0);
- SurfaceFrame& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
- SurfaceFrame& presentedSurfaceFrame2 = getSurfaceFrame(0, 1);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ auto& presentedSurfaceFrame2 = getSurfaceFrame(0, 1);
presentFence1->signalForTest(42);
// Fences haven't been flushed yet, so it should be 0
- EXPECT_EQ(displayFrame->surfaceFlingerActuals.presentTime, 0);
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
EXPECT_EQ(presentedSurfaceFrame1.getActuals().presentTime, 0);
EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 0);
- EXPECT_EQ(surfaceFrame1->getToken(), surfaceFrameToken1);
- EXPECT_EQ(surfaceFrame2->getToken(), surfaceFrameToken2);
-
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto surfaceFrame3 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken2);
- mFrameTimeline->setSfWakeUp(sfToken2, 52);
- mFrameTimeline->addSurfaceFrame(surfaceFrame3, SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken2, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, 11);
+ surfaceFrame3->setPresentState(SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame3);
mFrameTimeline->setSfPresent(56, presentFence2);
displayFrame = getDisplayFrame(0);
// Fences have flushed, so the present timestamps should be updated
- EXPECT_EQ(displayFrame->surfaceFlingerActuals.presentTime, 42);
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 42);
EXPECT_EQ(presentedSurfaceFrame1.getActuals().presentTime, 42);
EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42);
EXPECT_NE(surfaceFrame1->getJankType(), std::nullopt);
@@ -270,6 +292,12 @@
TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
// Insert kMaxDisplayFrames' count of DisplayFrames to fill the deque
int frameTimeFactor = 0;
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_))
+ .Times(static_cast<int32_t>(*maxDisplayFrames));
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_))
+ .Times(static_cast<int32_t>(*maxDisplayFrames));
for (size_t i = 0; i < *maxDisplayFrames; i++) {
auto presentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions(
@@ -277,11 +305,11 @@
int64_t sfToken = mTokenManager->generateTokenForPredictions(
{22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken);
- mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor);
- mFrameTimeline->addSurfaceFrame(surfaceFrame,
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
presentFence->signalForTest(32 + frameTimeFactor);
frameTimeFactor += 30;
@@ -289,8 +317,7 @@
auto displayFrame0 = getDisplayFrame(0);
// The 0th Display Frame should have actuals 22, 27, 32
- EXPECT_EQ(compareTimelineItems(displayFrame0->surfaceFlingerActuals, TimelineItem(22, 27, 32)),
- true);
+ EXPECT_EQ(compareTimelineItems(displayFrame0->getActuals(), TimelineItem(22, 27, 32)), true);
// Add one more display frame
auto presentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -299,51 +326,54 @@
int64_t sfToken = mTokenManager->generateTokenForPredictions(
{22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken);
- mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor);
- mFrameTimeline->addSurfaceFrame(surfaceFrame, SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
presentFence->signalForTest(32 + frameTimeFactor);
displayFrame0 = getDisplayFrame(0);
// The window should have slided by 1 now and the previous 0th display frame
// should have been removed from the deque
- EXPECT_EQ(compareTimelineItems(displayFrame0->surfaceFlingerActuals, TimelineItem(52, 57, 62)),
- true);
+ EXPECT_EQ(compareTimelineItems(displayFrame0->getActuals(), TimelineItem(52, 57, 62)), true);
}
TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceAfterQueue) {
- auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, 0, "acquireFenceAfterQueue",
- "acquireFenceAfterQueue", std::nullopt);
+ auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, 0,
+ "acquireFenceAfterQueue",
+ "acquireFenceAfterQueue");
surfaceFrame->setActualQueueTime(123);
surfaceFrame->setAcquireFenceTime(456);
EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
}
TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceBeforeQueue) {
- auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, 0, "acquireFenceAfterQueue",
- "acquireFenceAfterQueue", std::nullopt);
+ auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, 0,
+ "acquireFenceAfterQueue",
+ "acquireFenceAfterQueue");
surfaceFrame->setActualQueueTime(456);
surfaceFrame->setAcquireFenceTime(123);
EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
}
TEST_F(FrameTimelineTest, setMaxDisplayFramesSetsSizeProperly) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_))
+ .Times(static_cast<int32_t>(*maxDisplayFrames + 10));
auto presentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
presentFence->signalForTest(2);
// Size shouldn't exceed maxDisplayFrames - 64
for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22);
- mFrameTimeline->addSurfaceFrame(surfaceFrame,
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfWakeUp(sfToken, 22, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
}
EXPECT_EQ(getNumberOfDisplayFrames(), *maxDisplayFrames);
@@ -351,15 +381,18 @@
// Increase the size to 256
mFrameTimeline->setMaxDisplayFrames(256);
EXPECT_EQ(*maxDisplayFrames, 256);
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_))
+ .Times(static_cast<int32_t>(*maxDisplayFrames + 10));
for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22);
- mFrameTimeline->addSurfaceFrame(surfaceFrame,
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfWakeUp(sfToken, 22, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
}
EXPECT_EQ(getNumberOfDisplayFrames(), *maxDisplayFrames);
@@ -367,26 +400,30 @@
// Shrink the size to 128
mFrameTimeline->setMaxDisplayFrames(128);
EXPECT_EQ(*maxDisplayFrames, 128);
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_))
+ .Times(static_cast<int32_t>(*maxDisplayFrames + 10));
for (size_t i = 0; i < *maxDisplayFrames + 10; i++) {
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22);
- mFrameTimeline->addSurfaceFrame(surfaceFrame,
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfWakeUp(sfToken, 22, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
}
EXPECT_EQ(getNumberOfDisplayFrames(), *maxDisplayFrames);
}
+// Tests related to TimeStats
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(sUidOne, sLayerNameOne,
- HasBit(JankType::SurfaceFlingerDeadlineMissed)));
+ HasBit(JankType::SurfaceFlingerCpuDeadlineMissed)));
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(HasBit(JankType::SurfaceFlingerDeadlineMissed)));
+ incrementJankyFrames(HasBit(JankType::SurfaceFlingerCpuDeadlineMissed)));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
@@ -397,12 +434,13 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(56ms).count(),
std::chrono::duration_cast<std::chrono::nanoseconds>(60ms).count()});
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken1);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
mFrameTimeline->setSfWakeUp(sfToken1,
- std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count());
- mFrameTimeline->addSurfaceFrame(surfaceFrame1,
- SurfaceFrame::PresentState::Presented);
+ std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count(),
+ 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
presentFence1->signalForTest(
std::chrono::duration_cast<std::chrono::nanoseconds>(70ms).count());
@@ -412,8 +450,9 @@
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(sUidOne, sLayerNameOne, HasBit(JankType::Display)));
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(JankType::Display)));
+ incrementJankyFrames(sUidOne, sLayerNameOne, HasBit(JankType::DisplayHAL)));
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(JankType::DisplayHAL)));
+
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
@@ -424,18 +463,18 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(56ms).count(),
std::chrono::duration_cast<std::chrono::nanoseconds>(60ms).count()});
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken1);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
mFrameTimeline->setSfWakeUp(sfToken1,
- std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count());
- mFrameTimeline->addSurfaceFrame(surfaceFrame1,
- SurfaceFrame::PresentState::Presented);
+ std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count(),
+ 30);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
presentFence1->signalForTest(
std::chrono::duration_cast<std::chrono::nanoseconds>(90ms).count());
- mFrameTimeline->setSfPresent(std::chrono::duration_cast<std::chrono::nanoseconds>(59ms).count(),
+ mFrameTimeline->setSfPresent(std::chrono::duration_cast<std::chrono::nanoseconds>(56ms).count(),
presentFence1);
- EXPECT_NE(surfaceFrame1->getJankType(), std::nullopt);
- EXPECT_TRUE((surfaceFrame1->getJankType().value() & JankType::Display) != 0);
+ EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
@@ -449,26 +488,26 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(20ms).count(),
std::chrono::duration_cast<std::chrono::nanoseconds>(60ms).count()});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
- {std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count(),
- std::chrono::duration_cast<std::chrono::nanoseconds>(56ms).count(),
- std::chrono::duration_cast<std::chrono::nanoseconds>(60ms).count()});
+ {std::chrono::duration_cast<std::chrono::nanoseconds>(82ms).count(),
+ std::chrono::duration_cast<std::chrono::nanoseconds>(86ms).count(),
+ std::chrono::duration_cast<std::chrono::nanoseconds>(90ms).count()});
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken1);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
surfaceFrame1->setAcquireFenceTime(
std::chrono::duration_cast<std::chrono::nanoseconds>(45ms).count());
mFrameTimeline->setSfWakeUp(sfToken1,
- std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count());
+ std::chrono::duration_cast<std::chrono::nanoseconds>(52ms).count(),
+ 11);
- mFrameTimeline->addSurfaceFrame(surfaceFrame1,
- SurfaceFrame::PresentState::Presented);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
presentFence1->signalForTest(
std::chrono::duration_cast<std::chrono::nanoseconds>(90ms).count());
- mFrameTimeline->setSfPresent(std::chrono::duration_cast<std::chrono::nanoseconds>(56ms).count(),
+ mFrameTimeline->setSfPresent(std::chrono::duration_cast<std::chrono::nanoseconds>(86ms).count(),
presentFence1);
- EXPECT_NE(surfaceFrame1->getJankType(), std::nullopt);
- EXPECT_TRUE((surfaceFrame1->getJankType().value() & JankType::AppDeadlineMissed) != 0);
+ EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
}
/*
@@ -481,23 +520,26 @@
*/
TEST_F(FrameTimelineTest, tracing_noPacketsSentWithoutTraceStart) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
- auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, token1);
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20);
- mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->setSfWakeUp(token1, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfWakeUp(token2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
@@ -506,25 +548,29 @@
TEST_F(FrameTimelineTest, tracing_sanityTest) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
tracingSession->StartBlocking();
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
- auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, token1);
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token2, 20);
- mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfWakeUp(token2, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfWakeUp(token2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
presentFence2->signalForTest(55);
@@ -543,6 +589,8 @@
TEST_F(FrameTimelineTest, traceDisplayFrame_invalidTokenDoesNotEmitTracePacket) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -550,13 +598,13 @@
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
// Set up the display frame
- mFrameTimeline->setSfWakeUp(-1, 20);
+ mFrameTimeline->setSfWakeUp(-1, 20, 11);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(token1, 50);
+ mFrameTimeline->setSfWakeUp(token1, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
presentFence2->signalForTest(60);
@@ -572,24 +620,27 @@
TEST_F(FrameTimelineTest, traceSurfaceFrame_invalidTokenDoesNotEmitTracePacket) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
tracingSession->StartBlocking();
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
- auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, std::nullopt);
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20);
- mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->setSfWakeUp(token1, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfWakeUp(token2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
presentFence2->signalForTest(60);
@@ -662,6 +713,8 @@
TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -670,7 +723,7 @@
int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, 11);
mFrameTimeline->setSfPresent(26, presentFence1);
presentFence1->signalForTest(31);
@@ -687,7 +740,7 @@
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
presentFence2->signalForTest(55);
@@ -713,6 +766,10 @@
TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -722,8 +779,8 @@
int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
- sLayerNameOne, surfaceFrameToken);
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
surfaceFrame1->setActualStartTime(0);
surfaceFrame1->setActualQueueTime(15);
surfaceFrame1->setAcquireFenceTime(20);
@@ -743,15 +800,15 @@
protoSurfaceFrame.set_pid(sPidOne);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
- mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
- SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(26, presentFence1);
- presentFence1->signalForTest(31);
+ presentFence1->signalForTest(40);
// Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
// next frame
- mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50, 11);
mFrameTimeline->setSfPresent(55, presentFence2);
presentFence2->signalForTest(55);
@@ -775,4 +832,511 @@
validateSurfaceFrameEvent(surfaceFrameEvent, protoSurfaceFrame);
}
+// Tests for Jank classification
+TEST_F(FrameTimelineTest, jankClassification_presentOnTimeDoesNotClassify) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_));
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ auto displayFrame = getDisplayFrame(0);
+ auto& presentedSurfaceFrame = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(29);
+
+ // Fences haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+ EXPECT_EQ(presentedSurfaceFrame.getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame = getDisplayFrame(0);
+
+ // Fences have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 29);
+ EXPECT_EQ(presentedSurfaceFrame.getActuals().presentTime, 29);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::None);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ auto displayFrame = getDisplayFrame(0);
+ presentFence1->signalForTest(30);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, 11);
+ mFrameTimeline->setSfPresent(56, presentFence2);
+ displayFrame = getDisplayFrame(0);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 30);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ auto displayFrame2 = getDisplayFrame(1);
+ presentFence2->signalForTest(65);
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame2 = getDisplayFrame(1);
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 65);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ auto displayFrame = getDisplayFrame(0);
+ presentFence1->signalForTest(50);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, 11);
+ mFrameTimeline->setSfPresent(56, presentFence2);
+ displayFrame = getDisplayFrame(0);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 50);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::DisplayHAL);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ auto displayFrame2 = getDisplayFrame(1);
+ presentFence2->signalForTest(75);
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame2 = getDisplayFrame(1);
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 75);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({12, 18, 40});
+ mFrameTimeline->setSfWakeUp(sfToken1, 12, 11);
+
+ mFrameTimeline->setSfPresent(22, presentFence1);
+ auto displayFrame = getDisplayFrame(0);
+ presentFence1->signalForTest(28);
+
+ // Fences haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame = getDisplayFrame(0);
+
+ // Fences have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 28);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ mFrameTimeline->setSfWakeUp(sfToken1, 12, 11);
+ mFrameTimeline->setSfPresent(36, presentFence1);
+ auto displayFrame = getDisplayFrame(0);
+ presentFence1->signalForTest(52);
+
+ // Fences haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame = getDisplayFrame(0);
+
+ // Fences have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 52);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
+ int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setAcquireFenceTime(16);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ auto displayFrame1 = getDisplayFrame(0);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(30);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+ auto actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto surfaceFrame2 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken2, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame2->setAcquireFenceTime(36);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, 11);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+ mFrameTimeline->setSfPresent(56, presentFence2);
+ auto displayFrame2 = getDisplayFrame(1);
+ auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 30);
+ EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+
+ actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 30);
+ EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::SurfaceFlingerScheduling);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ presentFence2->signalForTest(65);
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+ auto actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 65);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+
+ actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 65);
+ EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
+ int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setAcquireFenceTime(16);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ auto displayFrame1 = getDisplayFrame(0);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(50);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+ auto actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto surfaceFrame2 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken2, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame2->setAcquireFenceTime(36);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, 11);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+ mFrameTimeline->setSfPresent(56, presentFence2);
+ auto displayFrame2 = getDisplayFrame(1);
+ auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 50);
+ EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame1->getJankType(), JankType::DisplayHAL);
+
+ actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 50);
+ EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::DisplayHAL);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ presentFence2->signalForTest(86);
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+ auto actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 86);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+
+ actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 86);
+ EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_));
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 26, 60});
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setAcquireFenceTime(40);
+ mFrameTimeline->setSfWakeUp(sfToken1, 42, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(46, presentFence1);
+ auto displayFrame1 = getDisplayFrame(0);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(50);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+ auto actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 50);
+ EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+
+ actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 50);
+ EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
+ EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::Unknown);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) {
+ // First frame - DisplayFrame is not janky. This should classify the SurfaceFrame as
+ // AppDeadlineMissed. Second frame - DisplayFrame is janky. This should propagate DisplayFrame's
+ // jank to the SurfaceFrame.
+
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({32, 36, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 30});
+ int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 50});
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setAcquireFenceTime(26);
+ mFrameTimeline->setSfWakeUp(sfToken1, 32, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(36, presentFence1);
+ auto displayFrame1 = getDisplayFrame(0);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(40);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+ auto actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto surfaceFrame2 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken2, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame2->setAcquireFenceTime(40);
+ mFrameTimeline->setSfWakeUp(sfToken2, 43, 11);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+ mFrameTimeline->setSfPresent(56, presentFence2);
+ auto displayFrame2 = getDisplayFrame(1);
+ auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 40);
+ EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+
+ actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 40);
+ EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ presentFence2->signalForTest(60);
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+ auto actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 60);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+
+ actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 60);
+ EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadlineMissed) {
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_)).Times(2);
+ // Layer specific increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_, testing::_, testing::_)).Times(2);
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 56, 60});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 116, 120});
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setAcquireFenceTime(50);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, 30);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(56, presentFence1);
+ auto displayFrame1 = getDisplayFrame(0);
+ auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+ presentFence1->signalForTest(60);
+
+ // Fences for the first frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+ auto actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.presentTime, 0);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto surfaceFrame2 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken2, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame2->setAcquireFenceTime(84);
+ mFrameTimeline->setSfWakeUp(sfToken2, 112, 30);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+ mFrameTimeline->setSfPresent(116, presentFence2);
+ auto displayFrame2 = getDisplayFrame(1);
+ auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+ presentFence2->signalForTest(120);
+
+ // Fences for the first frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame1->getActuals().presentTime, 60);
+ actuals1 = presentedSurfaceFrame1.getActuals();
+ EXPECT_EQ(actuals1.endTime, 50);
+ EXPECT_EQ(actuals1.presentTime, 60);
+
+ EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+
+ EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+
+ // Fences for the second frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+ auto actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences for the second frame have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 120);
+ actuals2 = presentedSurfaceFrame2.getActuals();
+ EXPECT_EQ(actuals2.presentTime, 120);
+
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+
+ EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
+ JankType::AppDeadlineMissed | JankType::BufferStuffing);
+}
} // namespace android::frametimeline
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
index a119e27..2c71a2e 100644
--- a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -381,4 +382,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index fa12315..bc1e88a 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -42,7 +42,10 @@
namespace android {
namespace {
-namespace hal = android::hardware::graphics::composer::hal;
+namespace V2_1 = hardware::graphics::composer::V2_1;
+namespace V2_4 = hardware::graphics::composer::V2_4;
+
+using Hwc2::Config;
using ::testing::_;
using ::testing::DoAll;
@@ -170,5 +173,88 @@
EXPECT_EQ(hal::Error::UNSUPPORTED, result);
}
+class HWComposerConfigsTest : public testing::Test {
+public:
+ Hwc2::mock::Composer* mHal = new StrictMock<Hwc2::mock::Composer>();
+ MockHWC2ComposerCallback mCallback;
+
+ void setActiveConfig(Config config) {
+ EXPECT_CALL(*mHal, getActiveConfig(_, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(config), Return(V2_1::Error::NONE)));
+ }
+
+ void setDisplayConfigs(std::vector<Config> configs) {
+ EXPECT_CALL(*mHal, getDisplayConfigs(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(configs), Return(V2_1::Error::NONE)));
+ EXPECT_CALL(*mHal, getDisplayAttribute(_, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(1), Return(V2_1::Error::NONE)));
+ }
+
+ void testSetActiveConfigWithConstraintsCommon(bool isVsyncPeriodSwitchSupported);
+};
+
+void HWComposerConfigsTest::testSetActiveConfigWithConstraintsCommon(
+ bool isVsyncPeriodSwitchSupported) {
+ EXPECT_CALL(*mHal, getMaxVirtualDisplayCount()).WillOnce(Return(0));
+ EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<hal::Capability>{}));
+ EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_)).WillOnce(Return(V2_4::Error::UNSUPPORTED));
+ EXPECT_CALL(*mHal, registerCallback(_));
+ EXPECT_CALL(*mHal, setVsyncEnabled(_, _)).WillRepeatedly(Return(V2_1::Error::NONE));
+ EXPECT_CALL(*mHal, getDisplayIdentificationData(_, _, _))
+ .WillRepeatedly(Return(V2_1::Error::UNSUPPORTED));
+ EXPECT_CALL(*mHal, setClientTargetSlotCount(_)).WillRepeatedly(Return(V2_1::Error::NONE));
+
+ EXPECT_CALL(*mHal, isVsyncPeriodSwitchSupported())
+ .WillRepeatedly(Return(isVsyncPeriodSwitchSupported));
+
+ if (isVsyncPeriodSwitchSupported) {
+ EXPECT_CALL(*mHal, setActiveConfigWithConstraints(_, _, _, _))
+ .WillRepeatedly(Return(V2_4::Error::NONE));
+ } else {
+ EXPECT_CALL(*mHal, setActiveConfig(_, _)).WillRepeatedly(Return(V2_1::Error::NONE));
+ }
+
+ impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
+ hwc.setConfiguration(&mCallback, 123);
+
+ setDisplayConfigs({15});
+ setActiveConfig(15);
+
+ const auto physicalId = PhysicalDisplayId::fromPort(0);
+ const hal::HWDisplayId hwcId = 0;
+ hwc.allocatePhysicalDisplay(hwcId, physicalId);
+
+ hal::VsyncPeriodChangeConstraints constraints;
+ constraints.desiredTimeNanos = systemTime();
+ constraints.seamlessRequired = false;
+
+ hal::VsyncPeriodChangeTimeline timeline = {0, 0, 0};
+ constexpr size_t kConfigIndex = 0;
+ const auto status =
+ hwc.setActiveConfigWithConstraints(physicalId, kConfigIndex, constraints, &timeline);
+ EXPECT_EQ(NO_ERROR, status);
+
+ const std::vector<Config> kConfigs{7, 8, 9, 10, 11};
+ // Change the set of supported modes.
+ setDisplayConfigs(kConfigs);
+ setActiveConfig(11);
+ hwc.onHotplug(hwcId, hal::Connection::CONNECTED);
+ hwc.allocatePhysicalDisplay(hwcId, physicalId);
+
+ for (size_t configIndex = 0; configIndex < kConfigs.size(); configIndex++) {
+ const auto status =
+ hwc.setActiveConfigWithConstraints(physicalId, configIndex, constraints, &timeline);
+ EXPECT_EQ(NO_ERROR, status) << "Error when switching to config " << configIndex;
+ }
+}
+
+TEST_F(HWComposerConfigsTest, setActiveConfigWithConstraintsWithVsyncSwitchingSupported) {
+ testSetActiveConfigWithConstraintsCommon(/*supported=*/true);
+}
+
+TEST_F(HWComposerConfigsTest, setActiveConfigWithConstraintsWithVsyncSwitchingNotSupported) {
+ testSetActiveConfigWithConstraintsCommon(/*supported=*/false);
+}
+
} // namespace
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index cbc1e02..2ee9c64 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "LayerHistoryTest"
@@ -754,3 +758,6 @@
} // namespace
} // namespace scheduler
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
index 75a061b..373fd74 100644
--- a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -107,3 +111,6 @@
} // namespace
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index 53dfe3f..8208b3f 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -69,6 +69,7 @@
~MockTokenManager() override = default;
MOCK_METHOD1(generateTokenForPredictions, int64_t(frametimeline::TimelineItem&& prediction));
+ MOCK_CONST_METHOD1(getPredictionsForToken, std::optional<frametimeline::TimelineItem>(int64_t));
};
class MessageQueueTest : public testing::Test {
diff --git a/services/surfaceflinger/tests/unittests/PromiseTest.cpp b/services/surfaceflinger/tests/unittests/PromiseTest.cpp
deleted file mode 100644
index e4dc1fe..0000000
--- a/services/surfaceflinger/tests/unittests/PromiseTest.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <algorithm>
-#include <future>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-#include "Promise.h"
-
-namespace android {
-namespace {
-
-using Bytes = std::vector<uint8_t>;
-
-Bytes decrement(Bytes bytes) {
- std::transform(bytes.begin(), bytes.end(), bytes.begin(), [](auto b) { return b - 1; });
- return bytes;
-}
-
-} // namespace
-
-TEST(PromiseTest, yield) {
- EXPECT_EQ(42, promise::yield(42).get());
-
- auto ptr = std::make_unique<char>('!');
- auto future = promise::yield(std::move(ptr));
- EXPECT_EQ('!', *future.get());
-}
-
-TEST(PromiseTest, chain) {
- std::packaged_task<const char*()> fetchString([] { return "ifmmp-"; });
-
- std::packaged_task<Bytes(std::string)> appendString([](std::string str) {
- str += "!xpsme";
- return Bytes{str.begin(), str.end()};
- });
-
- std::packaged_task<std::future<Bytes>(Bytes)> decrementBytes(
- [](Bytes bytes) { return promise::defer(decrement, std::move(bytes)); });
-
- auto fetch = fetchString.get_future();
- std::thread fetchThread(std::move(fetchString));
-
- std::thread appendThread, decrementThread;
-
- EXPECT_EQ("hello, world",
- promise::chain(std::move(fetch))
- .then([](const char* str) { return std::string(str); })
- .then([&](std::string str) {
- auto append = appendString.get_future();
- appendThread = std::thread(std::move(appendString), std::move(str));
- return append;
- })
- .then([&](Bytes bytes) {
- auto decrement = decrementBytes.get_future();
- decrementThread = std::thread(std::move(decrementBytes),
- std::move(bytes));
- return decrement;
- })
- .then([](std::future<Bytes> bytes) { return bytes; })
- .then([](const Bytes& bytes) {
- return std::string(bytes.begin(), bytes.end());
- })
- .get());
-
- fetchThread.join();
- appendThread.join();
- decrementThread.join();
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 83ad737..45f0ee6 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "SchedulerUnittests"
@@ -1545,40 +1549,117 @@
EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
}
-TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUnknownUid) {
- auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
- /*currentConfigId=*/HWC_CONFIG_ID_30);
- EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(1234));
-}
-
TEST_F(RefreshRateConfigsTest, RefreshRateDividerForUid) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
/*currentConfigId=*/HWC_CONFIG_ID_30);
- const uid_t uid = 1234;
- refreshRateConfigs->setPreferredRefreshRateForUid({uid, 30});
- EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+
+ const auto frameRate = Fps(30.f);
+ EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDivider(frameRate));
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_60);
- EXPECT_EQ(2, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+ EXPECT_EQ(2, refreshRateConfigs->getRefreshRateDivider(frameRate));
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_72);
- EXPECT_EQ(1, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+ EXPECT_EQ(0, refreshRateConfigs->getRefreshRateDivider(frameRate));
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- EXPECT_EQ(3, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+ EXPECT_EQ(3, refreshRateConfigs->getRefreshRateDivider(frameRate));
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_120);
- EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+ EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDivider(frameRate));
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.5f});
- EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
- refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.6f});
- EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
+ EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDivider(Fps(22.5f)));
+ EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDivider(Fps(22.6f)));
+}
+
+TEST_F(RefreshRateConfigsTest, populatePreferredFrameRate_noLayers) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
+ HWC_CONFIG_ID_120);
+
+ auto layers = std::vector<LayerRequirement>{};
+ ASSERT_TRUE(refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f)).empty());
+}
+
+TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
+ HWC_CONFIG_ID_120);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = Fps(60.0f);
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ auto frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_EQ(1, frameRateOverrides.size());
+ ASSERT_EQ(1, frameRateOverrides.count(1234));
+ ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_EQ(1, frameRateOverrides.size());
+ ASSERT_EQ(1, frameRateOverrides.count(1234));
+ ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+
+ layers[0].vote = LayerVoteType::NoVote;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Min;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Max;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Heuristic;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_F(RefreshRateConfigsTest, populatePreferredFrameRate_twoUids) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
+ HWC_CONFIG_ID_120);
+
+ auto layers = std::vector<LayerRequirement>{
+ LayerRequirement{.ownerUid = 1234, .weight = 1.0f},
+ LayerRequirement{.ownerUid = 5678, .weight = 1.0f},
+ };
+
+ layers[0].name = "Test layer 1234";
+ layers[0].desiredRefreshRate = Fps(60.0f);
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ layers[1].name = "Test layer 5678";
+ layers[1].desiredRefreshRate = Fps(30.0f);
+ layers[1].vote = LayerVoteType::ExplicitDefault;
+ auto frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+
+ ASSERT_EQ(2, frameRateOverrides.size());
+ ASSERT_EQ(1, frameRateOverrides.count(1234));
+ ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+ ASSERT_EQ(1, frameRateOverrides.count(5678));
+ ASSERT_EQ(30.0f, frameRateOverrides.at(5678).getValue());
+
+ layers[1].vote = LayerVoteType::Heuristic;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_EQ(1, frameRateOverrides.size());
+ ASSERT_EQ(1, frameRateOverrides.count(1234));
+ ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+
+ layers[1].ownerUid = 1234;
+ frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f));
+ ASSERT_TRUE(frameRateOverrides.empty());
}
} // namespace
} // namespace scheduler
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index e93d0d0..2188402 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "SchedulerUnittests"
@@ -216,3 +220,6 @@
} // namespace
} // namespace scheduler
} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index c47b141..1bbe8e2 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -432,7 +432,7 @@
EXPECT_EQ(FRAME_RATE_NO_VOTE, child2_1->getFrameRateForLayerTree());
}
-TEST_P(SetFrameRateTest, SetAndGetRearentChildren) {
+TEST_P(SetFrameRateTest, SetAndGetReparentChildren) {
EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
const auto& layerFactory = GetParam();
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp
index cd3f6ab..65efc85 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp
@@ -144,7 +144,7 @@
// SF should have a display token.
const auto displayId = Case::Display::DISPLAY_ID::get();
ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
- ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 1);
+ ASSERT_EQ(mFlinger.mutablePhysicalDisplayTokens().count(displayId), 1);
auto& displayToken = mFlinger.mutablePhysicalDisplayTokens()[displayId];
verifyDisplayIsConnected<Case>(displayToken);
@@ -259,14 +259,6 @@
processesHotplugConnectCommon<SimplePrimaryDisplayCase>();
}
-TEST_F(HandleTransactionLockedTest,
- processesHotplugConnectPrimaryDisplayWithExternalAlreadyConnected) {
- // Inject an external display.
- ExternalDisplayVariant::injectHwcDisplay(this);
-
- processesHotplugConnectCommon<SimplePrimaryDisplayCase>();
-}
-
TEST_F(HandleTransactionLockedTest, processesHotplugConnectExternalDisplay) {
// Inject a primary display.
PrimaryDisplayVariant::injectHwcDisplay(this);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 61f0788..cedb404 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -223,6 +223,7 @@
// Various native window calls will be made.
Case::Display::setupNativeWindowSurfaceCreationCallExpectations(this);
Case::Display::setupHwcGetActiveConfigCallExpectations(this);
+ Case::Display::setupHwcGetConfigsCallExpectations(this);
Case::WideColorSupport::setupComposerCallExpectations(this);
Case::HdrSupport::setupComposerCallExpectations(this);
Case::PerFrameMetadataSupport::setupComposerCallExpectations(this);
@@ -236,6 +237,7 @@
ASSERT_TRUE(displayId);
const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
ASSERT_TRUE(hwcDisplayId);
+ mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId);
state.physical = {.id = *displayId, .type = *connectionType, .hwcDisplayId = *hwcDisplayId};
}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 030073c..b57d473 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -78,7 +78,7 @@
}
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
- const scheduler::RefreshRateConfigs& /*refreshRateConfigs*/) override {
+ Fps /*currentRefreshRate*/) override {
return std::make_unique<scheduler::FakePhaseOffsets>();
}
@@ -156,8 +156,8 @@
}
std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
- std::shared_ptr<TimeStats> timeStats) override {
- return std::make_unique<mock::FrameTimeline>(timeStats);
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+ return std::make_unique<mock::FrameTimeline>(timeStats, surfaceFlingerPid);
}
using CreateBufferQueueFunction =
@@ -231,8 +231,7 @@
mFlinger->mRefreshRateStats =
std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
/*powerMode=*/hal::PowerMode::OFF);
- mFlinger->mVsyncConfiguration =
- mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
+ mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
@@ -372,14 +371,14 @@
const Vector<DisplayState>& displays, uint32_t flags,
const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands,
- int64_t desiredPresentTime, const client_cache_t& uncacheBuffer,
- bool hasListenerCallbacks,
+ int64_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
std::vector<ListenerCallbacks>& listenerCallbacks,
uint64_t transactionId) {
return mFlinger->setTransactionState(frameTimelineVsyncId, states, displays, flags,
applyToken, inputWindowCommands, desiredPresentTime,
- uncacheBuffer, hasListenerCallbacks, listenerCallbacks,
- transactionId);
+ isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+ listenerCallbacks, transactionId);
}
auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(); };
@@ -388,6 +387,8 @@
return mFlinger->onTransact(code, data, reply, flags);
}
+ auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
/* ------------------------------------------------------------------------
* Read-only access to private data to assert post-conditions.
*/
@@ -560,8 +561,15 @@
const auto physicalId = PhysicalDisplayId::tryCast(mDisplayId);
LOG_ALWAYS_FATAL_IF(!physicalId);
flinger->mutableHwcPhysicalDisplayIdMap().emplace(mHwcDisplayId, *physicalId);
- (mIsPrimary ? flinger->mutableInternalHwcDisplayId()
- : flinger->mutableExternalHwcDisplayId()) = mHwcDisplayId;
+ if (mIsPrimary) {
+ flinger->mutableInternalHwcDisplayId() = mHwcDisplayId;
+ } else {
+ // If there is an external HWC display there should always be an internal ID
+ // as well. Set it to some arbitrary value.
+ auto& internalId = flinger->mutableInternalHwcDisplayId();
+ if (!internalId) internalId = mHwcDisplayId - 1;
+ flinger->mutableExternalHwcDisplayId() = mHwcDisplayId;
+ }
}
}
@@ -690,6 +698,7 @@
void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override {}
void repaintEverythingForHWC() override {}
void kernelTimerChanged(bool) override {}
+ void triggerOnFrameRateOverridesChanged() {}
surfaceflinger::test::Factory mFactory;
sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index bbcc0c9..fb9afd4 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -356,9 +357,9 @@
EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
- mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(JankType::Display);
+ mTimeStats->incrementJankyFrames(JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
mTimeStats->incrementJankyFrames(JankType::None);
@@ -383,10 +384,10 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::AppDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
@@ -848,10 +849,10 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count()));
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::AppDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
@@ -987,9 +988,9 @@
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
- mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(JankType::Display);
+ mTimeStats->incrementJankyFrames(JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
mTimeStats->incrementJankyFrames(JankType::None);
@@ -1062,10 +1063,10 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
JankType::AppDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
@@ -1320,4 +1321,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index c36d994..06275c6 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "CompositionTest"
@@ -97,7 +98,8 @@
uint32_t flags = 0;
sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
InputWindowCommands inputWindowCommands;
- int64_t desiredPresentTime = -1;
+ int64_t desiredPresentTime = 0;
+ bool isAutoTimestamp = true;
int64_t frameTimelineVsyncId = ISurfaceComposer::INVALID_VSYNC_ID;
client_cache_t uncacheBuffer;
int64_t id = -1;
@@ -114,11 +116,13 @@
}
void setupSingle(TransactionInfo& transaction, uint32_t flags, bool syncInputWindows,
- int64_t desiredPresentTime, int64_t frameTimelineVsyncId) {
+ int64_t desiredPresentTime, bool isAutoTimestamp,
+ int64_t frameTimelineVsyncId) {
mTransactionNumber++;
transaction.flags |= flags; // ISurfaceComposer::eSynchronous;
transaction.inputWindowCommands.syncInputWindows = syncInputWindows;
transaction.desiredPresentTime = desiredPresentTime;
+ transaction.isAutoTimestamp = isAutoTimestamp;
transaction.frameTimelineVsyncId = frameTimelineVsyncId;
}
@@ -129,13 +133,15 @@
EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillOnce(Return(systemTime()));
TransactionInfo transaction;
setupSingle(transaction, flags, syncInputWindows,
- /*desiredPresentTime*/ -1, ISurfaceComposer::INVALID_VSYNC_ID);
+ /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
+ ISurfaceComposer::INVALID_VSYNC_ID);
nsecs_t applicationTime = systemTime();
mFlinger.setTransactionState(transaction.frameTimelineVsyncId, transaction.states,
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
- transaction.desiredPresentTime, transaction.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transaction.id);
+ transaction.desiredPresentTime, transaction.isAutoTimestamp,
+ transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transaction.id);
// This transaction should not have been placed on the transaction queue.
// If transaction is synchronous or syncs input windows, SF
@@ -164,13 +170,15 @@
.WillOnce(Return(time + nsecs_t(5 * 1e8)));
TransactionInfo transaction;
setupSingle(transaction, flags, syncInputWindows,
- /*desiredPresentTime*/ time + s2ns(1), ISurfaceComposer::INVALID_VSYNC_ID);
+ /*desiredPresentTime*/ time + s2ns(1), false,
+ ISurfaceComposer::INVALID_VSYNC_ID);
nsecs_t applicationSentTime = systemTime();
mFlinger.setTransactionState(transaction.frameTimelineVsyncId, transaction.states,
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
- transaction.desiredPresentTime, transaction.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transaction.id);
+ transaction.desiredPresentTime, transaction.isAutoTimestamp,
+ transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transaction.id);
nsecs_t returnedTime = systemTime();
EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
@@ -189,20 +197,23 @@
// transaction that should go on the pending thread
TransactionInfo transactionA;
setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
- /*desiredPresentTime*/ time + s2ns(1), ISurfaceComposer::INVALID_VSYNC_ID);
+ /*desiredPresentTime*/ time + s2ns(1), false,
+ ISurfaceComposer::INVALID_VSYNC_ID);
// transaction that would not have gone on the pending thread if not
// blocked
TransactionInfo transactionB;
setupSingle(transactionB, flags, syncInputWindows,
- /*desiredPresentTime*/ -1, ISurfaceComposer::INVALID_VSYNC_ID);
+ /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
+ ISurfaceComposer::INVALID_VSYNC_ID);
nsecs_t applicationSentTime = systemTime();
mFlinger.setTransactionState(transactionA.frameTimelineVsyncId, transactionA.states,
transactionA.displays, transactionA.flags,
transactionA.applyToken, transactionA.inputWindowCommands,
- transactionA.desiredPresentTime, transactionA.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transactionA.id);
+ transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
+ transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transactionA.id);
// This thread should not have been blocked by the above transaction
// (5s is the timeout period that applyTransactionState waits for SF to
@@ -213,8 +224,9 @@
mFlinger.setTransactionState(transactionB.frameTimelineVsyncId, transactionB.states,
transactionB.displays, transactionB.flags,
transactionB.applyToken, transactionB.inputWindowCommands,
- transactionB.desiredPresentTime, transactionB.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transactionB.id);
+ transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
+ transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
+ transactionB.id);
// this thread should have been blocked by the above transaction
// if this is an animation, this thread should be blocked for 5s
@@ -256,12 +268,12 @@
.WillOnce(Return(s2ns(2)));
TransactionInfo transactionA; // transaction to go on pending queue
setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
- /*desiredPresentTime*/ s2ns(1), ISurfaceComposer::INVALID_VSYNC_ID);
+ /*desiredPresentTime*/ s2ns(1), false, ISurfaceComposer::INVALID_VSYNC_ID);
mFlinger.setTransactionState(transactionA.frameTimelineVsyncId, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
- transactionA.id);
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
+ mHasListenerCallbacks, mCallbacks, transactionA.id);
auto& transactionQueue = mFlinger.getTransactionQueue();
ASSERT_EQ(1, transactionQueue.size());
@@ -279,8 +291,8 @@
empty.applyToken = sp<IBinder>();
mFlinger.setTransactionState(empty.frameTimelineVsyncId, empty.states, empty.displays,
empty.flags, empty.applyToken, empty.inputWindowCommands,
- empty.desiredPresentTime, empty.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, empty.id);
+ empty.desiredPresentTime, empty.isAutoTimestamp,
+ empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id);
// flush transaction queue should flush as desiredPresentTime has
// passed
@@ -333,4 +345,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 72b5396..00cf574 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -1152,4 +1153,4 @@
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index a142022..a4ddbf4 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -17,6 +17,7 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -492,4 +493,4 @@
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index a7568e4..b9651ea 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
#define LOG_NDEBUG 0
@@ -491,3 +495,6 @@
}
} // namespace android::scheduler
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
index 2a35f69..bb7578d 100644
--- a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
@@ -19,6 +19,7 @@
#include <gmock/gmock.h>
#include <log/log.h>
+#include <chrono>
#include <thread>
#include "Scheduler/VsyncConfiguration.h"
@@ -27,14 +28,15 @@
namespace android::scheduler {
+using namespace std::chrono_literals;
+
class TestableWorkDuration : public impl::WorkDuration {
public:
TestableWorkDuration(Fps currentFps, nsecs_t sfDuration, nsecs_t appDuration,
nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
- : impl::WorkDuration({Fps(60.0f), Fps(90.0f)}, currentFps, sfDuration, appDuration,
- sfEarlyDuration, appEarlyDuration, sfEarlyGlDuration,
- appEarlyGlDuration) {}
+ : impl::WorkDuration(currentFps, sfDuration, appDuration, sfEarlyDuration,
+ appEarlyDuration, sfEarlyGlDuration, appEarlyGlDuration) {}
};
class WorkDurationTest : public testing::Test {
@@ -171,9 +173,9 @@
std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
nsecs_t thresholdForNextVsync)
- : impl::PhaseOffsets({Fps(60.0f), Fps(90.0f)}, Fps(60.0f), vsyncPhaseOffsetNs,
- sfVSyncPhaseOffsetNs, earlySfOffsetNs, earlyGpuSfOffsetNs,
- earlyAppOffsetNs, earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
+ : impl::PhaseOffsets(Fps(60.0f), vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
+ earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
+ earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
highFpsEarlyGpuSfOffsetNs, highFpsEarlyAppOffsetNs,
highFpsEarlyGpuAppOffsetNs, thresholdForNextVsync) {}
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
index f784df3..ff005a0 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
@@ -19,8 +19,8 @@
namespace android::mock {
// Explicit default instantiation is recommended.
-FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
- : android::frametimeline::impl::FrameTimeline(timeStats) {}
+FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid)
+ : android::frametimeline::impl::FrameTimeline(timeStats, surfaceFlingerPid) {}
FrameTimeline::~FrameTimeline() = default;
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
index 6b12536..0a6a9f4 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -26,14 +26,12 @@
// No need to create mocks for SurfaceFrame and TokenManager yet. They are very small components
// and do not have external dependencies like perfetto.
public:
- FrameTimeline(std::shared_ptr<TimeStats> timeStats);
+ FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid);
~FrameTimeline();
MOCK_METHOD0(onBootFinished, void());
- MOCK_METHOD2(addSurfaceFrame,
- void(std::shared_ptr<frametimeline::SurfaceFrame>,
- frametimeline::SurfaceFrame::PresentState));
- MOCK_METHOD2(setSfWakeUp, void(int64_t, nsecs_t));
+ MOCK_METHOD1(addSurfaceFrame, void(std::shared_ptr<frametimeline::SurfaceFrame>));
+ MOCK_METHOD3(setSfWakeUp, void(int64_t, nsecs_t, nsecs_t));
MOCK_METHOD2(setSfPresent, void(nsecs_t, const std::shared_ptr<FenceTime>&));
};
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 078d8e0..ba2e4db 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -33,6 +33,7 @@
MOCK_CONST_METHOD0(isVisible, bool());
MOCK_METHOD0(createClone, sp<Layer>());
MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
+ MOCK_CONST_METHOD0(getOwnerUid, uid_t());
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
index efaa9fa..453c93a 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
@@ -30,7 +30,7 @@
~MessageQueue() override;
MOCK_METHOD1(init, void(const sp<SurfaceFlinger>&));
- MOCK_METHOD1(setEventConnection, void(const sp<EventThreadConnection>& connection));
+ MOCK_METHOD1(setInjector, void(sp<EventThreadConnection>));
MOCK_METHOD0(waitMessage, void());
MOCK_METHOD1(postMessage, void(sp<MessageHandler>&&));
MOCK_METHOD0(invalidate, void());
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 72bc89c..ab19886 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -29,6 +29,7 @@
scheduler::RefreshRateConfigEvent));
MOCK_METHOD0(repaintEverythingForHWC, void());
MOCK_METHOD1(kernelTimerChanged, void(bool));
+ MOCK_METHOD0(triggerOnFrameRateOverridesChanged, void());
};
struct NoOpSchedulerCallback final : ISchedulerCallback {
@@ -37,6 +38,7 @@
scheduler::RefreshRateConfigEvent) override {}
void repaintEverythingForHWC() override {}
void kernelTimerChanged(bool) override {}
+ void triggerOnFrameRateOverridesChanged() {}
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/utils/TransactionUtils.h b/services/surfaceflinger/tests/utils/TransactionUtils.h
index 5c5b18e..3cbfed9 100644
--- a/services/surfaceflinger/tests/utils/TransactionUtils.h
+++ b/services/surfaceflinger/tests/utils/TransactionUtils.h
@@ -18,6 +18,7 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
#include <chrono>
@@ -186,4 +187,4 @@
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
\ No newline at end of file