Merge "Create headers library for libvibrator"
diff --git a/aidl/gui/android/view/LayerMetadataKey.aidl b/aidl/gui/android/view/LayerMetadataKey.aidl
index 491c629..a1d8ce5 100644
--- a/aidl/gui/android/view/LayerMetadataKey.aidl
+++ b/aidl/gui/android/view/LayerMetadataKey.aidl
@@ -25,4 +25,5 @@
METADATA_MOUSE_CURSOR = 4,
METADATA_ACCESSIBILITY_ID = 5,
METADATA_OWNER_PID = 6,
+ METADATA_DEQUEUE_TIME = 7,
}
diff --git a/cmds/bugreport/bugreport.cpp b/cmds/bugreport/bugreport.cpp
index e5c52d8..f81994b 100644
--- a/cmds/bugreport/bugreport.cpp
+++ b/cmds/bugreport/bugreport.cpp
@@ -14,91 +14,19 @@
* limitations under the License.
*/
-#include <errno.h>
#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <cutils/properties.h>
-#include <cutils/sockets.h>
+// Only prints a warning redirecting to bugreportz.
+int main() {
+ fprintf(stderr,
+ "=============================================================================\n");
+ fprintf(stderr, "WARNING: Flat (text file, non-zipped) bugreports are deprecated.\n");
+ fprintf(stderr, "WARNING: Please generate zipped bugreports instead.\n");
+ fprintf(stderr, "WARNING: On the host use: adb bugreport filename.zip\n");
+ fprintf(stderr, "WARNING: On the device use: bugreportz\n");
+ fprintf(stderr, "WARNING: bugreportz will output the filename to use with adb pull.\n");
+ fprintf(stderr,
+ "=============================================================================\n\n\n");
-// This program will trigger the dumpstate service to start a call to
-// dumpstate, then connect to the dumpstate local client to read the
-// output. All of the dumpstate output is written to stdout, including
-// any errors encountered while reading/writing the output.
-int main(int argc, char* /*argv*/[]) {
- fprintf(stderr, "=============================================================================\n");
- fprintf(stderr, "WARNING: Flat (text file, non-zipped) bugreports are deprecated.\n");
- fprintf(stderr, "WARNING: Please generate zipped bugreports instead.\n");
- fprintf(stderr, "WARNING: On the host use: adb bugreport filename.zip\n");
- fprintf(stderr, "WARNING: On the device use: bugreportz\n");
- fprintf(stderr, "WARNING: bugreportz will output the filename to use with adb pull.\n");
- fprintf(stderr, "=============================================================================\n\n\n");
-
- if (argc != 1) {
- fprintf(stderr, "usage: bugreport\n");
- exit(1);
- }
-
- // Start the dumpstate service.
- property_set("ctl.start", "dumpstate");
-
- // Socket will not be available until service starts.
- int s = -1;
- for (int i = 0; i < 20; i++) {
- s = socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED,
- SOCK_STREAM);
- if (s >= 0)
- break;
- // Try again in 1 second.
- sleep(1);
- }
-
- if (s == -1) {
- printf("Failed to connect to dumpstate service: %s\n", strerror(errno));
- return 1;
- }
-
- // Set a timeout so that if nothing is read in 3 minutes, we'll stop
- // reading and quit. No timeout in dumpstate is longer than 60 seconds,
- // so this gives lots of leeway in case of unforeseen time outs.
- struct timeval tv;
- tv.tv_sec = 3 * 60;
- tv.tv_usec = 0;
- if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
- printf("WARNING: Cannot set socket timeout: %s\n", strerror(errno));
- }
-
- while (1) {
- char buffer[65536];
- ssize_t bytes_read = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)));
- if (bytes_read == 0) {
- break;
- } else if (bytes_read == -1) {
- // EAGAIN really means time out, so change the errno.
- if (errno == EAGAIN) {
- errno = ETIMEDOUT;
- }
- printf("\nBugreport read terminated abnormally (%s).\n", strerror(errno));
- break;
- }
-
- ssize_t bytes_to_send = bytes_read;
- ssize_t bytes_written;
- do {
- bytes_written = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
- buffer + bytes_read - bytes_to_send,
- bytes_to_send));
- if (bytes_written == -1) {
- printf("Failed to write data to stdout: read %zd, trying to send %zd (%s)\n",
- bytes_read, bytes_to_send, strerror(errno));
- return 1;
- }
- bytes_to_send -= bytes_written;
- } while (bytes_written != 0 && bytes_to_send > 0);
- }
-
- close(s);
- return 0;
+ return 0;
}
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index bfcc058..ba25a5a 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -39,8 +39,13 @@
std::string calling_package;
};
-static binder::Status exception(uint32_t code, const std::string& msg) {
- MYLOGE("%s (%d) ", msg.c_str(), code);
+static binder::Status exception(uint32_t code, const std::string& msg,
+ const std::string& extra_msg = "") {
+ if (extra_msg.empty()) {
+ MYLOGE("%s (%d) ", msg.c_str(), code);
+ } else {
+ MYLOGE("%s %s (%d) ", msg.c_str(), extra_msg.c_str(), code);
+ }
return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
}
@@ -60,7 +65,7 @@
} // namespace
-DumpstateService::DumpstateService() : ds_(nullptr) {
+DumpstateService::DumpstateService() : ds_(nullptr), calling_uid_(-1), calling_package_() {
}
char const* DumpstateService::getServiceName() {
@@ -131,6 +136,10 @@
ds_->SetOptions(std::move(options));
ds_->listener_ = listener;
+ // Track caller info for cancellation purposes.
+ calling_uid_ = calling_uid;
+ calling_package_ = calling_package;
+
DumpstateInfo* ds_info = new DumpstateInfo();
ds_info->ds = ds_;
ds_info->calling_uid = calling_uid;
@@ -149,8 +158,20 @@
return binder::Status::ok();
}
-binder::Status DumpstateService::cancelBugreport() {
+binder::Status DumpstateService::cancelBugreport(int32_t calling_uid,
+ const std::string& calling_package) {
std::lock_guard<std::mutex> lock(lock_);
+ if (calling_uid != calling_uid_ || calling_package != calling_package_) {
+ // Note: we use a SecurityException to prevent BugreportManagerServiceImpl from killing the
+ // report in progress (from another caller).
+ return exception(
+ binder::Status::EX_SECURITY,
+ StringPrintf("Cancellation requested by %d/%s does not match report in "
+ "progress",
+ calling_uid, calling_package.c_str()),
+ // Sharing the owner of the BR is a (minor) leak, so leave it out of the app's exception
+ StringPrintf("started by %d/%s", calling_uid_, calling_package_.c_str()));
+ }
ds_->Cancel();
return binder::Status::ok();
}
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index ac8d3ac..3ec8471 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -44,8 +44,7 @@
const sp<IDumpstateListener>& listener,
bool is_screenshot_requested) override;
- // No-op
- binder::Status cancelBugreport();
+ binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package);
private:
// Dumpstate object which contains all the bugreporting logic.
@@ -53,6 +52,8 @@
// one bugreport.
// This service does not own this object.
Dumpstate* ds_;
+ int32_t calling_uid_;
+ std::string calling_package_;
std::mutex lock_;
};
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index ba008bb..0793f0b 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (c) 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,9 +19,9 @@
import android.os.IDumpstateListener;
/**
- * Binder interface for the currently running dumpstate process.
- * {@hide}
- */
+ * Binder interface for the currently running dumpstate process.
+ * {@hide}
+ */
interface IDumpstate {
// NOTE: If you add to or change these modes, please also change the corresponding enums
@@ -49,10 +49,10 @@
// Default mode.
const int BUGREPORT_MODE_DEFAULT = 6;
- /*
+ /**
* Starts a bugreport in the background.
*
- *<p>Shows the user a dialog to get consent for sharing the bugreport with the calling
+ * <p>Shows the user a dialog to get consent for sharing the bugreport with the calling
* application. If they deny {@link IDumpstateListener#onError} will be called. If they
* consent and bugreport generation is successful artifacts will be copied to the given fds and
* {@link IDumpstateListener#onFinished} will be called. If there
@@ -71,8 +71,15 @@
int bugreportMode, IDumpstateListener listener,
boolean isScreenshotRequested);
- /*
+ /**
* Cancels the bugreport currently in progress.
+ *
+ * <p>The caller must match the original caller of {@link #startBugreport} in order for the
+ * report to actually be cancelled. A {@link SecurityException} is reported if a mismatch is
+ * detected.
+ *
+ * @param callingUid UID of the original application that requested the cancellation.
+ * @param callingPackage package of the original application that requested the cancellation.
*/
- void cancelBugreport();
+ void cancelBugreport(int callingUid, @utf8InCpp String callingPackage);
}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index cbb74b3..6cb8d83 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -28,6 +28,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mount.h>
#include <sys/poll.h>
#include <sys/prctl.h>
#include <sys/resource.h>
@@ -227,7 +228,6 @@
static const std::string DUMP_HALS_TASK = "DUMP HALS";
static const std::string DUMP_BOARD_TASK = "dumpstate_board()";
static const std::string DUMP_CHECKINS_TASK = "DUMP CHECKINS";
-static const std::string DUMP_APP_INFOS_TASK = "DUMP APP INFOS";
namespace android {
namespace os {
@@ -1550,7 +1550,7 @@
dprintf(out_fd, "========================================================\n");
RunDumpsys("APP PROVIDERS PLATFORM", {"activity", "provider", "all-platform"},
- DUMPSYS_COMPONENTS_OPTIONS, out_fd);
+ DUMPSYS_COMPONENTS_OPTIONS, 0, out_fd);
dprintf(out_fd, "========================================================\n");
dprintf(out_fd, "== Running Application Providers (non-platform)\n");
@@ -1577,7 +1577,6 @@
ds.dump_pool_->enqueueTask(DUMP_INCIDENT_REPORT_TASK, &DumpIncidentReport);
ds.dump_pool_->enqueueTaskWithFd(DUMP_BOARD_TASK, &Dumpstate::DumpstateBoard, &ds, _1);
ds.dump_pool_->enqueueTaskWithFd(DUMP_CHECKINS_TASK, &DumpCheckins, _1);
- ds.dump_pool_->enqueueTaskWithFd(DUMP_APP_INFOS_TASK, &DumpAppInfos, _1);
}
// Dump various things. Note that anything that takes "long" (i.e. several seconds) should
@@ -1730,11 +1729,7 @@
RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_CHECKINS_TASK, DumpCheckins);
}
- if (ds.dump_pool_) {
- WAIT_TASK_WITH_CONSENT_CHECK(DUMP_APP_INFOS_TASK, ds.dump_pool_);
- } else {
- RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK_AND_LOG(DUMP_APP_INFOS_TASK, DumpAppInfos);
- }
+ RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(DumpAppInfos);
printf("========================================================\n");
printf("== Dropbox crashes\n");
@@ -2163,6 +2158,22 @@
return;
}
+ /*
+ * mount debugfs for non-user builds which launch with S and unmount it
+ * after invoking dumpstateBoard_* methods. This is to enable debug builds
+ * to not have debugfs mounted during runtime. It will also ensure that
+ * debugfs is only accessed by the dumpstate HAL.
+ */
+ auto api_level = android::base::GetIntProperty("ro.product.first_api_level", 0);
+ bool mount_debugfs = !PropertiesHelper::IsUserBuild() && api_level >= 31;
+
+ if (mount_debugfs) {
+ RunCommand("mount debugfs", {"mount", "-t", "debugfs", "debugfs", "/sys/kernel/debug"},
+ AS_ROOT_20);
+ RunCommand("chmod debugfs", {"chmod", "0755", "/sys/kernel/debug"},
+ AS_ROOT_20);
+ }
+
std::vector<std::string> paths;
std::vector<android::base::ScopeGuard<std::function<void()>>> remover;
for (int i = 0; i < NUM_OF_DUMPS; i++) {
@@ -2262,6 +2273,10 @@
"there might be racing in content\n", killing_timeout_sec);
}
+ if (mount_debugfs) {
+ RunCommand("unmount debugfs", {"umount", "/sys/kernel/debug"}, AS_ROOT_20);
+ }
+
auto file_sizes = std::make_unique<ssize_t[]>(paths.size());
for (size_t i = 0; i < paths.size(); i++) {
struct stat s;
@@ -2864,31 +2879,23 @@
// duration is logged into MYLOG instead.
PrintHeader();
- // TODO(b/158737089) reduce code repetition in if branches
- if (options_->telephony_only) {
- MaybeTakeEarlyScreenshot();
- onUiIntensiveBugreportDumpsFinished(calling_uid);
- MaybeCheckUserConsent(calling_uid, calling_package);
- DumpstateTelephonyOnly(calling_package);
- } else if (options_->wifi_only) {
- MaybeTakeEarlyScreenshot();
- onUiIntensiveBugreportDumpsFinished(calling_uid);
- MaybeCheckUserConsent(calling_uid, calling_package);
- DumpstateWifiOnly();
- } else if (options_->limited_only) {
- MaybeTakeEarlyScreenshot();
- onUiIntensiveBugreportDumpsFinished(calling_uid);
- MaybeCheckUserConsent(calling_uid, calling_package);
- DumpstateLimitedOnly();
- } else {
+ bool is_dumpstate_restricted = options_->telephony_only
+ || options_->wifi_only
+ || options_->limited_only;
+ if (!is_dumpstate_restricted) {
// Invoke critical dumpsys first to preserve system state, before doing anything else.
RunDumpsysCritical();
-
- // Take screenshot and get consent only after critical dumpsys has finished.
- MaybeTakeEarlyScreenshot();
- onUiIntensiveBugreportDumpsFinished(calling_uid);
- MaybeCheckUserConsent(calling_uid, calling_package);
-
+ }
+ MaybeTakeEarlyScreenshot();
+ onUiIntensiveBugreportDumpsFinished(calling_uid);
+ MaybeCheckUserConsent(calling_uid, calling_package);
+ if (options_->telephony_only) {
+ DumpstateTelephonyOnly(calling_package);
+ } else if (options_->wifi_only) {
+ DumpstateWifiOnly();
+ } else if (options_->limited_only) {
+ DumpstateLimitedOnly();
+ } else {
// Dump state for the default case. This also drops root.
RunStatus s = DumpstateDefaultAfterCritical();
if (s != RunStatus::OK) {
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index fe6a34a..0e366cb 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -319,6 +319,16 @@
*/
class BugreportSectionTest : public Test {
public:
+ ZipArchiveHandle handle;
+
+ void SetUp() {
+ ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath().c_str(), &handle), 0);
+ }
+
+ void TearDown() {
+ CloseArchive(handle);
+ }
+
static void SetUpTestCase() {
ParseSections(ZippedBugreportGenerationTest::getZipFilePath().c_str(),
ZippedBugreportGenerationTest::sections.get());
@@ -343,6 +353,19 @@
}
FAIL() << sectionName << " not found.";
}
+
+ /**
+ * Whether or not the content of the section is injected by other commands.
+ */
+ bool IsContentInjectedByOthers(const std::string& line) {
+ // Command header such as `------ APP ACTIVITIES (/system/bin/dumpsys activity -v) ------`.
+ static const std::regex kCommandHeader = std::regex{"------ .+ \\(.+\\) ------"};
+ std::smatch match;
+ if (std::regex_match(line, match, kCommandHeader)) {
+ return true;
+ }
+ return false;
+ }
};
TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
@@ -400,6 +423,28 @@
SectionExists("wifi", /* bytes= */ 100000);
}
+TEST_F(BugreportSectionTest, NoInjectedContentByOtherCommand) {
+ // Extract the main entry to a temp file
+ TemporaryFile tmp_binary;
+ ASSERT_NE(-1, tmp_binary.fd);
+ ExtractBugreport(&handle, tmp_binary.fd);
+
+ // Read line by line and identify sections
+ std::ifstream ifs(tmp_binary.path, std::ifstream::in);
+ std::string line;
+ std::string current_section_name;
+ while (std::getline(ifs, line)) {
+ std::string section_name;
+ if (IsSectionStart(line, §ion_name)) {
+ current_section_name = section_name;
+ } else if (IsSectionEnd(line)) {
+ current_section_name = "";
+ } else if (!current_section_name.empty()) {
+ EXPECT_FALSE(IsContentInjectedByOthers(line));
+ }
+ }
+}
+
class DumpstateBinderTest : public Test {
protected:
void SetUp() override {
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index 1327cfd..a017246 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -427,7 +427,7 @@
<< strerror(errno) << std::endl;
status = -errno;
break;
- } else if (rc == 0) {
+ } else if (rc == 0 || time_left_ms() == 0) {
status = TIMED_OUT;
break;
}
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index 64bfdf9..643b3ca 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -15,7 +15,7 @@
cc_defaults {
name: "idlcli-defaults",
shared_libs: [
- "android.hardware.vibrator-ndk_platform",
+ "android.hardware.vibrator-unstable-ndk_platform",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index b821578..818804a 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -398,6 +398,10 @@
PLOG(ERROR) << "Failed to prepare " << profile_dir;
return false;
}
+ if (selinux_android_restorecon(profile_dir.c_str(), 0)) {
+ PLOG(ERROR) << "Failed to restorecon " << profile_dir;
+ return false;
+ }
const std::string ref_profile_path =
create_primary_reference_profile_package_dir_path(packageName);
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 594880a..65fc46e 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -1818,10 +1818,13 @@
pid_t pid = fork();
if (pid == 0) {
+ // Need to set schedpolicy before dropping privileges
+ // for cgroup migration. See details at b/175178520.
+ SetDex2OatScheduling(boot_complete);
+
/* child -- drop privileges before continuing */
drop_capabilities(uid);
- SetDex2OatScheduling(boot_complete);
if (flock(out_oat.fd(), LOCK_EX | LOCK_NB) != 0) {
PLOG(ERROR) << "flock(" << out_oat.path() << ") failed";
_exit(DexoptReturnCodes::kFlock);
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 987adaf..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",
@@ -72,15 +74,18 @@
cc_test {
name: "lshal_test",
test_suites: ["device-tests"],
- defaults: ["lshal_defaults"],
+ defaults: [
+ "libvintf_static_user_defaults",
+ "lshal_defaults"
+ ],
gtest: true,
static_libs: [
"android.hardware.tests.inheritance@1.0",
"libgmock",
+ "libvintf",
],
shared_libs: [
"libhidlbase",
- "libvintf",
],
srcs: [
"test.cpp"
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/android.software.opengles.deqp.level-2020-03-01.xml b/data/etc/android.software.opengles.deqp.level-2020-03-01.xml
new file mode 100644
index 0000000..f11e0bb
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2020-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with date 2020-03-01 (0x07E40301). -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132383489" />
+</permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-2021-03-01.xml b/data/etc/android.software.opengles.deqp.level-2021-03-01.xml
new file mode 100644
index 0000000..b60697d
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2021-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with date 2021-03-01 (0x07E50301). -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132449025" />
+</permissions>
diff --git a/data/etc/android.software.translation.xml b/data/etc/android.software.translation.xml
new file mode 100644
index 0000000..3b361e5
--- /dev/null
+++ b/data/etc/android.software.translation.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<permissions>
+ <feature name="android.software.translation" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml
index 9c67d4a..d3ad45a 100644
--- a/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml
+++ b/data/etc/android.software.vulkan.deqp.level-2019-03-01.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<!-- This is the standard feature indicating that the device passes Vulkan deQP
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
tests associated with date 2019-03-01 (0x07E30301). -->
<permissions>
<feature name="android.software.vulkan.deqp.level" version="132317953" />
diff --git a/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml
index 19b269b..84ba389 100644
--- a/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml
+++ b/data/etc/android.software.vulkan.deqp.level-2020-03-01.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<!-- This is the standard feature indicating that the device passes Vulkan deQP
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
tests associated with date 2020-03-01 (0x07E40301). -->
<permissions>
<feature name="android.software.vulkan.deqp.level" version="132383489" />
diff --git a/data/etc/android.software.vulkan.deqp.level-2021-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2021-03-01.xml
new file mode 100644
index 0000000..ae26269
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2021-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+ tests associated with date 2021-03-01 (0x07E50301). -->
+<permissions>
+ <feature name="android.software.vulkan.deqp.level" version="132449025" />
+</permissions>
diff --git a/data/etc/cec_config.xml b/data/etc/cec_config.xml
deleted file mode 100644
index 480e0ec..0000000
--- a/data/etc/cec_config.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
-<cec-settings>
- <setting name="hdmi_cec_enabled"
- value-type="int"
- user-configurable="true">
- <allowed-values>
- <value int-value="0" />
- <value int-value="1" />
- </allowed-values>
- <default-value int-value="1" />
- </setting>
- <setting name="hdmi_cec_version"
- value-type="int"
- user-configurable="true">
- <allowed-values>
- <value int-value="0x05" />
- <value int-value="0x06" />
- </allowed-values>
- <default-value int-value="0x05" />
- </setting>
- <setting name="send_standby_on_sleep"
- value-type="string"
- user-configurable="true">
- <allowed-values>
- <value string-value="to_tv" />
- <value string-value="broadcast" />
- <value string-value="none" />
- </allowed-values>
- <default-value string-value="to_tv" />
- </setting>
- <setting name="power_state_change_on_active_source_lost"
- value-type="string"
- user-configurable="true">
- <allowed-values>
- <value string-value="none" />
- <value string-value="standby_now" />
- </allowed-values>
- <default-value string-value="none" />
- </setting>
- <setting name="system_audio_mode_muting"
- value-type="int"
- user-configurable="true">
- <allowed-values>
- <value int-value="0" />
- <value int-value="1" />
- </allowed-values>
- <default-value int-value="1" />
- </setting>
-</cec-settings>
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
index dc6963f..2c34047 100644
--- a/data/etc/handheld_core_hardware.xml
+++ b/data/etc/handheld_core_hardware.xml
@@ -52,6 +52,7 @@
<feature name="android.software.autofill" />
<feature name="android.software.cant_save_state" />
<feature name="android.software.secure_lock_screen" />
+ <feature name="android.software.translation" />
<!-- Feature to specify if the device supports adding device admins. -->
<feature name="android.software.device_admin" />
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/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
index e878f86..873b5b7 100644
--- a/data/etc/tablet_core_hardware.xml
+++ b/data/etc/tablet_core_hardware.xml
@@ -52,6 +52,7 @@
<feature name="android.software.autofill" />
<feature name="android.software.cant_save_state" />
<feature name="android.software.secure_lock_screen" />
+ <feature name="android.software.translation" />
<!-- Feature to specify if the device supports adding device admins. -->
<feature name="android.software.device_admin" />
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index 3dd1534..98401f7 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -57,13 +57,16 @@
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. Use {@link AImageDecoder_resultToString} for a readable
+ * version of the result code.
*/
enum {
/**
@@ -107,14 +110,46 @@
/**
* AImageDecoder did not recognize the format.
*/
- ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT = -9
+ ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT = -9,
+ /**
+ * The animation has reached the end.
+ */
+ ANDROID_IMAGE_DECODER_FINISHED = -10,
+ /**
+ * This method cannot be called while the AImageDecoder is in its current
+ * state. For example, various setters (like {@link AImageDecoder_setTargetSize})
+ * can only be called while the AImageDecoder is set to decode the first
+ * frame of an animation. This ensures that any blending and/or restoring
+ * prior frames works correctly.
+ */
+ ANDROID_IMAGE_DECODER_INVALID_STATE = -11,
};
+#if __ANDROID_API__ >= 31
+
+/**
+ * Return a constant string value representing the error code.
+ *
+ * Introduced in API 31.
+ *
+ * Pass the return value from an {@link AImageDecoder} method (e.g.
+ * {@link AImageDecoder_decodeImage}) for a text string representing the error
+ * code.
+ *
+ * Errors:
+ * - Returns null for a value out of range.
+ */
+const char* _Nullable AImageDecoder_resultToString(int)__INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
struct AImageDecoder;
/**
* Opaque handle for decoding images.
*
+ * Introduced in API 30
+ *
* Create using one of the following:
* - {@link AImageDecoder_createFromAAsset}
* - {@link AImageDecoder_createFromFd}
@@ -123,13 +158,15 @@
* After creation, {@link AImageDecoder_getHeaderInfo} can be used to retrieve
* information about the encoded image. Other functions, like
* {@link AImageDecoder_setTargetSize}, can be used to specify how to decode, and
- * {@link AImageDecoder_decode} will decode into client provided memory.
+ * {@link AImageDecoder_decodeImage} will decode into client provided memory.
*
* {@link AImageDecoder} objects are NOT thread-safe, and should not be shared across
* threads.
*/
typedef struct AImageDecoder AImageDecoder;
+#if __ANDROID_API__ >= 30
+
/**
* Create a new {@link AImageDecoder} from an {@link AAsset}.
*
@@ -159,7 +196,7 @@
* supported.
*/
int AImageDecoder_createFromAAsset(struct AAsset* _Nonnull asset,
- AImageDecoder* _Nonnull * _Nonnull outDecoder)
+ AImageDecoder* _Nullable * _Nonnull outDecoder)
__INTRODUCED_IN(30);
/**
@@ -190,7 +227,7 @@
* - {@link ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT}: The format is not
* supported.
*/
-int AImageDecoder_createFromFd(int fd, AImageDecoder* _Nonnull * _Nonnull outDecoder)
+int AImageDecoder_createFromFd(int fd, AImageDecoder* _Nullable * _Nonnull outDecoder)
__INTRODUCED_IN(30);
/**
@@ -221,7 +258,7 @@
* supported.
*/
int AImageDecoder_createFromBuffer(const void* _Nonnull buffer, size_t length,
- AImageDecoder* _Nonnull * _Nonnull outDecoder)
+ AImageDecoder* _Nullable * _Nonnull outDecoder)
__INTRODUCED_IN(30);
/**
@@ -229,11 +266,15 @@
*
* Available since API level 30.
*/
-void AImageDecoder_delete(AImageDecoder* _Nonnull decoder) __INTRODUCED_IN(30);
+void AImageDecoder_delete(AImageDecoder* _Nullable decoder) __INTRODUCED_IN(30);
/**
* Choose the desired output format.
*
+ * If the encoded image represents an animation, this must be called while on
+ * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
+ * after calling {@link AImageDecoder_rewind}).
+ *
* Available since API level 30.
*
* @param format {@link AndroidBitmapFormat} to use for the output.
@@ -249,6 +290,8 @@
* {@link AndroidBitmapFormat}.
* - {@link ANDROID_IMAGE_DECODER_INVALID_CONVERSION}: The
* {@link AndroidBitmapFormat} is incompatible with the image.
+ * - {@link ANDROID_IMAGE_DECODER_INVALID_STATE}: The animation is not on
+ * the first frame.
*/
int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* _Nonnull decoder,
int32_t format) __INTRODUCED_IN(30);
@@ -260,6 +303,10 @@
* Pass true to this method to leave them unpremultiplied. This has no effect on an
* opaque image.
*
+ * If the encoded image represents an animation, this must be called while on
+ * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
+ * after calling {@link AImageDecoder_rewind}).
+ *
* Available since API level 30.
*
* @param unpremultipliedRequired Pass true to leave the pixels unpremultiplied.
@@ -272,6 +319,8 @@
* {@link AImageDecoder_setTargetSize}.
* - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The
* {@link AImageDecoder} is null.
+ * - {@link ANDROID_IMAGE_DECODER_INVALID_STATE}: The animation is not on
+ * the first frame.
*/
int AImageDecoder_setUnpremultipliedRequired(AImageDecoder* _Nonnull decoder,
bool unpremultipliedRequired) __INTRODUCED_IN(30);
@@ -282,6 +331,10 @@
* Ignored by {@link ANDROID_BITMAP_FORMAT_A_8}, which does not support
* an {@link ADataSpace}.
*
+ * If the encoded image represents an animation, this must be called while on
+ * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
+ * after calling {@link AImageDecoder_rewind}).
+ *
* Available since API level 30.
*
* @param dataspace The {@link ADataSpace} to decode into. An ADataSpace
@@ -297,6 +350,8 @@
* - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The
* {@link AImageDecoder} is null or |dataspace| does not correspond to an
* {@link ADataSpace} value.
+ * - {@link ANDROID_IMAGE_DECODER_INVALID_STATE}: The animation is not on
+ * the first frame.
*/
int AImageDecoder_setDataSpace(AImageDecoder* _Nonnull decoder, int32_t dataspace)
__INTRODUCED_IN(30);
@@ -310,6 +365,10 @@
* specified by width and height, and the output image will be the size of the
* crop rect.
*
+ * If the encoded image represents an animation, this must be called while on
+ * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
+ * after calling {@link AImageDecoder_rewind}).
+ *
* Available since API level 30.
*
* @param width Width of the output (prior to cropping).
@@ -324,14 +383,15 @@
* - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The
* {@link AImageDecoder} is null.
* - {@link ANDROID_IMAGE_DECODER_INVALID_SCALE}: |width| or |height| is <= 0,
- * the size is too big, any existing crop is not contained by the new image dimensions,
- * or the scale is incompatible with a previous call to
+ * the size is too big, any existing crop is not contained by the new image
+ * dimensions, or the scale is incompatible with a previous call to
* {@link AImageDecoder_setUnpremultipliedRequired}(true).
+ * - {@link ANDROID_IMAGE_DECODER_INVALID_STATE}: The animation is not on
+ * the first frame.
*/
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 +421,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).
*
@@ -368,6 +429,10 @@
* the specified {@link ARect}. Clients will only need to allocate enough memory
* for the cropped ARect.
*
+ * If the encoded image represents an animation, this must be called while on
+ * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
+ * after calling {@link AImageDecoder_rewind}).
+ *
* Available since API level 30.
*
* @param crop Rectangle describing a crop of the decode. It must be contained inside of
@@ -383,20 +448,29 @@
*
* Errors:
* - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The
- * {@link AImageDecoder} is null or the crop is not contained by the
+ * {@link AImageDecoder} is null, or the crop is not contained by the
* (possibly scaled) image dimensions.
+ * - {@link ANDROID_IMAGE_DECODER_INVALID_STATE}: The animation is not on
+ * the first frame.
*/
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.
*
@@ -459,6 +533,10 @@
* {@link AImageDecoder_decodeImage} will premultiply pixels by default.
*
* Available since API level 30.
+ *
+ * Starting in API level 31, an AImageDecoder may contain multiple frames of an
+ * animation, but this method still only reports whether the first frame has
+ * alpha.
*/
int AImageDecoderHeaderInfo_getAlphaFlags(
const AImageDecoderHeaderInfo* _Nonnull) __INTRODUCED_IN(30);
@@ -505,8 +583,45 @@
* Available since API level 30.
*
* Starting in API level 31, it can be used to decode all of the frames of an
- * animated image (i.e. GIF, WebP, HEIF) using new APIs (TODO (scroggo): list
- * and describe here).
+ * animated image (i.e. GIF, WebP) using new APIs. Internally,
+ * AImageDecoder keeps track of its "current frame" - that is, the frame that
+ * will be decoded by a call to AImageDecoder_decodeImage. At creation time, the
+ * current frame is always the first frame, and multiple calls to this method
+ * will each decode the first frame. {@link AImageDecoder_advanceFrame} advances
+ * the current frame to the following frame, so that future calls to this method
+ * will decode that frame. Some frames may update only part of the image. They
+ * may only update a sub-rectangle (see {@link
+ * AImageDecoderFrameInfo_getFrameRect}), or they may have alpha (see
+ * {@link AImageDecoderFrameInfo_hasAlphaWithinBounds}). In these cases, this
+ * method assumes that the prior frame is still residing in the |pixels| buffer,
+ * decodes only the new portion, and blends it with the buffer. Frames that change
+ * the entire |pixels| buffer are "independent", and do not require the prior
+ * frame to remain in the buffer. The first frame is always independent. A
+ * sophisticated client can use information from the {@link AImageDecoderFrameInfo}
+ * to determine whether other frames are independent, or what frames they rely on.
+ *
+ * If the current frame is marked {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS},
+ * AImageDecoder_decodeImage will store the |pixels| buffer prior to decoding
+ * (note: this only happens for the first in a string of consecutive
+ * ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frames). After advancing to the
+ * following frame, AImageDecoder_decodeImage will restore that buffer prior to
+ * decoding that frame. This is the default behavior, but it can be disabled
+ * by passing false to {@link AImageDecoder_setInternallyHandleDisposePrevious}.
+ *
+ * Ignoring timing information, display, etc, a client wishing to decode all
+ * frames of an animated image may conceptually use code like the following:
+ *
+ * while (true) {
+ * int result = AImageDecoder_decodeImage(decoder, pixels, stride, size);
+ * if (result != ANDROID_IMAGE_DECODER_SUCCESS) break;
+ *
+ * // Display or save the image in |pixels|, keeping the buffer intact for
+ * // AImageDecoder to decode the next frame correctly.
+ * Application_viewImage(pixels);
+ *
+ * result = AImageDecoder_advanceFrame(decoder);
+ * if (result != ANDROID_IMAGE_DECODER_SUCCESS) break;
+ * }
*
* @param decoder Opaque object representing the decoder.
* @param pixels On success, will be filled with the result
@@ -534,6 +649,10 @@
* failed to seek.
* - {@link ANDROID_IMAGE_DECODER_INTERNAL_ERROR}: Some other error, like a
* failure to allocate memory.
+ * - {@link ANDROID_IMAGE_DECODER_FINISHED}: The input contains no
+ * more frames. No decoding occurred. The client must call
+ * {@link AImageDecoder_rewind} before calling
+ * {@link AImageDecoder_decodeImage} again.
*/
int AImageDecoder_decodeImage(AImageDecoder* _Nonnull decoder,
void* _Nonnull pixels, size_t stride,
@@ -548,8 +667,8 @@
*
* Introduced in API 31.
*
- * This may require seeking past the first frame to verify whether
- * there is a following frame (e.g. for GIF).
+ * A single frame GIF is considered to *not* be animated. This may require
+ * seeking past the first frame to verify whether there is a following frame.
*
* Errors:
* - returns false if |decoder| is null.
@@ -557,14 +676,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.
*
@@ -588,9 +713,326 @@
* - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The AImageDecoder
* is null.
*/
-int32_t AImageDecoder_getRepeatCount(AImageDecoder* _Nonnull decoder);
+int32_t AImageDecoder_getRepeatCount(AImageDecoder* _Nonnull decoder)
__INTRODUCED_IN(31);
+/**
+ * Advance to the next frame in the animation.
+ *
+ * Introduced in API 31.
+ *
+ * The AImageDecoder keeps track internally which frame it is ready to decode
+ * (the "current frame"). Initially it is set to decode the first frame, and
+ * each call to {@link AImageDecoder_decodeImage} will continue to decode
+ * the same frame until this method (or {@link AImageDecoder_rewind})
+ * is called.
+ *
+ * Note that this can be used to skip a frame without decoding it. But
+ * some frames depend on (i.e. blend with) prior frames, and
+ * AImageDecoder_decodeImage assumes that the prior frame is in the
+ * |pixels| buffer. In addition, AImageDecoder_decodeImage handles caching and
+ * restoring frames (see {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS}), so
+ * skipping frames in an image with such frames may not produce the correct
+ * results.
+ *
+ * @return {@link ANDROID_IMAGE_DECODER_SUCCESS} on success or a value
+ * indicating the reason for the failure.
+ *
+ * Errors:
+ * - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The AImageDecoder
+ * represents an image that is not animated (see
+ * {@link AImageDecoder_isAnimated}) or the AImageDecoder is null.
+ * - {@link ANDROID_IMAGE_DECODER_INCOMPLETE}: The input appears
+ * to be truncated. The client must call {@link AImageDecoder_rewind}
+ * before calling {@link AImageDecoder_decodeImage} again.
+ * - {@link ANDROID_IMAGE_DECODER_ERROR}: The input contains an error.
+ * The client must call {@link AImageDecoder_rewind} before
+ * calling {@link AImageDecoder_decodeImage} again.
+ * - {@link ANDROID_IMAGE_DECODER_FINISHED}: The input contains no
+ * more frames. The client must call {@link AImageDecoder_rewind}
+ * before calling {@link AImageDecoder_decodeImage} again.
+ */
+int AImageDecoder_advanceFrame(AImageDecoder* _Nonnull decoder)
+ __INTRODUCED_IN(31);
+
+/**
+ * Return to the beginning of the animation.
+ *
+ * Introduced in API 31.
+ *
+ * After this call, the AImageDecoder will be ready to decode the
+ * first frame of the animation. This can be called after reaching
+ * the end of the animation or an error or in the middle of the
+ * animation.
+ *
+ * @return {@link ANDROID_IMAGE_DECODER_SUCCESS} on success or a value
+ * indicating the reason for the failure.
+ *
+ * Errors:
+ * - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: The AImageDecoder
+ * represents an image that is not animated (see
+ * {@link AImageDecoder_isAnimated}) or the AImageDecoder is
+ * null.
+ * - {@link ANDROID_IMAGE_DECODER_SEEK_ERROR}: The asset or file
+ * descriptor failed to seek.
+ */
+int AImageDecoder_rewind(AImageDecoder* _Nonnull decoder)
+ __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
+struct AImageDecoderFrameInfo;
+
+/**
+ * Opaque handle to animation information about a single frame.
+ *
+ * Introduced in API 31
+ *
+ * The duration (retrieved with {@link AImageDecoderFrameInfo_getDuration}) is
+ * necessary for clients to display the animation at the proper speed. The other
+ * information is helpful for a client that wants to determine what frames are
+ * independent (or what frames they depend on), but is unnecessary for
+ * a simple client that wants to sequentially display all frames.
+ */
+typedef struct AImageDecoderFrameInfo AImageDecoderFrameInfo;
+
+#if __ANDROID_API__ >= 31
+
+/**
+ * Create an uninitialized AImageDecoderFrameInfo.
+ *
+ * Introduced in API 31.
+ *
+ * This can be passed to {@link AImageDecoder_getFrameInfo} to fill
+ * in information about the current frame. It may be reused.
+ *
+ * Must be deleted with {@link AImageDecoderFrameInfo_delete}.
+ */
+AImageDecoderFrameInfo* _Nullable AImageDecoderFrameInfo_create()
+ __INTRODUCED_IN(31);
+
+/**
+ * Delete an AImageDecoderFrameInfo.
+ *
+ * Introduced in API 31.
+ */
+void AImageDecoderFrameInfo_delete(
+ AImageDecoderFrameInfo* _Nullable info) __INTRODUCED_IN(31);
+
+/**
+ * Fill |info| with information about the current frame.
+ *
+ * Introduced in API 31.
+ *
+ * Initially, this will return information about the first frame.
+ * {@link AImageDecoder_advanceFrame} and
+ * {@link AImageDecoder_rewind} can be used to change which frame
+ * is the current frame.
+ *
+ * If the image only has one frame, this will fill the {@link
+ * AImageDecoderFrameInfo} with the encoded info, if any, or reasonable
+ * defaults.
+ *
+ * @param decoder Opaque object representing the decoder.
+ * @param info Opaque object to hold frame information. On success, will be
+ * filled with information regarding the current frame.
+ * @return {@link ANDROID_IMAGE_DECODER_SUCCESS} on success or a value
+ * indicating the reason for the failure.
+ *
+ * Errors:
+ * - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER}: One of the parameters is null.
+ * - {@link ANDROID_IMAGE_DECODER_FINISHED}: The input contains no
+ * more frames. The client must call {@link AImageDecoder_rewind} to reset the
+ * current frame to a valid frame (0).
+ */
+int AImageDecoder_getFrameInfo(AImageDecoder* _Nonnull decoder,
+ AImageDecoderFrameInfo* _Nonnull info) __INTRODUCED_IN(31);
+
+/**
+ * Report the number of nanoseconds to show the current frame.
+ *
+ * Introduced in API 31.
+ *
+ * Errors:
+ * - returns 0 if |info| is null.
+ */
+int64_t AImageDecoderFrameInfo_getDuration(
+ const AImageDecoderFrameInfo* _Nonnull info) __INTRODUCED_IN(31);
+
+/**
+ * The rectangle of the image (within 0, 0,
+ * {@link AImageDecoder_getWidth}, {@link AImageDecoder_getHeight})
+ * updated by this frame.
+ *
+ * Introduced in API 31.
+ *
+ * Note that this is unaffected by calls to
+ * {@link AImageDecoder_setTargetSize} or
+ * {@link AImageDecoder_setCrop}.
+ *
+ * A frame may update only part of the image. This will always be
+ * contained by the image’s dimensions.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles blending frames, so a simple
+ * sequential client does not need this.
+ *
+ * Errors:
+ * - returns an empty ARect if |info| is null.
+ */
+ARect AImageDecoderFrameInfo_getFrameRect(
+ const AImageDecoderFrameInfo* _Nonnull info) __INTRODUCED_IN(31);
+
+/**
+ * Whether the new portion of this frame may contain alpha.
+ *
+ * Introduced in API 31.
+ *
+ * Note that this may differ from whether the composed frame has
+ * alpha. If this frame does not fill the entire image dimensions
+ * (see {@link AImageDecoderFrameInfo_getFrameRect}) or it blends
+ * with an opaque frame, for example, the composed frame’s alpha
+ * may not match. It is also conservative; for example, if a color
+ * index-based frame has a color with alpha but does not use it,
+ * this will still return true.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles blending frames, so a simple
+ * sequential client does not need this.
+ *
+ * Errors:
+ * - returns false if |info| is null.
+ */
+bool AImageDecoderFrameInfo_hasAlphaWithinBounds(
+ const AImageDecoderFrameInfo* _Nonnull info) __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
+/**
+ * How a frame is “disposed” before showing the next one.
+ *
+ * Introduced in API 31.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles disposing of frames, so a simple
+ * sequential client does not need this.
+ */
+enum {
+ // No disposal. The following frame will be drawn directly
+ // on top of this one.
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE = 1,
+ // The frame’s rectangle is cleared to transparent (by AImageDecoder)
+ // before decoding the next frame.
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND = 2,
+ // The frame’s rectangle is reverted to the prior frame before decoding
+ // the next frame. This is handled by AImageDecoder, unless
+ // {@link AImageDecoder_setInternallyHandleDisposePrevious} is set to false.
+ ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS = 3,
+};
+
+#if __ANDROID_API__ >= 31
+
+/**
+ * Return how this frame is “disposed” before showing the next one.
+ *
+ * Introduced in API 31.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles disposing of frames, so a simple
+ * sequential client does not need this.
+ *
+ * @return one of:
+ * - {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE}
+ * - {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND}
+ * - {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS}
+ * Errors:
+ * - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER} if |info| is null.
+ */
+int32_t AImageDecoderFrameInfo_getDisposeOp(
+ const AImageDecoderFrameInfo* _Nonnull info) __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
+/**
+ * How a frame is blended with the previous frame.
+ *
+ * Introduced in API 31.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles blending frames, so a simple
+ * sequential client does not need this.
+ */
+enum {
+ // This frame replaces existing content. This corresponds
+ // to webp’s “do not blend”.
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC = 1,
+ // This frame blends with the previous frame.
+ ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER = 2,
+};
+
+#if __ANDROID_API__ >= 31
+
+/**
+ * Return how this frame is blended with the previous frame.
+ *
+ * Introduced in API 31.
+ *
+ * This, along with other information in AImageDecoderFrameInfo,
+ * can be useful for determining whether a frame is independent, but
+ * the decoder handles blending frames, so a simple
+ * sequential client does not need this.
+ *
+ * @return one of:
+ * - {@link ANDROID_IMAGE_DECODER_BLEND_OP_SRC}
+ * - {@link ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER}
+ * Errors:
+ * - {@link ANDROID_IMAGE_DECODER_BAD_PARAMETER} if |info| is null.
+ */
+int32_t AImageDecoderFrameInfo_getBlendOp(
+ const AImageDecoderFrameInfo* _Nonnull info)
+ __INTRODUCED_IN(31);
+
+/**
+ * Whether to have AImageDecoder store the frame prior to a
+ * frame marked {@link ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS}.
+ *
+ * Introduced in API 31.
+ *
+ * The default is true. Many images will not have such a frame (it
+ * is not supported by WebP, and only some GIFs use it). But
+ * if frame i is ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, then i+1
+ * may depend on i-1. When this setting is true, AImageDecoder will
+ * defensively copy frame i-1 (i.e. the contents of |pixels| in
+ * {@link AImageDecoder_decodeImage}) into an internal buffer so that
+ * it can be used to decode i+1.
+ *
+ * AImageDecoder will only store a single frame, at the size specified
+ * by {@link AImageDecoder_setTargetSize} (or the original dimensions
+ * if that method has not been called), and will discard it when it is
+ * no longer necessary.
+ *
+ * A client that desires to manually store such frames may set this to
+ * false, so that AImageDecoder does not need to store this extra
+ * frame. Instead, when decoding the same
+ * ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frame i, AImageDecoder
+ * will decode directly into |pixels|, assuming the client stored i-1.
+ * When asked to decode frame i+1, AImageDecoder will now assume that
+ * the client provided i-1 in |pixels|.
+ *
+ * @param handleInternally Whether AImageDecoder will internally
+ * handle ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS
+ * frames.
+ */
+void AImageDecoder_setInternallyHandleDisposePrevious(
+ AImageDecoder* _Nonnull decoder, bool handleInternally)
+ __INTRODUCED_IN(31);
+
+
#endif // __ANDROID_API__ >= 31
#ifdef __cplusplus
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/android/thermal.h b/include/android/thermal.h
index 83582d6..eb81534 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -53,7 +53,7 @@
#include <sys/types.h>
#if !defined(__INTRODUCED_IN)
-#define __INTRODUCED_IN(30) /* Introduced in API level 30 */
+#define __INTRODUCED_IN(__api_level) /* nothing */
#endif
#ifdef __cplusplus
@@ -181,6 +181,51 @@
#endif // __ANDROID_API__ >= 30
+#if __ANDROID_API__ >= 31
+
+/**
+ * Provides an estimate of how much thermal headroom the device currently has before
+ * hitting severe throttling.
+ *
+ * Note that this only attempts to track the headroom of slow-moving sensors, such as
+ * the skin temperature sensor. This means that there is no benefit to calling this function
+ * more frequently than about once per second, and attempted to call significantly
+ * more frequently may result in the function returning {@code NaN}.
+ *
+ * In addition, in order to be able to provide an accurate forecast, the system does
+ * not attempt to forecast until it has multiple temperature samples from which to
+ * extrapolate. This should only take a few seconds from the time of the first call,
+ * but during this time, no forecasting will occur, and the current headroom will be
+ * returned regardless of the value of {@code forecastSeconds}.
+ *
+ * The value returned is a non-negative float that represents how much of the thermal envelope
+ * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is
+ * (or will be) throttled at {@link #THERMAL_STATUS_SEVERE}. Such throttling can affect the
+ * CPU, GPU, and other subsystems. Values may exceed 1.0, but there is no implied mapping
+ * to specific thermal levels beyond that point. This means that values greater than 1.0
+ * may correspond to {@link #THERMAL_STATUS_SEVERE}, but may also represent heavier throttling.
+ *
+ * A value of 0.0 corresponds to a fixed distance from 1.0, but does not correspond to any
+ * particular thermal status or temperature. Values on (0.0, 1.0] may be expected to scale
+ * linearly with temperature, though temperature changes over time are typically not linear.
+ * Negative values will be clamped to 0.0 before returning.
+ *
+ * Available since API level 31.
+ *
+ * @param manager The manager instance to use.
+ * Acquired via {@link AThermal_acquireManager}.
+ * @param forecastSeconds how many seconds into the future to forecast. Given that device
+ * conditions may change at any time, forecasts from further in the
+ * future will likely be less accurate than forecasts in the near future.
+ * @return a value greater than equal to 0.0, where 1.0 indicates the SEVERE throttling threshold,
+ * as described above. Returns NaN if the device does not support this functionality or
+ * if this function is called significantly faster than once per second.
+ */
+float AThermal_getThermalHeadroom(AThermalManager *manager,
+ int forecastSeconds) __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
#ifdef __cplusplus
}
#endif
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 639df7a..4aa2f60 100644
--- a/include/audiomanager/AudioManager.h
+++ b/include/audiomanager/AudioManager.h
@@ -37,6 +37,7 @@
PLAYER_STATE_STARTED = 2,
PLAYER_STATE_PAUSED = 3,
PLAYER_STATE_STOPPED = 4,
+ PLAYER_UPDATE_DEVICE_ID = 5,
} player_state_t;
// must be kept in sync with definitions in AudioManager.java
diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h
index 2f5ccb8..7d1f38f 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -49,7 +49,8 @@
audio_content_type_t content, const sp<IBinder>& player) = 0;
/*oneway*/ virtual status_t playerAttributes(audio_unique_id_t piid, audio_usage_t usage,
audio_content_type_t content)= 0;
- /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event) = 0;
+ /*oneway*/ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
+ audio_port_handle_t deviceId) = 0;
/*oneway*/ virtual status_t releasePlayer(audio_unique_id_t piid) = 0;
virtual audio_unique_id_t trackRecorder(const sp<IBinder>& recorder) = 0;
/*oneway*/ virtual status_t recorderEvent(audio_unique_id_t riid, recorder_state_t event) = 0;
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/DisplayViewport.h b/include/input/DisplayViewport.h
index b90d57e..5e40ca7 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -119,7 +119,7 @@
physicalBottom = height;
deviceWidth = width;
deviceHeight = height;
- isActive = false;
+ isActive = true;
uniqueId.clear();
physicalPort = std::nullopt;
type = ViewportType::INTERNAL;
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..9ea9732 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: [
@@ -182,7 +179,26 @@
"--header-filter=^.*frameworks/native/libs/binder/.*.h$",
],
tidy_checks_as_errors: [
- "*",
+ // Explicitly list the checks that should not occur in this module.
+ "abseil-*",
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "google-*",
+ "-google-readability-*",
+ "-google-runtime-references",
+ "misc-*",
+ "-misc-no-recursion",
+ "-misc-redundant-expression",
+ "-misc-unused-using-decls",
+ "performance*",
+ "portability*",
],
}
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index f2d223d..d964d25 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -133,6 +133,7 @@
public:
// unlocked objects
bool mRequestingSid = false;
+ bool mInheritRt = false;
sp<IBinder> mExtension;
int mPolicy = SCHED_NORMAL;
int mPriority = 0;
@@ -327,6 +328,27 @@
return e->mPriority;
}
+bool BBinder::isInheritRt() {
+ Extras* e = mExtras.load(std::memory_order_acquire);
+
+ return e && e->mInheritRt;
+}
+
+void BBinder::setInheritRt(bool inheritRt) {
+ Extras* e = mExtras.load(std::memory_order_acquire);
+
+ if (!e) {
+ if (!inheritRt) {
+ return;
+ }
+
+ e = getOrCreateExtras();
+ if (!e) return; // out of memory
+ }
+
+ e->mInheritRt = inheritRt;
+}
+
pid_t BBinder::getDebugPid() {
return getpid();
}
diff --git a/libs/binder/BufferedTextOutput.cpp b/libs/binder/BufferedTextOutput.cpp
index 8cf6097..88c85bf 100644
--- a/libs/binder/BufferedTextOutput.cpp
+++ b/libs/binder/BufferedTextOutput.cpp
@@ -18,7 +18,6 @@
#include <binder/Debug.h>
#include <cutils/atomic.h>
-#include <cutils/threads.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
#include <utils/Vector.h>
@@ -91,22 +90,6 @@
static pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;
-static thread_store_t tls;
-
-BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
-{
- ThreadState* ts = (ThreadState*) thread_store_get( &tls );
- if (ts) return ts;
- ts = new ThreadState;
- thread_store_set( &tls, ts, threadDestructor );
- return ts;
-}
-
-void BufferedTextOutput::threadDestructor(void *st)
-{
- delete ((ThreadState*)st);
-}
-
static volatile int32_t gSequence = 0;
static volatile int32_t gFreeBufferIndex = -1;
@@ -266,16 +249,14 @@
BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
{
if ((mFlags&MULTITHREADED) != 0) {
- ThreadState* ts = getThreadState();
- if (ts) {
- while (ts->states.size() <= (size_t)mIndex) ts->states.add(nullptr);
- BufferState* bs = ts->states[mIndex].get();
- if (bs != nullptr && bs->seq == mSeq) return bs;
-
- ts->states.editItemAt(mIndex) = new BufferState(mIndex);
- bs = ts->states[mIndex].get();
- if (bs != nullptr) return bs;
- }
+ thread_local ThreadState ts;
+ while (ts.states.size() <= (size_t)mIndex) ts.states.add(nullptr);
+ BufferState* bs = ts.states[mIndex].get();
+ if (bs != nullptr && bs->seq == mSeq) return bs;
+
+ ts.states.editItemAt(mIndex) = new BufferState(mIndex);
+ bs = ts.states[mIndex].get();
+ if (bs != nullptr) return bs;
}
return mGlobalState;
diff --git a/libs/binder/BufferedTextOutput.h b/libs/binder/BufferedTextOutput.h
index 1b27bb2..fdd532a 100644
--- a/libs/binder/BufferedTextOutput.h
+++ b/libs/binder/BufferedTextOutput.h
@@ -47,10 +47,7 @@
private:
struct BufferState;
struct ThreadState;
-
- static ThreadState*getThreadState();
- static void threadDestructor(void *st);
-
+
BufferState*getBuffer() const;
uint32_t mFlags;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index d4c7acf..7d01e0b 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1248,10 +1248,22 @@
constexpr uint32_t kForwardReplyFlags = TF_CLEAR_BUF;
sendReply(reply, (tr.flags & kForwardReplyFlags));
} else {
- if (error != OK || reply.dataSize() != 0) {
- alog << "oneway function results will be dropped but finished with status "
- << statusToString(error)
- << " and parcel size " << reply.dataSize() << endl;
+ if (error != OK) {
+ alog << "oneway function results for code " << tr.code
+ << " on binder at "
+ << reinterpret_cast<void*>(tr.target.ptr)
+ << " will be dropped but finished with status "
+ << statusToString(error);
+
+ // ideally we could log this even when error == OK, but it
+ // causes too much logspam because some manually-written
+ // interfaces have clients that call methods which always
+ // write results, sometimes as oneway methods.
+ if (reply.dataSize() != 0) {
+ alog << " and reply parcel size " << reply.dataSize();
+ }
+
+ alog << endl;
}
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
}
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 88a631a..440c98c 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;
@@ -222,6 +222,9 @@
if (local->isRequestingSid()) {
obj.flags |= FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
}
+ if (local->isInheritRt()) {
+ obj.flags |= FLAT_BINDER_FLAG_INHERIT_RT;
+ }
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local);
@@ -1474,6 +1477,31 @@
goto data_sorted;
}
+status_t Parcel::readVectorSizeWithCoarseBoundCheck(int32_t *size) const {
+ int32_t requestedSize;
+ const status_t status = readInt32(&requestedSize);
+ if (status != NO_ERROR) return status;
+
+ // We permit negative sizes, which indicate presence of a nullable vector,
+ // i.e. a vector embedded in std::optional, std::unique_ptr, or std::shared_ptr.
+ if (requestedSize > 0) {
+ // Check if there are fewer bytes than vector elements.
+ // A lower bound is 1 byte per element, satisfied by some enum and int8_t and uint8_t.
+ const size_t availableBytes = dataAvail();
+ if (static_cast<size_t>(requestedSize) > availableBytes) {
+ // We have a size that is greater than the number of bytes available.
+ // On bounds failure we do not 'rewind' position by 4 bytes of the size already read.
+ ALOGW("%s: rejecting out of bounds vector size (requestedSize):%d "
+ "Parcel{dataAvail:%zu mDataSize:%zu mDataPos:%zu mDataCapacity:%zu}",
+ __func__, requestedSize, availableBytes, mDataSize, mDataPos, mDataCapacity);
+ return BAD_VALUE;
+ }
+ }
+
+ *size = requestedSize;
+ return NO_ERROR;
+}
+
status_t Parcel::read(void* outData, size_t len) const
{
if (len > INT32_MAX) {
@@ -1696,7 +1724,7 @@
status_t Parcel::readBoolVector(std::optional<std::vector<bool>>* val) const {
const int32_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
@@ -1718,7 +1746,7 @@
status_t Parcel::readBoolVector(std::unique_ptr<std::vector<bool>>* val) const {
const int32_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
@@ -1739,7 +1767,7 @@
status_t Parcel::readBoolVector(std::vector<bool>* val) const {
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
if (status != OK) {
return status;
@@ -2055,8 +2083,11 @@
if (size >= 0 && size < INT32_MAX) {
*outLen = size;
const char* str = (const char*)readInplace(size+1);
- if (str != nullptr && str[size] == '\0') {
- return str;
+ if (str != nullptr) {
+ if (str[size] == '\0') {
+ return str;
+ }
+ android_errorWriteLog(0x534e4554, "172655291");
}
}
*outLen = 0;
@@ -2138,8 +2169,11 @@
if (size >= 0 && size < INT32_MAX) {
*outLen = size;
const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));
- if (str != nullptr && str[size] == u'\0') {
- return str;
+ if (str != nullptr) {
+ if (str[size] == u'\0') {
+ return str;
+ }
+ android_errorWriteLog(0x534e4554, "172655291");
}
}
*outLen = 0;
@@ -2500,19 +2534,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/ParcelableHolder.cpp b/libs/binder/ParcelableHolder.cpp
index b2b8671..2e86b74 100644
--- a/libs/binder/ParcelableHolder.cpp
+++ b/libs/binder/ParcelableHolder.cpp
@@ -37,7 +37,7 @@
size_t sizePos = p->dataPosition();
RETURN_ON_FAILURE(p->writeInt32(0));
size_t dataStartPos = p->dataPosition();
- RETURN_ON_FAILURE(p->writeUtf8AsUtf16(this->mParcelableName));
+ RETURN_ON_FAILURE(p->writeString16(this->mParcelableName));
this->mParcelable->writeToParcel(p);
size_t dataSize = p->dataPosition() - dataStartPos;
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/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 4375818..00a14f4 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -10,6 +10,9 @@
"name": "binderAllocationLimits"
},
{
+ "name": "binderClearBufTest"
+ },
+ {
"name": "binderDriverInterfaceTest"
},
{
@@ -38,6 +41,9 @@
"name": "aidl_lazy_test"
},
{
+ "name": "aidl_integration_test"
+ },
+ {
"name": "libbinderthreadstateutils_test"
},
{
diff --git a/libs/binder/include/binder/AppOpsManager.h b/libs/binder/include/binder/AppOpsManager.h
index 35c697e..f1085cf 100644
--- a/libs/binder/include/binder/AppOpsManager.h
+++ b/libs/binder/include/binder/AppOpsManager.h
@@ -136,7 +136,9 @@
OP_PHONE_CALL_MICROPHONE = 100,
OP_PHONE_CALL_CAMERA = 101,
OP_RECORD_AUDIO_HOTWORD = 102,
- _NUM_OP = 103
+ // Ops 103-105 are currently unused in native, and intentionally omitted
+ OP_RECORD_AUDIO_OUTPUT = 106,
+ _NUM_OP = 107
};
AppOpsManager();
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index d6da397..7079544 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -87,6 +87,11 @@
int getMinSchedulerPolicy();
int getMinSchedulerPriority();
+ // Whether realtime scheduling policies are inherited.
+ bool isInheritRt();
+ // This must be called before the object is sent to another process. Not thread safe.
+ void setInheritRt(bool inheritRt);
+
pid_t getDebugPid();
protected:
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/IInterface.h b/libs/binder/include/binder/IInterface.h
index 988508e..e2a0f87 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -232,7 +232,6 @@
"android.gui.IGraphicBufferConsumer",
"android.gui.IRegionSamplingListener",
"android.gui.ITransactionComposerListener",
- "android.gui.IScreenCaptureListener",
"android.gui.SensorEventConnection",
"android.gui.SensorServer",
"android.hardware.ICamera",
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..54c49e4 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();
@@ -520,6 +517,11 @@
void initState();
void scanForFds() const;
status_t validateReadData(size_t len) const;
+
+ // Reads an int32 size and does a coarse bounds check against the number
+ // of available bytes in the Parcel.
+ status_t readVectorSizeWithCoarseBoundCheck(int32_t *size) const;
+
void updateWorkSourceRequestHeaderPosition() const;
status_t finishFlattenBinder(const sp<IBinder>& binder);
@@ -790,6 +792,7 @@
template<typename T>
status_t Parcel::resizeOutVector(std::vector<T>* val) const {
int32_t size;
+ // used for allocating 'out' vector args, do not use readVectorSizeWithCoarseBoundCheck() here
status_t err = readInt32(&size);
if (err != NO_ERROR) {
return err;
@@ -805,6 +808,7 @@
template<typename T>
status_t Parcel::resizeOutVector(std::optional<std::vector<T>>* val) const {
int32_t size;
+ // used for allocating 'out' vector args, do not use readVectorSizeWithCoarseBoundCheck() here
status_t err = readInt32(&size);
if (err != NO_ERROR) {
return err;
@@ -821,6 +825,7 @@
template<typename T>
status_t Parcel::resizeOutVector(std::unique_ptr<std::vector<T>>* val) const {
int32_t size;
+ // used for allocating 'out' vector args, do not use readVectorSizeWithCoarseBoundCheck() here
status_t err = readInt32(&size);
if (err != NO_ERROR) {
return err;
@@ -837,7 +842,7 @@
template<typename T>
status_t Parcel::reserveOutVector(std::vector<T>* val, size_t* size) const {
int32_t read_size;
- status_t err = readInt32(&read_size);
+ status_t err = readVectorSizeWithCoarseBoundCheck(&read_size);
if (err != NO_ERROR) {
return err;
}
@@ -853,7 +858,7 @@
template<typename T>
status_t Parcel::reserveOutVector(std::optional<std::vector<T>>* val, size_t* size) const {
int32_t read_size;
- status_t err = readInt32(&read_size);
+ status_t err = readVectorSizeWithCoarseBoundCheck(&read_size);
if (err != NO_ERROR) {
return err;
}
@@ -873,7 +878,7 @@
status_t Parcel::reserveOutVector(std::unique_ptr<std::vector<T>>* val,
size_t* size) const {
int32_t read_size;
- status_t err = readInt32(&read_size);
+ status_t err = readVectorSizeWithCoarseBoundCheck(&read_size);
if (err != NO_ERROR) {
return err;
}
@@ -926,7 +931,7 @@
std::vector<T>* val,
status_t(Parcel::*read_func)(U*) const) const {
int32_t size;
- status_t status = this->readInt32(&size);
+ status_t status = this->readVectorSizeWithCoarseBoundCheck(&size);
if (status != OK) {
return status;
@@ -968,7 +973,7 @@
status_t(Parcel::*read_func)(T*) const) const {
const size_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
@@ -992,7 +997,7 @@
status_t(Parcel::*read_func)(T*) const) const {
const size_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
@@ -1096,7 +1101,7 @@
status_t Parcel::readParcelableVector(std::optional<std::vector<std::optional<T>>>* val) const {
const size_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
@@ -1120,7 +1125,7 @@
status_t Parcel::readParcelableVector(std::unique_ptr<std::vector<std::unique_ptr<T>>>* val) const {
const size_t start = dataPosition();
int32_t size;
- status_t status = readInt32(&size);
+ status_t status = readVectorSizeWithCoarseBoundCheck(&size);
val->reset();
if (status != OK || size < 0) {
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index ce5027e..9e4475c 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -18,6 +18,7 @@
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
+#include <utils/String16.h>
#include <mutex>
#include <optional>
#include <tuple>
@@ -52,85 +53,85 @@
}
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();
+ const String16& 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;
+ String8(*mParcelableName).c_str(), String8(parcelableDesc).c_str());
+ *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);
+ status_t status = this->mParcelPtr->readString16(&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:
mutable std::shared_ptr<Parcelable> mParcelable;
- mutable std::optional<std::string> mParcelableName;
+ mutable std::optional<String16> mParcelableName;
mutable std::unique_ptr<Parcel> mParcelPtr;
Stability mStability;
};
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..82f3882 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -104,7 +104,28 @@
"--header-filter=^.*frameworks/native/libs/binder/.*.h$",
],
tidy_checks_as_errors: [
- "*",
+ // Explicitly list the checks that should not occur in this module.
+ "abseil-*",
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-*",
+ "-clang-analyzer-core.CallAndMessage",
+ "-clang-analyzer-core.uninitialized.Assign",
+ "-clang-analyzer-unix.Malloc",
+ "-clang-analyzer-deadcode.DeadStores",
+ "-clang-analyzer-optin.cplusplus.UninitializedObject",
+ "google-*",
+ "-google-readability-*",
+ "-google-runtime-references",
+ "misc-*",
+ "-misc-no-recursion",
+ "-misc-non-private-member-variables-in-classes",
+ "-misc-redundant-expression",
+ "-misc-unused-parameters",
+ "-misc-unused-using-decls",
+ "performance*",
+ "portability*",
],
}
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 350c658..454fbd0 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -301,6 +301,26 @@
return binder.get();
}
+AIBinder_Weak* AIBinder_Weak_clone(const AIBinder_Weak* weak) {
+ if (weak == nullptr) {
+ return nullptr;
+ }
+
+ return new AIBinder_Weak{weak->binder};
+}
+
+bool AIBinder_lt(const AIBinder* lhs, const AIBinder* rhs) {
+ if (lhs == nullptr || rhs == nullptr) return lhs < rhs;
+
+ return const_cast<AIBinder*>(lhs)->getBinder() < const_cast<AIBinder*>(rhs)->getBinder();
+}
+
+bool AIBinder_Weak_lt(const AIBinder_Weak* lhs, const AIBinder_Weak* rhs) {
+ if (lhs == nullptr || rhs == nullptr) return lhs < rhs;
+
+ return lhs->binder < rhs->binder;
+}
+
AIBinder_Class::AIBinder_Class(const char* interfaceDescriptor, AIBinder_Class_onCreate onCreate,
AIBinder_Class_onDestroy onDestroy,
AIBinder_Class_onTransact onTransact)
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 2d85f90..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>
@@ -114,6 +115,13 @@
*/
AIBinder** getR() { return &mBinder; }
+ bool operator!=(const SpAIBinder& rhs) const { return get() != rhs.get(); }
+ bool operator<(const SpAIBinder& rhs) const { return get() < rhs.get(); }
+ bool operator<=(const SpAIBinder& rhs) const { return get() <= rhs.get(); }
+ bool operator==(const SpAIBinder& rhs) const { return get() == rhs.get(); }
+ bool operator>(const SpAIBinder& rhs) const { return get() > rhs.get(); }
+ bool operator>=(const SpAIBinder& rhs) const { return get() >= rhs.get(); }
+
private:
AIBinder* mBinder = nullptr;
};
@@ -123,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:
/**
@@ -191,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.
@@ -200,12 +208,19 @@
~ScopedAParcel() {}
ScopedAParcel(ScopedAParcel&&) = default;
ScopedAParcel& operator=(ScopedAParcel&&) = default;
+
+ bool operator!=(const ScopedAParcel& rhs) const { return get() != rhs.get(); }
+ bool operator<(const ScopedAParcel& rhs) const { return get() < rhs.get(); }
+ bool operator<=(const ScopedAParcel& rhs) const { return get() <= rhs.get(); }
+ bool operator==(const ScopedAParcel& rhs) const { return get() == rhs.get(); }
+ bool operator>(const ScopedAParcel& rhs) const { return get() > rhs.get(); }
+ bool operator>=(const ScopedAParcel& rhs) const { return get() >= rhs.get(); }
};
/**
* 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.
@@ -277,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:
/**
@@ -294,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.
@@ -310,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.
@@ -323,6 +350,13 @@
~ScopedFileDescriptor() {}
ScopedFileDescriptor(ScopedFileDescriptor&&) = default;
ScopedFileDescriptor& operator=(ScopedFileDescriptor&&) = default;
+
+ bool operator!=(const ScopedFileDescriptor& rhs) const { return get() != rhs.get(); }
+ bool operator<(const ScopedFileDescriptor& rhs) const { return get() < rhs.get(); }
+ bool operator<=(const ScopedFileDescriptor& rhs) const { return get() <= rhs.get(); }
+ bool operator==(const ScopedFileDescriptor& rhs) const { return get() == rhs.get(); }
+ bool operator>(const ScopedFileDescriptor& rhs) const { return get() > rhs.get(); }
+ bool operator>=(const ScopedFileDescriptor& rhs) const { return get() >= rhs.get(); }
};
} // namespace ndk
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index a4f4441..a1102e2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -83,7 +83,8 @@
template <class T, class... Args>
static std::shared_ptr<T> make(Args&&... args) {
T* t = new T(std::forward<Args>(args)...);
- return t->template ref<T>();
+ // warning: Potential leak of memory pointed to by 't' [clang-analyzer-unix.Malloc]
+ return t->template ref<T>(); // NOLINT(clang-analyzer-unix.Malloc)
}
static void operator delete(void* p) { std::free(p); }
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 4858514..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,38 +82,48 @@
}
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 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;
parcelable_stability_t mStability;
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 5e1ed46..0ca3a07 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -657,6 +657,68 @@
*/
const char* AIBinder_Class_getDescriptor(const AIBinder_Class* clazz) __INTRODUCED_IN(31);
+/**
+ * Whether AIBinder is less than another.
+ *
+ * This provides a per-process-unique total ordering of binders determined by
+ * an underlying allocation address where a null AIBinder* is considered to be
+ * ordered before all other binders.
+ *
+ * AIBinder* pointers themselves actually also create a per-process-unique total
+ * ordering. However, this ordering is inconsistent with AIBinder_Weak_lt for
+ * remote binders.
+ *
+ * Available since API level 31.
+ *
+ * \param lhs comparison object
+ * \param rhs comparison object
+ *
+ * \return whether "lhs < rhs" is true
+ */
+bool AIBinder_lt(const AIBinder* lhs, const AIBinder* rhs);
+
+/**
+ * Clone an AIBinder_Weak. Useful because even if a weak binder promotes to a
+ * null value, after further binder transactions, it may no longer promote to a
+ * null value.
+ *
+ * Available since API level 31.
+ *
+ * \param weak Object to clone
+ *
+ * \return clone of the input parameter. This must be deleted with
+ * AIBinder_Weak_delete. Null if weak input parameter is also null.
+ */
+AIBinder_Weak* AIBinder_Weak_clone(const AIBinder_Weak* weak);
+
+/**
+ * Whether AIBinder_Weak is less than another.
+ *
+ * This provides a per-process-unique total ordering of binders which is exactly
+ * the same as AIBinder_lt. Similarly, a null AIBinder_Weak* is considered to be
+ * ordered before all other weak references.
+ *
+ * If you have many AIBinder_Weak* objects which are all references to distinct
+ * binder objects which happen to have the same underlying address (as ordered
+ * by AIBinder_lt), these AIBinder_Weak* objects will retain the same order with
+ * respect to all other AIBinder_Weak* pointers with different underlying
+ * addresses and are also guaranteed to have a per-process-unique ordering. That
+ * is, even though multiple AIBinder* instances may happen to be allocated at
+ * the same underlying address, this function will still correctly distinguish
+ * that these are weak pointers to different binder objects.
+ *
+ * Unlike AIBinder*, the AIBinder_Weak* addresses themselves have nothing to do
+ * with the underlying binder.
+ *
+ * Available since API level 31.
+ *
+ * \param lhs comparison object
+ * \param rhs comparison object
+ *
+ * \return whether "lhs < rhs" is true
+ */
+bool AIBinder_Weak_lt(const AIBinder_Weak* lhs, const AIBinder_Weak* rhs);
+
#endif //__ANDROID_API__ >= 31
__END_DECLS
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index e233ffd..9a93bf3 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -122,6 +122,9 @@
AServiceManager_waitForService; # apex llndk
AIBinder_Class_getDescriptor;
+ AIBinder_Weak_clone;
+ AIBinder_Weak_lt;
+ AIBinder_lt;
AParcel_appendFrom;
AParcel_create;
AParcel_getDataSize;
diff --git a/libs/binder/parcel_fuzzer/main.cpp b/libs/binder/parcel_fuzzer/main.cpp
index 386c70b..78606cc 100644
--- a/libs/binder/parcel_fuzzer/main.cpp
+++ b/libs/binder/parcel_fuzzer/main.cpp
@@ -20,12 +20,16 @@
#include "hwbinder.h"
#include "util.h"
+#include <iostream>
+
#include <android-base/logging.h>
#include <fuzzbinder/random_parcel.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <cstdlib>
#include <ctime>
+#include <sys/resource.h>
+#include <sys/time.h>
using android::fillRandomParcel;
@@ -77,7 +81,25 @@
}
}
+size_t getHardMemoryLimit() {
+ struct rlimit limit;
+ CHECK(0 == getrlimit(RLIMIT_AS, &limit)) << errno;
+ return limit.rlim_max;
+}
+
+void setMemoryLimit(size_t cur, size_t max) {
+ const struct rlimit kLimit = {
+ .rlim_cur = cur,
+ .rlim_max = max,
+ };
+ CHECK(0 == setrlimit(RLIMIT_AS, &kLimit)) << errno;
+}
+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static constexpr size_t kMemLimit = 1 * 1024 * 1024;
+ size_t hardLimit = getHardMemoryLimit();
+ setMemoryLimit(std::min(kMemLimit, hardLimit), hardLimit);
+
if (size <= 1) return 0; // no use
// avoid timeouts, see b/142617274, b/142473153
@@ -102,5 +124,7 @@
provider.PickValueInArray(fuzzBackend)(std::move(provider));
+ setMemoryLimit(hardLimit, hardLimit);
+
return 0;
}
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index b558f27..ed3b9ec 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -592,7 +592,9 @@
// pointer.
return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
}
- } else if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
+ }
+
+ if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
let service: $crate::Result<$crate::Binder<$native>> =
std::convert::TryFrom::try_from(ibinder.clone());
if let Ok(service) = service {
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/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 497361a..bb8c492 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -482,4 +482,17 @@
let _interface: Box<dyn ITestSameDescriptor> = FromIBinder::try_from(service.as_binder())
.expect("Could not re-interpret service as the ITestSameDescriptor interface");
}
+
+ /// Test that we can round-trip a rust service through a generic IBinder
+ #[test]
+ fn reassociate_rust_binder() {
+ let service_name = "testing_service";
+ let service_ibinder = BnTest::new_binder(TestService { s: service_name.to_string() })
+ .as_binder();
+
+ let service: Box<dyn ITest> = service_ibinder.into_interface()
+ .expect("Could not reassociate the generic ibinder");
+
+ assert_eq!(service.test().unwrap(), service_name);
+ }
}
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 87f1d45..988f7f3 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -157,6 +157,24 @@
require_root: true,
}
+cc_test {
+ name: "binderClearBufTest",
+ defaults: ["binder_test_defaults"],
+ srcs: [
+ "binderClearBufTest.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "liblog",
+ "libutils",
+ ],
+
+ test_suites: ["general-tests"],
+ require_root: true,
+}
+
aidl_interface {
name: "binderStabilityTestIface",
unstable: true,
diff --git a/libs/binder/tests/binderClearBufTest.cpp b/libs/binder/tests/binderClearBufTest.cpp
new file mode 100644
index 0000000..a565e72
--- /dev/null
+++ b/libs/binder/tests/binderClearBufTest.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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/logging.h>
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <binder/Stability.h>
+#include <gtest/gtest.h>
+
+#include <sys/prctl.h>
+#include <thread>
+
+using namespace android;
+
+const String16 kServerName = String16("binderClearBuf");
+
+std::string hexString(const void* bytes, size_t len) {
+ if (bytes == nullptr) return "<null>";
+
+ const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
+ char chars[] = "0123456789abcdef";
+ std::string result;
+ result.resize(len * 2);
+
+ for (size_t i = 0; i < len; i++) {
+ result[2 * i] = chars[bytes8[i] >> 4];
+ result[2 * i + 1] = chars[bytes8[i] & 0xf];
+ }
+
+ return result;
+}
+
+class FooBar : public BBinder {
+ public:
+ enum {
+ TRANSACTION_REPEAT_STRING = IBinder::FIRST_CALL_TRANSACTION,
+ };
+
+ std::mutex foo;
+ std::string last;
+
+ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ // not checking data, since there is no hook at the time this test is
+ // written to check values there are set to zero. Instead, we only check
+ // the reply parcel.
+
+ switch (code) {
+ case TRANSACTION_REPEAT_STRING: {
+ const char* str = data.readCString();
+ return reply->writeCString(str == nullptr ? "<null>" : str);
+ }
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+ static std::string RepeatString(const sp<IBinder> binder,
+ const std::string& repeat,
+ std::string* outBuffer) {
+ Parcel data;
+ data.writeCString(repeat.c_str());
+ std::string result;
+ const uint8_t* lastReply;
+ size_t lastReplySize;
+ {
+ Parcel reply;
+ binder->transact(TRANSACTION_REPEAT_STRING, data, &reply, FLAG_CLEAR_BUF);
+ result = reply.readCString();
+ lastReply = reply.data();
+ lastReplySize = reply.dataSize();
+ }
+ IPCThreadState::self()->flushCommands();
+ *outBuffer = hexString(lastReply, lastReplySize);
+ return result;
+ }
+};
+
+TEST(BinderClearBuf, ClearKernelBuffer) {
+ sp<IBinder> binder = defaultServiceManager()->getService(kServerName);
+ ASSERT_NE(nullptr, binder);
+
+ std::string replyBuffer;
+ std::string result = FooBar::RepeatString(binder, "foo", &replyBuffer);
+ EXPECT_EQ("foo", result);
+
+ // the buffer must have at least some length for the string, but we will
+ // just check it has some length, to avoid assuming anything about the
+ // format
+ EXPECT_GT(replyBuffer.size(), 0);
+
+ for (size_t i = 0; i < replyBuffer.size(); i++) {
+ EXPECT_EQ(replyBuffer[i], '0') << "reply buffer at " << i;
+ }
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+
+ sp<IBinder> server = new FooBar;
+ android::defaultServiceManager()->addService(kServerName, server);
+
+ IPCThreadState::self()->joinThreadPool(true);
+ exit(1); // should not reach
+ }
+
+ // This is not racey. Just giving these services some time to register before we call
+ // getService which sleeps for much longer. One alternative would be to
+ // start a threadpool + use waitForService, but we want to have as few
+ // binder things going on in this test as possible, since we are checking
+ // memory is zero'd which the kernel has a right to change.
+ usleep(100000);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index ad4729d..a5261e5 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -30,6 +30,7 @@
#include <binder/IServiceManager.h>
#include <private/binder/binder_module.h>
+#include <linux/sched.h>
#include <sys/epoll.h>
#include <sys/prctl.h>
@@ -53,6 +54,7 @@
static constexpr int kSchedPolicy = SCHED_RR;
static constexpr int kSchedPriority = 7;
+static constexpr int kSchedPriorityMore = 8;
static String16 binderLibTestServiceName = String16("test.binderLib");
@@ -402,6 +404,13 @@
EXPECT_EQ(NO_ERROR, ret);
}
+TEST_F(BinderLibTest, NopTransactionOneway) {
+ status_t ret;
+ Parcel data, reply;
+ ret = m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply, TF_ONE_WAY);
+ EXPECT_EQ(NO_ERROR, ret);
+}
+
TEST_F(BinderLibTest, NopTransactionClear) {
status_t ret;
Parcel data, reply;
@@ -1088,6 +1097,25 @@
EXPECT_EQ(kSchedPriority, priority);
}
+TEST_F(BinderLibTest, InheritRt) {
+ sp<IBinder> server = addServer();
+ ASSERT_TRUE(server != nullptr);
+
+ const struct sched_param param {
+ .sched_priority = kSchedPriorityMore,
+ };
+ EXPECT_EQ(0, sched_setscheduler(getpid(), SCHED_RR, ¶m));
+
+ Parcel data, reply;
+ status_t ret = server->transact(BINDER_LIB_TEST_GET_SCHEDULING_POLICY, data, &reply);
+ EXPECT_EQ(NO_ERROR, ret);
+
+ int policy = reply.readInt32();
+ int priority = reply.readInt32();
+
+ EXPECT_EQ(kSchedPolicy, policy & (~SCHED_RESET_ON_FORK));
+ EXPECT_EQ(kSchedPriorityMore, priority);
+}
TEST_F(BinderLibTest, VectorSent) {
Parcel data, reply;
@@ -1161,9 +1189,6 @@
virtual status_t onTransact(uint32_t code,
const Parcel& data, Parcel* reply,
uint32_t flags = 0) {
- //printf("%s: code %d\n", __func__, code);
- (void)flags;
-
if (getuid() != (uid_t)IPCThreadState::self()->getCallingUid()) {
return PERMISSION_DENIED;
}
@@ -1237,8 +1262,12 @@
return NO_ERROR;
case BINDER_LIB_TEST_NOP_TRANSACTION_WAIT:
usleep(5000);
- return NO_ERROR;
+ [[fallthrough]];
case BINDER_LIB_TEST_NOP_TRANSACTION:
+ // oneway error codes should be ignored
+ if (flags & TF_ONE_WAY) {
+ return UNKNOWN_ERROR;
+ }
return NO_ERROR;
case BINDER_LIB_TEST_DELAYED_CALL_BACK: {
// Note: this transaction is only designed for use with a
@@ -1460,6 +1489,8 @@
testService->setMinSchedulerPolicy(kSchedPolicy, kSchedPriority);
+ testService->setInheritRt(true);
+
/*
* Normally would also contain functionality as well, but we are only
* testing the extension mechanism.
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 2f9d85e..ffb3ef2 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -36,12 +36,15 @@
#include <optional>
#include <sys/eventfd.h>
+#include <sys/prctl.h>
using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
namespace android {
namespace tests {
+static const String16 kServiceName("SafeInterfaceTest");
+
enum class TestEnum : uint32_t {
INVALID = 0,
INITIAL = 1,
@@ -601,40 +604,13 @@
static constexpr const char* getLogTag() { return "SafeInterfaceTest"; }
sp<ISafeInterfaceTest> getRemoteService() {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wexit-time-destructors"
- static std::mutex sMutex;
- static sp<ISafeInterfaceTest> sService;
- static sp<IBinder> sDeathToken = new BBinder;
-#pragma clang diagnostic pop
+ sp<IBinder> binder = defaultServiceManager()->getService(kServiceName);
+ sp<ISafeInterfaceTest> iface = interface_cast<ISafeInterfaceTest>(binder);
+ EXPECT_TRUE(iface != nullptr);
- std::unique_lock<decltype(sMutex)> lock;
- if (sService == nullptr) {
- ALOG(LOG_INFO, getLogTag(), "Forking remote process");
- pid_t forkPid = fork();
- EXPECT_NE(forkPid, -1);
+ iface->setDeathToken(new BBinder);
- const String16 serviceName("SafeInterfaceTest");
-
- if (forkPid == 0) {
- ALOG(LOG_INFO, getLogTag(), "Remote process checking in");
- sp<ISafeInterfaceTest> nativeService = new BnSafeInterfaceTest;
- defaultServiceManager()->addService(serviceName,
- IInterface::asBinder(nativeService));
- ProcessState::self()->startThreadPool();
- IPCThreadState::self()->joinThreadPool();
- // We shouldn't get to this point
- [&]() { FAIL(); }();
- }
-
- sp<IBinder> binder = defaultServiceManager()->getService(serviceName);
- sService = interface_cast<ISafeInterfaceTest>(binder);
- EXPECT_TRUE(sService != nullptr);
-
- sService->setDeathToken(sDeathToken);
- }
-
- return sService;
+ return iface;
}
};
@@ -840,5 +816,23 @@
ASSERT_EQ(b + 1, bPlusOne);
}
+extern "C" int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ sp<BnSafeInterfaceTest> nativeService = new BnSafeInterfaceTest;
+ status_t status = defaultServiceManager()->addService(kServiceName, nativeService);
+ if (status != OK) {
+ ALOG(LOG_INFO, "SafeInterfaceServer", "could not register");
+ return EXIT_FAILURE;
+ }
+ IPCThreadState::self()->joinThreadPool();
+ return EXIT_FAILURE;
+ }
+
+ return RUN_ALL_TESTS();
+}
+
} // namespace tests
} // namespace android
diff --git a/libs/binder/tests/fuzzers/Android.bp b/libs/binder/tests/fuzzers/Android.bp
index c465bed..5531296 100644
--- a/libs/binder/tests/fuzzers/Android.bp
+++ b/libs/binder/tests/fuzzers/Android.bp
@@ -69,3 +69,18 @@
defaults: ["binder_fuzz_defaults"],
srcs: ["TextOutputFuzz.cpp"],
}
+
+cc_fuzz {
+ name: "binder_bufferedTextOutputFuzz",
+ include_dirs: [
+ "frameworks/native/libs/binder",
+ ],
+ defaults: ["binder_fuzz_defaults"],
+ srcs: ["BufferedTextOutputFuzz.cpp"],
+}
+
+cc_fuzz {
+ name: "binder_memoryDealerFuzz",
+ defaults: ["binder_fuzz_defaults"],
+ srcs: ["MemoryDealerFuzz.cpp"],
+}
diff --git a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
index 9ac65bb..69f1b9d 100644
--- a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
@@ -37,8 +37,8 @@
bbinder->isRequestingSid();
},
[](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
- bool request_sid = fdp->ConsumeBool();
- bbinder->setRequestingSid(request_sid);
+ bool requestSid = fdp->ConsumeBool();
+ bbinder->setRequestingSid(requestSid);
},
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getExtension();
@@ -63,6 +63,13 @@
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getMinSchedulerPriority();
},
+ [](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
+ bool inheritRt = fdp->ConsumeBool();
+ bbinder->setInheritRt(inheritRt);
+ },
+ [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
+ bbinder->isInheritRt();
+ },
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getDebugPid();
}};
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/binder/tests/fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/fuzzers/BufferedTextOutputFuzz.cpp
new file mode 100644
index 0000000..09cb216
--- /dev/null
+++ b/libs/binder/tests/fuzzers/BufferedTextOutputFuzz.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <commonFuzzHelpers.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <string>
+#include <vector>
+#include "BufferedTextOutput.h"
+
+namespace android {
+
+class FuzzBufferedTextOutput : public BufferedTextOutput {
+public:
+ FuzzBufferedTextOutput(uint32_t flags) : BufferedTextOutput(flags) {}
+ virtual status_t writeLines(const struct iovec& buf, size_t) {
+ size_t len = buf.iov_len;
+ void* tmp_buf = malloc(len);
+
+ if (tmp_buf == NULL) {
+ return status_t();
+ }
+
+ // This will attempt to read data from iov_base to ensure valid params were passed.
+ memcpy(tmp_buf, buf.iov_base, len);
+ free(tmp_buf);
+ return status_t();
+ }
+};
+
+// Fuzzer entry point.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp(data, size);
+ uint32_t flags = fdp.ConsumeIntegral<uint32_t>();
+ size_t push_count = 0;
+ std::shared_ptr<BufferedTextOutput> bTextOutput(new FuzzBufferedTextOutput(flags));
+
+ while (fdp.remaining_bytes() > 0) {
+ fdp.PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ bTextOutput->pushBundle();
+ push_count++;
+ },
+ [&]() -> void {
+ std::string txt = fdp.ConsumeRandomLengthString(fdp.remaining_bytes());
+ size_t len = fdp.ConsumeIntegralInRange<size_t>(0, txt.length());
+ bTextOutput->print(txt.c_str(), len);
+ },
+ [&]() -> void {
+ if (push_count == 0) return;
+
+ bTextOutput->popBundle();
+ push_count--;
+ },
+ })();
+ }
+
+ return 0;
+}
+} // namespace android
diff --git a/libs/binder/tests/fuzzers/MemoryDealerFuzz.cpp b/libs/binder/tests/fuzzers/MemoryDealerFuzz.cpp
new file mode 100644
index 0000000..f9dda8c
--- /dev/null
+++ b/libs/binder/tests/fuzzers/MemoryDealerFuzz.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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/MemoryDealer.h>
+#include <commonFuzzHelpers.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <string>
+#include <unordered_set>
+
+namespace android {
+
+static constexpr size_t kMaxBufferSize = 10000;
+static constexpr size_t kMaxDealerSize = 1024 * 512;
+static constexpr size_t kMaxAllocSize = 1024;
+
+// Fuzzer entry point.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size > kMaxBufferSize) {
+ return 0;
+ }
+
+ FuzzedDataProvider fdp(data, size);
+ size_t dSize = fdp.ConsumeIntegralInRange<size_t>(0, kMaxDealerSize);
+ std::string name = fdp.ConsumeRandomLengthString(fdp.remaining_bytes());
+ uint32_t flags = fdp.ConsumeIntegral<uint32_t>();
+ sp<MemoryDealer> dealer = new MemoryDealer(dSize, name.c_str(), flags);
+
+ // This is used to track offsets that have been freed already to avoid an expected fatal log.
+ std::unordered_set<size_t> free_list;
+
+ while (fdp.remaining_bytes() > 0) {
+ fdp.PickValueInArray<std::function<void()>>({
+ [&]() -> void { dealer->getAllocationAlignment(); },
+ [&]() -> void { dealer->getMemoryHeap(); },
+ [&]() -> void {
+ size_t offset = fdp.ConsumeIntegral<size_t>();
+
+ // Offset has already been freed, so return instead.
+ if (free_list.find(offset) != free_list.end()) return;
+
+ dealer->deallocate(offset);
+ free_list.insert(offset);
+ },
+ [&]() -> void {
+ std::string randString = fdp.ConsumeRandomLengthString(fdp.remaining_bytes());
+ dealer->dump(randString.c_str());
+ },
+ [&]() -> void {
+ size_t allocSize = fdp.ConsumeIntegralInRange<size_t>(0, kMaxAllocSize);
+ sp<IMemory> allocated = dealer->allocate(allocSize);
+ // If the allocation was successful, try to write to it
+ if (allocated != nullptr && allocated->unsecurePointer() != nullptr) {
+ memset(allocated->unsecurePointer(), 1, allocated->size());
+
+ // Clear the address from freelist since it has been allocated over again.
+ free_list.erase(allocated->offset());
+ }
+ },
+ })();
+ }
+
+ return 0;
+}
+} // namespace android
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/cputimeinstate/Android.bp b/libs/cputimeinstate/Android.bp
index b1943a4..e3cd085 100644
--- a/libs/cputimeinstate/Android.bp
+++ b/libs/cputimeinstate/Android.bp
@@ -33,5 +33,6 @@
"-Wall",
"-Wextra",
],
+ require_root: true,
}
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 6058430..2e72cc4 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -56,9 +56,11 @@
static std::vector<std::vector<uint32_t>> gPolicyFreqs;
static std::vector<std::vector<uint32_t>> gPolicyCpus;
static std::set<uint32_t> gAllFreqs;
+static unique_fd gTisTotalMapFd;
static unique_fd gTisMapFd;
static unique_fd gConcurrentMapFd;
static unique_fd gUidLastUpdateMapFd;
+static unique_fd gPidTisMapFd;
static std::optional<std::vector<uint32_t>> readNumbersFromFile(const std::string &path) {
std::string data;
@@ -128,6 +130,10 @@
gPolicyCpus.emplace_back(*cpus);
}
+ gTisTotalMapFd =
+ unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_total_time_in_state_map")};
+ if (gTisTotalMapFd < 0) return false;
+
gTisMapFd = unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_time_in_state_map")};
if (gTisMapFd < 0) return false;
@@ -139,6 +145,12 @@
unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_last_update_map")};
if (gUidLastUpdateMapFd < 0) return false;
+ gPidTisMapFd = unique_fd{mapRetrieveRO(BPF_FS_PATH "map_time_in_state_pid_time_in_state_map")};
+ if (gPidTisMapFd < 0) return false;
+
+ unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+ if (trackedPidMapFd < 0) return false;
+
gInitialized = true;
return true;
}
@@ -222,7 +234,8 @@
}
gTracking = attachTracepointProgram("sched", "sched_switch") &&
- attachTracepointProgram("power", "cpu_frequency");
+ attachTracepointProgram("power", "cpu_frequency") &&
+ attachTracepointProgram("sched", "sched_process_free");
return gTracking;
}
@@ -231,6 +244,31 @@
return gPolicyFreqs;
}
+std::optional<std::vector<std::vector<uint64_t>>> getTotalCpuFreqTimes() {
+ if (!gInitialized && !initGlobals()) return {};
+
+ std::vector<std::vector<uint64_t>> out;
+ uint32_t maxFreqCount = 0;
+ for (const auto &freqList : gPolicyFreqs) {
+ if (freqList.size() > maxFreqCount) maxFreqCount = freqList.size();
+ out.emplace_back(freqList.size(), 0);
+ }
+
+ std::vector<uint64_t> vals(gNCpus);
+ const uint32_t freqCount = maxFreqCount <= MAX_FREQS_FOR_TOTAL ? maxFreqCount :
+ MAX_FREQS_FOR_TOTAL;
+ for (uint32_t freqIdx = 0; freqIdx < freqCount; ++freqIdx) {
+ if (findMapEntry(gTisTotalMapFd, &freqIdx, vals.data())) return {};
+ for (uint32_t policyIdx = 0; policyIdx < gNPolicies; ++policyIdx) {
+ if (freqIdx >= gPolicyFreqs[policyIdx].size()) continue;
+ for (const auto &cpu : gPolicyCpus[policyIdx]) {
+ out[policyIdx][freqIdx] += vals[cpu];
+ }
+ }
+ }
+
+ return out;
+}
// Retrieve the times in ns that uid spent running at each CPU frequency.
// Return contains no value on error, otherwise it contains a vector of vectors using the format:
// [[t0_0, t0_1, ...],
@@ -502,5 +540,106 @@
return true;
}
+bool startTrackingProcessCpuTimes(pid_t pid) {
+ if (!gInitialized && !initGlobals()) return false;
+
+ unique_fd trackedPidHashMapFd(
+ mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_hash_map"));
+ if (trackedPidHashMapFd < 0) return false;
+
+ unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+ if (trackedPidMapFd < 0) return false;
+
+ for (uint32_t index = 0; index < MAX_TRACKED_PIDS; index++) {
+ // Find first available [index, pid] entry in the pid_tracked_hash_map map
+ if (writeToMapEntry(trackedPidHashMapFd, &index, &pid, BPF_NOEXIST) != 0) {
+ if (errno != EEXIST) {
+ return false;
+ }
+ continue; // This index is already taken
+ }
+
+ tracked_pid_t tracked_pid = {.pid = pid, .state = TRACKED_PID_STATE_ACTIVE};
+ if (writeToMapEntry(trackedPidMapFd, &index, &tracked_pid, BPF_ANY) != 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+// Marks the specified task identified by its PID (aka TID) for CPU time-in-state tracking
+// aggregated with other tasks sharing the same TGID and aggregation key.
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+ if (!gInitialized && !initGlobals()) return false;
+
+ unique_fd taskAggregationMapFd(
+ mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_task_aggregation_map"));
+ if (taskAggregationMapFd < 0) return false;
+
+ return writeToMapEntry(taskAggregationMapFd, &pid, &aggregationKey, BPF_ANY) == 0;
+}
+
+// Retrieves the times in ns that each thread spent running at each CPU freq, aggregated by
+// aggregation key.
+// Return contains no value on error, otherwise it contains a map from aggregation keys
+// to vectors of vectors using the format:
+// { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
+// aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
+// where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest freq.
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys) {
+ if (!gInitialized && !initGlobals()) return {};
+
+ uint32_t maxFreqCount = 0;
+ std::vector<std::vector<uint64_t>> mapFormat;
+ for (const auto &freqList : gPolicyFreqs) {
+ if (freqList.size() > maxFreqCount) maxFreqCount = freqList.size();
+ mapFormat.emplace_back(freqList.size(), 0);
+ }
+
+ bool dataCollected = false;
+ std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
+ std::vector<tis_val_t> vals(gNCpus);
+ for (uint16_t aggregationKey : aggregationKeys) {
+ map.emplace(aggregationKey, mapFormat);
+
+ aggregated_task_tis_key_t key{.tgid = tgid, .aggregation_key = aggregationKey};
+ for (key.bucket = 0; key.bucket <= (maxFreqCount - 1) / FREQS_PER_ENTRY; ++key.bucket) {
+ if (findMapEntry(gPidTisMapFd, &key, vals.data()) != 0) {
+ if (errno != ENOENT) {
+ return {};
+ }
+ continue;
+ } else {
+ dataCollected = true;
+ }
+
+ // Combine data by aggregating time-in-state data grouped by CPU cluster aka policy.
+ uint32_t offset = key.bucket * FREQS_PER_ENTRY;
+ uint32_t nextOffset = offset + FREQS_PER_ENTRY;
+ for (uint32_t j = 0; j < gNPolicies; ++j) {
+ if (offset >= gPolicyFreqs[j].size()) continue;
+ auto begin = map[key.aggregation_key][j].begin() + offset;
+ auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY
+ : map[key.aggregation_key][j].end();
+ for (const auto &cpu : gPolicyCpus[j]) {
+ std::transform(begin, end, std::begin(vals[cpu].ar), begin,
+ std::plus<uint64_t>());
+ }
+ }
+ }
+ }
+
+ if (!dataCollected) {
+ // Check if eBPF is supported on this device. If it is, gTisMap should not be empty.
+ time_key_t key;
+ if (getFirstMapKey(gTisMapFd, &key) != 0) {
+ return {};
+ }
+ }
+ return map;
+}
+
} // namespace bpf
} // namespace android
diff --git a/libs/cputimeinstate/cputimeinstate.h b/libs/cputimeinstate/cputimeinstate.h
index b7600f5..46de669 100644
--- a/libs/cputimeinstate/cputimeinstate.h
+++ b/libs/cputimeinstate/cputimeinstate.h
@@ -23,6 +23,7 @@
namespace bpf {
bool startTrackingUidTimes();
+std::optional<std::vector<std::vector<uint64_t>>> getTotalCpuFreqTimes();
std::optional<std::vector<std::vector<uint64_t>>> getUidCpuFreqTimes(uint32_t uid);
std::optional<std::unordered_map<uint32_t, std::vector<std::vector<uint64_t>>>>
getUidsCpuFreqTimes();
@@ -41,5 +42,10 @@
getUidsUpdatedConcurrentTimes(uint64_t *lastUpdate);
bool clearUidTimes(unsigned int uid);
+bool startTrackingProcessCpuTimes(pid_t pid);
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys);
+
} // namespace bpf
} // namespace android
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 0d5f412..d25b2e9 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -19,6 +19,8 @@
#include <sys/sysinfo.h>
+#include <pthread.h>
+#include <semaphore.h>
#include <numeric>
#include <unordered_map>
#include <vector>
@@ -38,6 +40,12 @@
using std::vector;
+TEST(TimeInStateTest, TotalTimeInState) {
+ auto times = getTotalCpuFreqTimes();
+ ASSERT_TRUE(times.has_value());
+ EXPECT_FALSE(times->empty());
+}
+
TEST(TimeInStateTest, SingleUidTimeInState) {
auto times = getUidCpuFreqTimes(0);
ASSERT_TRUE(times.has_value());
@@ -184,6 +192,31 @@
}
}
+TEST(TimeInStateTest, TotalAndAllUidTimeInStateConsistent) {
+ auto allUid = getUidsCpuFreqTimes();
+ auto total = getTotalCpuFreqTimes();
+
+ ASSERT_TRUE(allUid.has_value() && total.has_value());
+
+ // Check the number of policies.
+ ASSERT_EQ(allUid->at(0).size(), total->size());
+
+ for (uint32_t policyIdx = 0; policyIdx < total->size(); ++policyIdx) {
+ std::vector<uint64_t> totalTimes = total->at(policyIdx);
+ uint32_t totalFreqsCount = totalTimes.size();
+ std::vector<uint64_t> allUidTimes(totalFreqsCount, 0);
+ for (auto const &[uid, uidTimes]: *allUid) {
+ for (uint32_t freqIdx = 0; freqIdx < uidTimes[policyIdx].size(); ++freqIdx) {
+ allUidTimes[std::min(freqIdx, totalFreqsCount - 1)] += uidTimes[policyIdx][freqIdx];
+ }
+ }
+
+ for (uint32_t freqIdx = 0; freqIdx < totalFreqsCount; ++freqIdx) {
+ ASSERT_LE(allUidTimes[freqIdx], totalTimes[freqIdx]);
+ }
+ }
+}
+
TEST(TimeInStateTest, SingleAndAllUidTimeInStateConsistent) {
uint64_t zero = 0;
auto maps = {getUidsCpuFreqTimes(), getUidsUpdatedCpuFreqTimes(&zero)};
@@ -290,6 +323,22 @@
ASSERT_LE(after - before, NSEC_PER_SEC * 2 * get_nprocs_conf());
}
+TEST(TimeInStateTest, TotalTimeInStateMonotonic) {
+ auto before = getTotalCpuFreqTimes();
+ ASSERT_TRUE(before.has_value());
+ sleep(1);
+ auto after = getTotalCpuFreqTimes();
+ ASSERT_TRUE(after.has_value());
+
+ for (uint32_t policyIdx = 0; policyIdx < after->size(); ++policyIdx) {
+ auto timesBefore = before->at(policyIdx);
+ auto timesAfter = after->at(policyIdx);
+ for (uint32_t freqIdx = 0; freqIdx < timesAfter.size(); ++freqIdx) {
+ ASSERT_NO_FATAL_FAILURE(TestCheckDelta(timesBefore[freqIdx], timesAfter[freqIdx]));
+ }
+ }
+}
+
TEST(TimeInStateTest, AllUidTimeInStateMonotonic) {
auto map1 = getUidsCpuFreqTimes();
ASSERT_TRUE(map1.has_value());
@@ -504,5 +553,85 @@
for (size_t i = 0; i < freqs->size(); ++i) EXPECT_EQ((*freqs)[i].size(), (*times)[i].size());
}
+uint64_t timeNanos() {
+ struct timespec spec;
+ clock_gettime(CLOCK_MONOTONIC, &spec);
+ return spec.tv_sec * 1000000000 + spec.tv_nsec;
+}
+
+// Keeps CPU busy with some number crunching
+void useCpu() {
+ long sum = 0;
+ for (int i = 0; i < 100000; i++) {
+ sum *= i;
+ }
+}
+
+sem_t pingsem, pongsem;
+
+void *testThread(void *) {
+ for (int i = 0; i < 10; i++) {
+ sem_wait(&pingsem);
+ useCpu();
+ sem_post(&pongsem);
+ }
+ return nullptr;
+}
+
+TEST(TimeInStateTest, GetAggregatedTaskCpuFreqTimes) {
+ uint64_t startTimeNs = timeNanos();
+
+ sem_init(&pingsem, 0, 1);
+ sem_init(&pongsem, 0, 0);
+
+ pthread_t thread;
+ ASSERT_EQ(pthread_create(&thread, NULL, &testThread, NULL), 0);
+
+ // This process may have been running for some time, so when we start tracking
+ // CPU time, the very first switch may include the accumulated time.
+ // Yield the remainder of this timeslice to the newly created thread.
+ sem_wait(&pongsem);
+ sem_post(&pingsem);
+
+ pid_t tgid = getpid();
+ startTrackingProcessCpuTimes(tgid);
+
+ pid_t tid = pthread_gettid_np(thread);
+ startAggregatingTaskCpuTimes(tid, 42);
+
+ // Play ping-pong with the other thread to ensure that both threads get
+ // some CPU time.
+ for (int i = 0; i < 9; i++) {
+ sem_wait(&pongsem);
+ useCpu();
+ sem_post(&pingsem);
+ }
+
+ pthread_join(thread, NULL);
+
+ std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> optionalMap =
+ getAggregatedTaskCpuFreqTimes(tgid, {0, 42});
+ ASSERT_TRUE(optionalMap);
+
+ std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map = *optionalMap;
+ ASSERT_EQ(map.size(), 2u);
+
+ uint64_t testDurationNs = timeNanos() - startTimeNs;
+ for (auto pair : map) {
+ uint16_t aggregationKey = pair.first;
+ ASSERT_TRUE(aggregationKey == 0 || aggregationKey == 42);
+
+ std::vector<std::vector<uint64_t>> timesInState = pair.second;
+ uint64_t totalCpuTime = 0;
+ for (size_t i = 0; i < timesInState.size(); i++) {
+ for (size_t j = 0; j < timesInState[i].size(); j++) {
+ totalCpuTime += timesInState[i][j];
+ }
+ }
+ ASSERT_GT(totalCpuTime, 0ul);
+ ASSERT_LE(totalCpuTime, testDurationNs);
+ }
+}
+
} // namespace bpf
} // 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/gralloc/OWNERS b/libs/gralloc/OWNERS
index 4a95778..93879d8 100644
--- a/libs/gralloc/OWNERS
+++ b/libs/gralloc/OWNERS
@@ -1,2 +1 @@
chrisforbes@google.com
-vhau@google.com
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
index 1a7c2c9..2f418ac 100644
--- a/libs/gralloc/types/include/gralloctypes/Gralloc4.h
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -32,202 +32,6 @@
#include <android/hardware/graphics/mapper/4.0/IMapper.h>
namespace android {
-
-/**
- * Define equality operators for Stable AIDL types.
- */
-inline bool operator==(const aidl::android::hardware::graphics::common::ExtendableType& lhs,
- const aidl::android::hardware::graphics::common::ExtendableType& rhs) {
- return !std::strcmp(lhs.name.c_str(), rhs.name.c_str()) && lhs.value == rhs.value;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::ExtendableType& lhs,
- const aidl::android::hardware::graphics::common::ExtendableType& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::PlaneLayoutComponent& lhs,
- const aidl::android::hardware::graphics::common::PlaneLayoutComponent& rhs) {
- if (lhs.type.name != rhs.type.name) {
- return false;
- }
- if (lhs.type.value != rhs.type.value) {
- return false;
- }
- if (lhs.sizeInBits != rhs.sizeInBits) {
- return false;
- }
- if (lhs.offsetInBits != rhs.offsetInBits) {
- return false;
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::PlaneLayoutComponent& lhs,
- const aidl::android::hardware::graphics::common::PlaneLayoutComponent& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Rect& lhs,
- const aidl::android::hardware::graphics::common::Rect& rhs) {
- if (lhs.left != rhs.left) {
- return false;
- }
- if (lhs.top != rhs.top) {
- return false;
- }
- if (lhs.right != rhs.right) {
- return false;
- }
- if (lhs.bottom != rhs.bottom) {
- return false;
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Rect& lhs,
- const aidl::android::hardware::graphics::common::Rect& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const std::vector<aidl::android::hardware::graphics::common::Rect>& lhs,
- const std::vector<aidl::android::hardware::graphics::common::Rect>& rhs) {
- if (lhs.size() != rhs.size()) {
- return false;
- }
- for (size_t i = 0; i < lhs.size(); i++) {
- if (lhs[i] != rhs[i]) {
- return false;
- }
- }
- return true;
-}
-
-inline bool operator!=(const std::vector<aidl::android::hardware::graphics::common::Rect>& lhs,
- const std::vector<aidl::android::hardware::graphics::common::Rect>& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::PlaneLayout& lhs,
- const aidl::android::hardware::graphics::common::PlaneLayout& rhs) {
- if (lhs.offsetInBytes != rhs.offsetInBytes) {
- return false;
- }
- if (lhs.sampleIncrementInBits != rhs.sampleIncrementInBits) {
- return false;
- }
- if (lhs.strideInBytes != rhs.strideInBytes) {
- return false;
- }
- if (lhs.widthInSamples != rhs.widthInSamples) {
- return false;
- }
- if (lhs.heightInSamples != rhs.heightInSamples) {
- return false;
- }
- if (lhs.totalSizeInBytes != rhs.totalSizeInBytes) {
- return false;
- }
- if (lhs.horizontalSubsampling != rhs.horizontalSubsampling) {
- return false;
- }
- if (lhs.verticalSubsampling != rhs.verticalSubsampling) {
- return false;
- }
- if (lhs.components.size() != rhs.components.size()) {
- return false;
- }
- for (size_t i = 0; i < lhs.components.size(); i++) {
- if (lhs.components[i] != rhs.components[i]) {
- return false;
- }
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::PlaneLayout& lhs,
- const aidl::android::hardware::graphics::common::PlaneLayout& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& lhs,
- const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& rhs) {
- if (lhs.size() != rhs.size()) {
- return false;
- }
- for (size_t i = 0; i < lhs.size(); i++) {
- if (lhs[i] != rhs[i]) {
- return false;
- }
- }
- return true;
-}
-
-inline bool operator!=(const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& lhs,
- const std::vector<aidl::android::hardware::graphics::common::PlaneLayout>& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::XyColor& lhs,
- const aidl::android::hardware::graphics::common::XyColor& rhs) {
- if (lhs.x != rhs.x) {
- return false;
- }
- if (lhs.y != rhs.y) {
- return false;
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::XyColor& lhs,
- const aidl::android::hardware::graphics::common::XyColor& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Smpte2086& lhs,
- const aidl::android::hardware::graphics::common::Smpte2086& rhs) {
- if (lhs.primaryRed != rhs.primaryRed) {
- return false;
- }
- if (lhs.primaryGreen != rhs.primaryGreen) {
- return false;
- }
- if (lhs.primaryBlue != rhs.primaryBlue) {
- return false;
- }
- if (lhs.whitePoint != rhs.whitePoint) {
- return false;
- }
- if (lhs.maxLuminance != rhs.maxLuminance) {
- return false;
- }
- if (lhs.minLuminance != rhs.minLuminance) {
- return false;
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Smpte2086& lhs,
- const aidl::android::hardware::graphics::common::Smpte2086& rhs) {
- return !(lhs == rhs);
-}
-
-inline bool operator==(const aidl::android::hardware::graphics::common::Cta861_3& lhs,
- const aidl::android::hardware::graphics::common::Cta861_3& rhs) {
- if (lhs.maxContentLightLevel != rhs.maxContentLightLevel) {
- return false;
- }
- if (lhs.maxFrameAverageLightLevel != rhs.maxFrameAverageLightLevel) {
- return false;
- }
- return true;
-}
-
-inline bool operator!=(const aidl::android::hardware::graphics::common::Cta861_3& lhs,
- const aidl::android::hardware::graphics::common::Cta861_3& rhs) {
- return !(lhs == rhs);
-}
-
namespace gralloc4 {
#define GRALLOC4_STANDARD_METADATA_TYPE "android.hardware.graphics.common.StandardMetadataType"
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index af9ef06..38ae353 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,
@@ -68,7 +69,6 @@
"IGraphicBufferProducer.cpp",
"IProducerListener.cpp",
"IRegionSamplingListener.cpp",
- "IScreenCaptureListener.cpp",
"ISurfaceComposer.cpp",
"ISurfaceComposerClient.cpp",
"ITransactionCompletedListener.cpp",
@@ -77,6 +77,7 @@
"LayerState.cpp",
"OccupancyTracker.cpp",
"StreamSplitter.cpp",
+ "ScreenCaptureResults.cpp",
"Surface.cpp",
"SurfaceControl.cpp",
"SurfaceComposerClient.cpp",
@@ -161,6 +162,7 @@
filegroup {
name: "libgui_bufferqueue_sources",
srcs: [
+ "BatchBufferOps.cpp",
"BufferItem.cpp",
"BufferQueue.cpp",
"BufferQueueConsumer.cpp",
@@ -171,7 +173,7 @@
"FrameTimestamps.cpp",
"GLConsumerUtils.cpp",
"HdrMetadata.cpp",
- "QueueBufferInputOutput.cpp",
+ "IGraphicBufferProducerFlattenables.cpp",
"bufferqueue/1.0/Conversion.cpp",
"bufferqueue/1.0/H2BProducerListener.cpp",
"bufferqueue/1.0/WProducerListener.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 3a3a96f..f4b5a26 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>
@@ -42,6 +47,10 @@
// Macros to include adapter info in log messages
#define BQA_LOGV(x, ...) \
ALOGV("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
+// enable logs for a single layer
+//#define BQA_LOGV(x, ...) \
+// ALOGV_IF((strstr(mName.c_str(), "SurfaceView") != nullptr), "[%s](f:%u,a:%u) " x, \
+// mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
#define BQA_LOGE(x, ...) \
ALOGE("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
@@ -114,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());
@@ -123,7 +132,7 @@
mProducer->setMaxDequeuedBufferCount(2);
}
mBufferItemConsumer =
- new BLASTBufferItemConsumer(mConsumer, GraphicBuffer::USAGE_HW_COMPOSER, 1, true);
+ new BLASTBufferItemConsumer(mConsumer, GraphicBuffer::USAGE_HW_COMPOSER, 1, false);
static int32_t id = 0;
auto consumerName = mName + "(BLAST Consumer)" + std::to_string(id);
id++;
@@ -142,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;
@@ -150,6 +172,16 @@
if (mRequestedSize != newSize) {
mRequestedSize.set(newSize);
mBufferItemConsumer->setDefaultBufferSize(mRequestedSize.width, mRequestedSize.height);
+ if (mLastBufferScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
+ // If the buffer supports scaling, update the frame immediately since the client may
+ // want to scale the existing buffer to the new size.
+ mSize = mRequestedSize;
+ SurfaceComposerClient::Transaction t;
+ t.setFrame(mSurfaceControl,
+ {0, 0, static_cast<int32_t>(mSize.width),
+ static_cast<int32_t>(mSize.height)});
+ t.apply();
+ }
}
}
@@ -165,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) {
@@ -238,7 +285,8 @@
BufferItem bufferItem;
- status_t status = mBufferItemConsumer->acquireBuffer(&bufferItem, -1, false);
+ status_t status =
+ mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
if (status != OK) {
BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
return;
@@ -276,7 +324,13 @@
// Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
incStrong((void*)transactionCallbackThunk);
+ mLastBufferScalingMode = bufferItem.mScalingMode;
+ mLastAcquiredFrameNumber = bufferItem.mFrameNumber;
+
t->setBuffer(mSurfaceControl, buffer);
+ t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
+ t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
+ t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
t->setAcquireFence(mSurfaceControl,
bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
@@ -286,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()) {
@@ -298,15 +354,40 @@
t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh);
mAutoRefresh = bufferItem.mAutoRefresh;
}
+ {
+ std::unique_lock _lock{mTimestampMutex};
+ auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
+ if (dequeueTime != mDequeueTimestamps.end()) {
+ Parcel p;
+ p.writeInt64(dequeueTime->second);
+ t->setMetadata(mSurfaceControl, METADATA_DEQUEUE_TIME, p);
+ mDequeueTimestamps.erase(dequeueTime);
+ }
+ }
+
+ 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) {
@@ -341,6 +422,16 @@
// Do nothing since we are not storing unacquired buffer items locally.
}
+void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
+ std::unique_lock _lock{mTimestampMutex};
+ mDequeueTimestamps[bufferId] = systemTime();
+};
+
+void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
+ std::unique_lock _lock{mTimestampMutex};
+ mDequeueTimestamps.erase(bufferId);
+};
+
void BLASTBufferQueue::setNextTransaction(SurfaceComposerClient::Transaction* t) {
std::lock_guard _lock{mMutex};
mNextTransaction = t;
@@ -348,7 +439,6 @@
bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
- mSize = mRequestedSize;
// Only reject buffers if scaling mode is freeze.
return false;
}
@@ -370,6 +460,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.
@@ -435,4 +536,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/BatchBufferOps.cpp b/libs/gui/BatchBufferOps.cpp
new file mode 100644
index 0000000..60aceb1
--- /dev/null
+++ b/libs/gui/BatchBufferOps.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "IGBPBatchOps"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+/**
+ * Default implementation of batched buffer operations. These default
+ * implementations call into the non-batched version of the same operation.
+ */
+
+status_t IGraphicBufferProducer::requestBuffers(
+ const std::vector<int32_t>& slots,
+ std::vector<RequestBufferOutput>* outputs) {
+ outputs->clear();
+ outputs->reserve(slots.size());
+ for (int32_t slot : slots) {
+ RequestBufferOutput& output = outputs->emplace_back();
+ output.result = requestBuffer(static_cast<int>(slot),
+ &output.buffer);
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::dequeueBuffers(
+ const std::vector<DequeueBufferInput>& inputs,
+ std::vector<DequeueBufferOutput>* outputs) {
+ outputs->clear();
+ outputs->reserve(inputs.size());
+ for (const DequeueBufferInput& input : inputs) {
+ DequeueBufferOutput& output = outputs->emplace_back();
+ output.result = dequeueBuffer(
+ &output.slot,
+ &output.fence,
+ input.width,
+ input.height,
+ input.format,
+ input.usage,
+ &output.bufferAge,
+ input.getTimestamps ? &output.timestamps.emplace() : nullptr);
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::detachBuffers(
+ const std::vector<int32_t>& slots,
+ std::vector<status_t>* results) {
+ results->clear();
+ results->reserve(slots.size());
+ for (int32_t slot : slots) {
+ results->emplace_back(detachBuffer(slot));
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::attachBuffers(
+ const std::vector<sp<GraphicBuffer>>& buffers,
+ std::vector<AttachBufferOutput>* outputs) {
+ outputs->clear();
+ outputs->reserve(buffers.size());
+ for (const sp<GraphicBuffer>& buffer : buffers) {
+ AttachBufferOutput& output = outputs->emplace_back();
+ output.result = attachBuffer(&output.slot, buffer);
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::queueBuffers(
+ const std::vector<QueueBufferInput>& inputs,
+ std::vector<QueueBufferOutput>* outputs) {
+ outputs->clear();
+ outputs->reserve(inputs.size());
+ for (const QueueBufferInput& input : inputs) {
+ QueueBufferOutput& output = outputs->emplace_back();
+ output.result = queueBuffer(input.slot, input, &output);
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::cancelBuffers(
+ const std::vector<CancelBufferInput>& inputs,
+ std::vector<status_t>* results) {
+ results->clear();
+ results->reserve(inputs.size());
+ for (const CancelBufferInput& input : inputs) {
+ results->emplace_back() = cancelBuffer(input.slot, input.fence);
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::query(const std::vector<int32_t> inputs,
+ std::vector<QueryOutput>* outputs) {
+ outputs->clear();
+ outputs->reserve(inputs.size());
+ for (int32_t input : inputs) {
+ QueryOutput& output = outputs->emplace_back();
+ int value{};
+ output.result = static_cast<status_t>(
+ query(static_cast<int>(input), &value));
+ output.value = static_cast<int64_t>(value);
+ }
+ return NO_ERROR;
+}
+
+} // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index ad00939..c1f9b85 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -74,6 +74,13 @@
GET_CONSUMER_USAGE,
SET_LEGACY_BUFFER_DROP,
SET_AUTO_PREROTATION,
+ REQUEST_BUFFERS,
+ DEQUEUE_BUFFERS,
+ DETACH_BUFFERS,
+ ATTACH_BUFFERS,
+ QUEUE_BUFFERS,
+ CANCEL_BUFFERS,
+ QUERY_MULTIPLE,
};
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -90,7 +97,7 @@
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
data.writeInt32(bufferIdx);
- status_t result =remote()->transact(REQUEST_BUFFER, data, &reply);
+ status_t result = remote()->transact(REQUEST_BUFFER, data, &reply);
if (result != NO_ERROR) {
return result;
}
@@ -107,6 +114,27 @@
return result;
}
+ virtual status_t requestBuffers(
+ const std::vector<int32_t>& slots,
+ std::vector<RequestBufferOutput>* outputs) override {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeInt32Vector(slots);
+ status_t result = remote()->transact(REQUEST_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.resizeOutVector(outputs);
+ for (RequestBufferOutput& output : *outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(output);
+ }
+
+ return result;
+ }
+
virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) {
Parcel data, reply;
data.writeInterfaceToken(
@@ -183,6 +211,29 @@
return result;
}
+ virtual status_t dequeueBuffers(
+ const std::vector<DequeueBufferInput>& inputs,
+ std::vector<DequeueBufferOutput>* outputs) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeVectorSize(inputs);
+ for (const auto& input : inputs) {
+ data.write(input);
+ }
+ status_t result = remote()->transact(DEQUEUE_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.resizeOutVector(outputs);
+ for (auto& output : *outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(output);
+ }
+ return result;
+ }
+
virtual status_t detachBuffer(int slot) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -195,6 +246,19 @@
return result;
}
+ virtual status_t detachBuffers(const std::vector<int32_t>& slots,
+ std::vector<status_t>* results) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeInt32Vector(slots);
+ status_t result = remote()->transact(DETACH_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32Vector(results);
+ return result;
+ }
+
virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
sp<Fence>* outFence) {
if (outBuffer == nullptr) {
@@ -256,6 +320,39 @@
return result;
}
+ virtual status_t attachBuffers(
+ const std::vector<sp<GraphicBuffer>>& buffers,
+ std::vector<AttachBufferOutput>* outputs) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeVectorSize(buffers);
+ for (const sp<GraphicBuffer>& buffer : buffers) {
+ data.write(*buffer.get());
+ }
+ status_t result = remote()->transact(ATTACH_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.resizeOutVector(outputs);
+ for (AttachBufferOutput& output : *outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(output);
+ }
+ if (result == NO_ERROR) {
+ for (AttachBufferOutput& output : *outputs) {
+ if (output.result == NO_ERROR && output.slot < 0) {
+ ALOGE("attachBuffers returned invalid slot %d",
+ output.slot);
+ android_errorWriteLog(0x534e4554, "37478824");
+ output.result = UNKNOWN_ERROR;
+ }
+ }
+ }
+ return result;
+ }
+
virtual status_t queueBuffer(int buf,
const QueueBufferInput& input, QueueBufferOutput* output) {
Parcel data, reply;
@@ -278,6 +375,28 @@
return result;
}
+ virtual status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+ std::vector<QueueBufferOutput>* outputs) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeVectorSize(inputs);
+ for (const QueueBufferInput& input : inputs) {
+ data.write(input);
+ }
+ status_t result = remote()->transact(QUEUE_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.resizeOutVector(outputs);
+ for (QueueBufferOutput& output : *outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(output);
+ }
+ return result;
+ }
+
virtual status_t cancelBuffer(int buf, const sp<Fence>& fence) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -291,6 +410,23 @@
return result;
}
+ virtual status_t cancelBuffers(
+ const std::vector<CancelBufferInput>& inputs,
+ std::vector<status_t>* results) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeVectorSize(inputs);
+ for (const CancelBufferInput& input : inputs) {
+ data.write(input);
+ }
+ status_t result = remote()->transact(CANCEL_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32Vector(results);
+ return result;
+ }
+
virtual int query(int what, int* value) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
@@ -304,6 +440,25 @@
return result;
}
+ virtual status_t query(const std::vector<int32_t> inputs,
+ std::vector<QueryOutput>* outputs) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeInt32Vector(inputs);
+ status_t result = remote()->transact(QUERY_MULTIPLE, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.resizeOutVector(outputs);
+ for (QueryOutput& output : *outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(output);
+ }
+ return result;
+ }
+
virtual status_t connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp, QueueBufferOutput* output) {
Parcel data, reply;
@@ -576,6 +731,12 @@
return mBase->requestBuffer(slot, buf);
}
+ status_t requestBuffers(
+ const std::vector<int32_t>& slots,
+ std::vector<RequestBufferOutput>* outputs) override {
+ return mBase->requestBuffers(slots, outputs);
+ }
+
status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers) override {
return mBase->setMaxDequeuedBufferCount(maxDequeuedBuffers);
}
@@ -590,10 +751,21 @@
return mBase->dequeueBuffer(slot, fence, w, h, format, usage, outBufferAge, outTimestamps);
}
+ status_t dequeueBuffers(
+ const std::vector<DequeueBufferInput>& inputs,
+ std::vector<DequeueBufferOutput>* outputs) override {
+ return mBase->dequeueBuffers(inputs, outputs);
+ }
+
status_t detachBuffer(int slot) override {
return mBase->detachBuffer(slot);
}
+ status_t detachBuffers(const std::vector<int32_t>& slots,
+ std::vector<status_t>* results) override {
+ return mBase->detachBuffers(slots, results);
+ }
+
status_t detachNextBuffer(
sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence) override {
return mBase->detachNextBuffer(outBuffer, outFence);
@@ -604,6 +776,12 @@
return mBase->attachBuffer(outSlot, buffer);
}
+ status_t attachBuffers(
+ const std::vector<sp<GraphicBuffer>>& buffers,
+ std::vector<AttachBufferOutput>* outputs) override {
+ return mBase->attachBuffers(buffers, outputs);
+ }
+
status_t queueBuffer(
int slot,
const QueueBufferInput& input,
@@ -611,14 +789,30 @@
return mBase->queueBuffer(slot, input, output);
}
+ status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+ std::vector<QueueBufferOutput>* outputs) override {
+ return mBase->queueBuffers(inputs, outputs);
+ }
+
status_t cancelBuffer(int slot, const sp<Fence>& fence) override {
return mBase->cancelBuffer(slot, fence);
}
+ status_t cancelBuffers(
+ const std::vector<CancelBufferInput>& inputs,
+ std::vector<status_t>* results) override {
+ return mBase->cancelBuffers(inputs, results);
+ }
+
int query(int what, int* value) override {
return mBase->query(what, value);
}
+ status_t query(const std::vector<int32_t> inputs,
+ std::vector<QueryOutput>* outputs) override {
+ return mBase->query(inputs, outputs);
+ }
+
status_t connect(
const sp<IProducerListener>& listener,
int api, bool producerControlledByApp,
@@ -789,7 +983,7 @@
switch(code) {
case REQUEST_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
- int bufferIdx = data.readInt32();
+ int bufferIdx = data.readInt32();
sp<GraphicBuffer> buffer;
int result = requestBuffer(bufferIdx, &buffer);
reply->writeInt32(buffer != nullptr);
@@ -799,6 +993,24 @@
reply->writeInt32(result);
return NO_ERROR;
}
+ case REQUEST_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<int32_t> slots;
+ std::vector<RequestBufferOutput> outputs;
+ status_t result = data.readInt32Vector(&slots);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ (void)requestBuffers(slots, &outputs);
+ result = reply->writeVectorSize(outputs);
+ for (const RequestBufferOutput& output : outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->write(output);
+ }
+ return result;
+ }
case SET_MAX_DEQUEUED_BUFFER_COUNT: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
int maxDequeuedBuffers = data.readInt32();
@@ -841,6 +1053,30 @@
reply->writeInt32(result);
return NO_ERROR;
}
+ case DEQUEUE_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<DequeueBufferInput> inputs;
+ std::vector<DequeueBufferOutput> outputs;
+ status_t result = data.resizeOutVector(&inputs);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ for (DequeueBufferInput& input : inputs) {
+ result = data.read(input);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ }
+ (void)dequeueBuffers(inputs, &outputs);
+ result = reply->writeVectorSize(outputs);
+ for (const DequeueBufferOutput& output : outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->write(output);
+ }
+ return result;
+ }
case DETACH_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
int slot = data.readInt32();
@@ -848,6 +1084,17 @@
reply->writeInt32(result);
return NO_ERROR;
}
+ case DETACH_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<int32_t> slots;
+ std::vector<status_t> results;
+ status_t result = data.readInt32Vector(&slots);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ (void)detachBuffers(slots, &results);
+ return reply->writeInt32Vector(results);
+ }
case DETACH_NEXT_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
sp<GraphicBuffer> buffer;
@@ -878,6 +1125,31 @@
reply->writeInt32(result);
return NO_ERROR;
}
+ case ATTACH_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<sp<GraphicBuffer>> buffers;
+ status_t result = data.resizeOutVector(&buffers);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ for (sp<GraphicBuffer>& buffer : buffers) {
+ buffer = new GraphicBuffer();
+ result = data.read(*buffer.get());
+ if (result != NO_ERROR) {
+ return result;
+ }
+ }
+ std::vector<AttachBufferOutput> outputs;
+ (void)attachBuffers(buffers, &outputs);
+ result = reply->writeVectorSize(outputs);
+ for (const AttachBufferOutput& output : outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->write(output);
+ }
+ return result;
+ }
case QUEUE_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
@@ -890,6 +1162,30 @@
return NO_ERROR;
}
+ case QUEUE_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<QueueBufferInput> inputs;
+ status_t result = data.resizeOutVector(&inputs);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ for (QueueBufferInput& input : inputs) {
+ result = data.read(input);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ }
+ std::vector<QueueBufferOutput> outputs;
+ (void)queueBuffers(inputs, &outputs);
+ result = reply->writeVectorSize(outputs);
+ for (const QueueBufferOutput& output : outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->write(output);
+ }
+ return result;
+ }
case CANCEL_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
int buf = data.readInt32();
@@ -901,6 +1197,26 @@
reply->writeInt32(result);
return NO_ERROR;
}
+ case CANCEL_BUFFERS: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<CancelBufferInput> inputs;
+ status_t result = data.resizeOutVector(&inputs);
+ for (CancelBufferInput& input : inputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = data.read(input);
+ }
+ if (result != NO_ERROR) {
+ return result;
+ }
+ std::vector<status_t> results;
+ result = cancelBuffers(inputs, &results);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply->writeInt32Vector(results);
+ }
case QUERY: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
int value = 0;
@@ -910,6 +1226,27 @@
reply->writeInt32(res);
return NO_ERROR;
}
+ case QUERY_MULTIPLE: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ std::vector<int32_t> inputs;
+ status_t result = data.readInt32Vector(&inputs);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ std::vector<QueryOutput> outputs;
+ result = query(inputs, &outputs);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->writeVectorSize(outputs);
+ for (const QueryOutput& output : outputs) {
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply->write(output);
+ }
+ return result;
+ }
case CONNECT: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
sp<IProducerListener> listener;
@@ -1083,11 +1420,4 @@
return BBinder::onTransact(code, data, reply, flags);
}
-// ----------------------------------------------------------------------------
-
-IGraphicBufferProducer::QueueBufferInput::QueueBufferInput(const Parcel& parcel) {
- parcel.read(*this);
-}
-
-
}; // namespace android
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
new file mode 100644
index 0000000..c8b9b67
--- /dev/null
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
+ return sizeof(timestamp) +
+ sizeof(isAutoTimestamp) +
+ sizeof(dataSpace) +
+ sizeof(crop) +
+ sizeof(scalingMode) +
+ sizeof(transform) +
+ sizeof(stickyTransform) +
+ sizeof(getFrameTimestamps) +
+ sizeof(slot);
+}
+
+size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
+ return minFlattenedSize() +
+ fence->getFlattenedSize() +
+ surfaceDamage.getFlattenedSize() +
+ hdrMetadata.getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::QueueBufferInput::getFdCount() const {
+ return fence->getFdCount();
+}
+
+status_t IGraphicBufferProducer::QueueBufferInput::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const
+{
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, timestamp);
+ FlattenableUtils::write(buffer, size, isAutoTimestamp);
+ FlattenableUtils::write(buffer, size, dataSpace);
+ FlattenableUtils::write(buffer, size, crop);
+ FlattenableUtils::write(buffer, size, scalingMode);
+ FlattenableUtils::write(buffer, size, transform);
+ FlattenableUtils::write(buffer, size, stickyTransform);
+ FlattenableUtils::write(buffer, size, getFrameTimestamps);
+
+ status_t result = fence->flatten(buffer, size, fds, count);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = surfaceDamage.flatten(buffer, size);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
+ result = hdrMetadata.flatten(buffer, size);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::advance(buffer, size, hdrMetadata.getFlattenedSize());
+ FlattenableUtils::write(buffer, size, slot);
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count)
+{
+ if (size < minFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, timestamp);
+ FlattenableUtils::read(buffer, size, isAutoTimestamp);
+ FlattenableUtils::read(buffer, size, dataSpace);
+ FlattenableUtils::read(buffer, size, crop);
+ FlattenableUtils::read(buffer, size, scalingMode);
+ FlattenableUtils::read(buffer, size, transform);
+ FlattenableUtils::read(buffer, size, stickyTransform);
+ FlattenableUtils::read(buffer, size, getFrameTimestamps);
+
+ fence = new Fence();
+ status_t result = fence->unflatten(buffer, size, fds, count);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = surfaceDamage.unflatten(buffer, size);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
+ result = hdrMetadata.unflatten(buffer, size);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::advance(buffer, size, hdrMetadata.getFlattenedSize());
+ FlattenableUtils::read(buffer, size, slot);
+ return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
+ return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
+ sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount) +
+ sizeof(result);
+}
+size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
+ return minFlattenedSize() + frameTimestamps.getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::QueueBufferOutput::getFdCount() const {
+ return frameTimestamps.getFdCount();
+}
+
+status_t IGraphicBufferProducer::QueueBufferOutput::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const
+{
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, width);
+ FlattenableUtils::write(buffer, size, height);
+ FlattenableUtils::write(buffer, size, transformHint);
+ FlattenableUtils::write(buffer, size, numPendingBuffers);
+ FlattenableUtils::write(buffer, size, nextFrameNumber);
+ FlattenableUtils::write(buffer, size, bufferReplaced);
+ FlattenableUtils::write(buffer, size, maxBufferCount);
+
+ status_t result = frameTimestamps.flatten(buffer, size, fds, count);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::write(buffer, size, result);
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueueBufferOutput::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count)
+{
+ if (size < minFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, width);
+ FlattenableUtils::read(buffer, size, height);
+ FlattenableUtils::read(buffer, size, transformHint);
+ FlattenableUtils::read(buffer, size, numPendingBuffers);
+ FlattenableUtils::read(buffer, size, nextFrameNumber);
+ FlattenableUtils::read(buffer, size, bufferReplaced);
+ FlattenableUtils::read(buffer, size, maxBufferCount);
+
+ status_t result = frameTimestamps.unflatten(buffer, size, fds, count);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ FlattenableUtils::read(buffer, size, result);
+ return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+constexpr size_t IGraphicBufferProducer::RequestBufferOutput::minFlattenedSize() {
+ return sizeof(result) +
+ sizeof(int32_t); // IsBufferNull
+}
+
+size_t IGraphicBufferProducer::RequestBufferOutput::getFlattenedSize() const {
+ return minFlattenedSize() + (buffer == nullptr ? 0 : buffer->getFlattenedSize());
+}
+
+size_t IGraphicBufferProducer::RequestBufferOutput::getFdCount() const {
+ return (buffer == nullptr ? 0 : buffer->getFdCount());
+}
+
+status_t IGraphicBufferProducer::RequestBufferOutput::flatten(
+ void*& fBuffer, size_t& size, int*& fds, size_t& count) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(fBuffer, size, result);
+ const int32_t isBufferNull = (buffer == nullptr ? 1 : 0);
+ FlattenableUtils::write(fBuffer, size, isBufferNull);
+
+ if (!isBufferNull) {
+ status_t status = buffer->flatten(fBuffer, size, fds, count);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::RequestBufferOutput::unflatten(
+ void const*& fBuffer, size_t& size, int const*& fds, size_t& count) {
+ if (size < minFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(fBuffer, size, result);
+ int32_t isBufferNull = 0;
+ FlattenableUtils::read(fBuffer, size, isBufferNull);
+ buffer = new GraphicBuffer();
+ if (!isBufferNull) {
+ status_t status = buffer->unflatten(fBuffer, size, fds, count);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ }
+ return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::DequeueBufferInput::getFlattenedSize() const {
+ return sizeof(width) + sizeof(height) + sizeof(format) + sizeof(usage) +
+ sizeof(int32_t/*getTimestamps*/);
+}
+
+status_t IGraphicBufferProducer::DequeueBufferInput::flatten(void* buffer, size_t size) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+ FlattenableUtils::write(buffer, size, width);
+ FlattenableUtils::write(buffer, size, height);
+ FlattenableUtils::write(buffer, size, format);
+ FlattenableUtils::write(buffer, size, usage);
+ const int32_t getTimestampsInt = (getTimestamps ? 1 : 0);
+ FlattenableUtils::write(buffer, size, getTimestampsInt);
+
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::DequeueBufferInput::unflatten(void const* buffer, size_t size) {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, width);
+ FlattenableUtils::read(buffer, size, height);
+ FlattenableUtils::read(buffer, size, format);
+ FlattenableUtils::read(buffer, size, usage);
+ int32_t getTimestampsInt = 0;
+ FlattenableUtils::read(buffer, size, getTimestampsInt);
+ getTimestamps = (getTimestampsInt == 1);
+
+ return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+constexpr size_t IGraphicBufferProducer::DequeueBufferOutput::minFlattenedSize() {
+ return sizeof(result) + sizeof(slot) + sizeof(bufferAge) + sizeof(int32_t/*hasTimestamps*/);
+}
+
+size_t IGraphicBufferProducer::DequeueBufferOutput::getFlattenedSize() const {
+ return minFlattenedSize() +
+ fence->getFlattenedSize() +
+ (timestamps.has_value() ? timestamps->getFlattenedSize() : 0);
+}
+
+size_t IGraphicBufferProducer::DequeueBufferOutput::getFdCount() const {
+ return fence->getFdCount() +
+ (timestamps.has_value() ? timestamps->getFdCount() : 0);
+}
+
+status_t IGraphicBufferProducer::DequeueBufferOutput::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, result);
+ FlattenableUtils::write(buffer, size, slot);
+ FlattenableUtils::write(buffer, size, bufferAge);
+ status_t status = fence->flatten(buffer, size, fds, count);
+ if (status != NO_ERROR) {
+ return result;
+ }
+ const int32_t hasTimestamps = timestamps.has_value() ? 1 : 0;
+ FlattenableUtils::write(buffer, size, hasTimestamps);
+ if (timestamps.has_value()) {
+ status = timestamps->flatten(buffer, size, fds, count);
+ }
+ return status;
+}
+
+status_t IGraphicBufferProducer::DequeueBufferOutput::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+ if (size < minFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, result);
+ FlattenableUtils::read(buffer, size, slot);
+ FlattenableUtils::read(buffer, size, bufferAge);
+
+ fence = new Fence();
+ status_t status = fence->unflatten(buffer, size, fds, count);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ int32_t hasTimestamps = 0;
+ FlattenableUtils::read(buffer, size, hasTimestamps);
+ if (hasTimestamps) {
+ timestamps.emplace();
+ status = timestamps->unflatten(buffer, size, fds, count);
+ }
+ return status;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::AttachBufferOutput::getFlattenedSize() const {
+ return sizeof(result) + sizeof(slot);
+}
+
+status_t IGraphicBufferProducer::AttachBufferOutput::flatten(void* buffer, size_t size) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+ FlattenableUtils::write(buffer, size, result);
+ FlattenableUtils::write(buffer, size, slot);
+
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::AttachBufferOutput::unflatten(void const* buffer, size_t size) {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+ FlattenableUtils::read(buffer, size, result);
+ FlattenableUtils::read(buffer, size, slot);
+
+ return NO_ERROR;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+constexpr size_t IGraphicBufferProducer::CancelBufferInput::minFlattenedSize() {
+ return sizeof(slot);
+}
+
+size_t IGraphicBufferProducer::CancelBufferInput::getFlattenedSize() const {
+ return minFlattenedSize() + fence->getFlattenedSize();
+}
+
+size_t IGraphicBufferProducer::CancelBufferInput::getFdCount() const {
+ return fence->getFdCount();
+}
+
+status_t IGraphicBufferProducer::CancelBufferInput::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, slot);
+ return fence->flatten(buffer, size, fds, count);
+}
+
+status_t IGraphicBufferProducer::CancelBufferInput::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+ if (size < minFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, slot);
+
+ fence = new Fence();
+ return fence->unflatten(buffer, size, fds, count);
+}
+
+////////////////////////////////////////////////////////////////////////
+
+size_t IGraphicBufferProducer::QueryOutput::getFlattenedSize() const {
+ return sizeof(result) + sizeof(value);
+}
+
+status_t IGraphicBufferProducer::QueryOutput::flatten(void* buffer, size_t size) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+ FlattenableUtils::write(buffer, size, result);
+ FlattenableUtils::write(buffer, size, value);
+
+ return NO_ERROR;
+}
+
+status_t IGraphicBufferProducer::QueryOutput::unflatten(void const* buffer, size_t size) {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+ FlattenableUtils::read(buffer, size, result);
+ FlattenableUtils::read(buffer, size, value);
+
+ return NO_ERROR;
+}
+
+} // namespace android
diff --git a/libs/gui/IScreenCaptureListener.cpp b/libs/gui/IScreenCaptureListener.cpp
deleted file mode 100644
index 0635e9c..0000000
--- a/libs/gui/IScreenCaptureListener.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gui/IScreenCaptureListener.h>
-#include <gui/LayerState.h>
-
-namespace android {
-
-namespace { // Anonymous
-
-enum class Tag : uint32_t {
- ON_SCREEN_CAPTURE_COMPLETE = IBinder::FIRST_CALL_TRANSACTION,
- LAST = ON_SCREEN_CAPTURE_COMPLETE,
-};
-
-} // Anonymous namespace
-
-class BpScreenCaptureListener : public SafeBpInterface<IScreenCaptureListener> {
-public:
- explicit BpScreenCaptureListener(const sp<IBinder>& impl)
- : SafeBpInterface<IScreenCaptureListener>(impl, "BpScreenCaptureListener") {}
-
- ~BpScreenCaptureListener() override;
-
- status_t onScreenCaptureComplete(const ScreenCaptureResults& captureResults) override {
- Parcel data, reply;
- data.writeInterfaceToken(IScreenCaptureListener::getInterfaceDescriptor());
-
- SAFE_PARCEL(captureResults.write, data);
- return remote()->transact(static_cast<uint32_t>(Tag::ON_SCREEN_CAPTURE_COMPLETE), data,
- &reply, IBinder::FLAG_ONEWAY);
- }
-};
-
-// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
-// clang warning -Wweak-vtables)
-BpScreenCaptureListener::~BpScreenCaptureListener() = default;
-
-IMPLEMENT_META_INTERFACE(ScreenCaptureListener, "android.gui.IScreenCaptureListener");
-
-status_t BnScreenCaptureListener::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags) {
- auto tag = static_cast<Tag>(code);
- switch (tag) {
- case Tag::ON_SCREEN_CAPTURE_COMPLETE: {
- CHECK_INTERFACE(IScreenCaptureListener, data, reply);
- ScreenCaptureResults captureResults;
- SAFE_PARCEL(captureResults.read, data);
- return onScreenCaptureComplete(captureResults);
- }
- default: {
- return BBinder::onTransact(code, data, reply, flags);
- }
- }
-}
-
-} // namespace android
\ No newline at end of file
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 69f7894..1808571 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "ITransactionCompletedListener"
//#define LOG_NDEBUG 0
+#include <gui/LayerState.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/ITransactionCompletedListener.h>
namespace android {
@@ -90,61 +92,61 @@
return err;
}
-status_t SurfaceStats::writeToParcel(Parcel* output) const {
- status_t err = output->writeStrongBinder(surfaceControl);
- if (err != NO_ERROR) {
- return err;
- }
- err = output->writeInt64(acquireTime);
- if (err != NO_ERROR) {
- return err;
- }
- if (previousReleaseFence) {
- err = output->writeBool(true);
- if (err != NO_ERROR) {
- return err;
- }
- err = output->write(*previousReleaseFence);
- } else {
- err = output->writeBool(false);
- }
- err = output->writeUint32(transformHint);
- if (err != NO_ERROR) {
- return err;
- }
+JankData::JankData() :
+ frameVsyncId(ISurfaceComposer::INVALID_VSYNC_ID),
+ jankType(JankType::None) {
+}
- err = output->writeParcelable(eventStats);
- return err;
+status_t JankData::writeToParcel(Parcel* output) const {
+ SAFE_PARCEL(output->writeInt64, frameVsyncId);
+ SAFE_PARCEL(output->writeInt32, jankType);
+ return NO_ERROR;
+}
+
+status_t JankData::readFromParcel(const Parcel* input) {
+ SAFE_PARCEL(input->readInt64, &frameVsyncId);
+ SAFE_PARCEL(input->readInt32, &jankType);
+ return NO_ERROR;
+}
+
+status_t SurfaceStats::writeToParcel(Parcel* output) const {
+ SAFE_PARCEL(output->writeStrongBinder, surfaceControl);
+ SAFE_PARCEL(output->writeInt64, acquireTime);
+ if (previousReleaseFence) {
+ SAFE_PARCEL(output->writeBool, true);
+ SAFE_PARCEL(output->write, *previousReleaseFence);
+ } else {
+ SAFE_PARCEL(output->writeBool, false);
+ }
+ SAFE_PARCEL(output->writeUint32, transformHint);
+ SAFE_PARCEL(output->writeParcelable, eventStats);
+ SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(jankData.size()));
+ for (const auto& data : jankData) {
+ SAFE_PARCEL(output->writeParcelable, data);
+ }
+ return NO_ERROR;
}
status_t SurfaceStats::readFromParcel(const Parcel* input) {
- status_t err = input->readStrongBinder(&surfaceControl);
- if (err != NO_ERROR) {
- return err;
- }
- err = input->readInt64(&acquireTime);
- if (err != NO_ERROR) {
- return err;
- }
+ SAFE_PARCEL(input->readStrongBinder, &surfaceControl);
+ SAFE_PARCEL(input->readInt64, &acquireTime);
bool hasFence = false;
- err = input->readBool(&hasFence);
- if (err != NO_ERROR) {
- return err;
- }
+ SAFE_PARCEL(input->readBool, &hasFence);
if (hasFence) {
previousReleaseFence = new Fence();
- err = input->read(*previousReleaseFence);
- if (err != NO_ERROR) {
- return err;
- }
+ SAFE_PARCEL(input->read, *previousReleaseFence);
}
- err = input->readUint32(&transformHint);
- if (err != NO_ERROR) {
- return err;
- }
+ SAFE_PARCEL(input->readUint32, &transformHint);
+ SAFE_PARCEL(input->readParcelable, &eventStats);
- err = input->readParcelable(&eventStats);
- return err;
+ int32_t jankData_size = 0;
+ SAFE_PARCEL_READ_SIZE(input->readInt32, &jankData_size, input->dataSize());
+ for (int i = 0; i < jankData_size; i++) {
+ JankData data;
+ SAFE_PARCEL(input->readParcelable, &data);
+ jankData.push_back(data);
+ }
+ return NO_ERROR;
}
status_t TransactionStats::writeToParcel(Parcel* output) const {
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 30c9b37..634d8b7 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -17,6 +17,7 @@
#include <android-base/stringprintf.h>
#include <binder/Parcel.h>
#include <gui/LayerMetadata.h>
+#include <inttypes.h>
#include "android/view/LayerMetadataKey.h"
@@ -113,6 +114,15 @@
memcpy(data.data(), p.data(), p.dataSize());
}
+std::optional<int64_t> LayerMetadata::getInt64(uint32_t key) const {
+ if (!has(key)) return std::nullopt;
+ const std::vector<uint8_t>& data = mMap.at(key);
+ if (data.size() < sizeof(int64_t)) return std::nullopt;
+ Parcel p;
+ p.setData(data.data(), data.size());
+ return p.readInt64();
+}
+
std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const {
if (!has(key)) return std::string();
switch (static_cast<view::LayerMetadataKey>(key)) {
@@ -124,6 +134,8 @@
return StringPrintf("taskId%s%d", separator, getInt32(key, 0));
case view::LayerMetadataKey::METADATA_OWNER_PID:
return StringPrintf("ownerPID%s%d", separator, getInt32(key, 0));
+ case view::LayerMetadataKey::METADATA_DEQUEUE_TIME:
+ return StringPrintf("dequeueTime%s%" PRId64, separator, *getInt64(key));
default:
return StringPrintf("%d%s%dbytes", key, separator,
static_cast<int>(mMap.at(key).size()));
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 7d2c7b8..63be3ed 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -714,33 +714,4 @@
return NO_ERROR;
}
-status_t ScreenCaptureResults::write(Parcel& output) const {
- if (buffer != nullptr) {
- SAFE_PARCEL(output.writeBool, true);
- SAFE_PARCEL(output.write, *buffer);
- } else {
- SAFE_PARCEL(output.writeBool, false);
- }
- SAFE_PARCEL(output.writeBool, capturedSecureLayers);
- SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(capturedDataspace));
- SAFE_PARCEL(output.writeInt32, result);
- return NO_ERROR;
-}
-
-status_t ScreenCaptureResults::read(const Parcel& input) {
- bool hasGraphicBuffer;
- SAFE_PARCEL(input.readBool, &hasGraphicBuffer);
- if (hasGraphicBuffer) {
- buffer = new GraphicBuffer();
- SAFE_PARCEL(input.read, *buffer);
- }
-
- SAFE_PARCEL(input.readBool, &capturedSecureLayers);
- uint32_t dataspace = 0;
- SAFE_PARCEL(input.readUint32, &dataspace);
- capturedDataspace = static_cast<ui::Dataspace>(dataspace);
- SAFE_PARCEL(input.readInt32, &result);
- return NO_ERROR;
-}
-
}; // namespace android
diff --git a/libs/gui/OWNERS b/libs/gui/OWNERS
index ecccf29..1667fb0 100644
--- a/libs/gui/OWNERS
+++ b/libs/gui/OWNERS
@@ -7,9 +7,7 @@
lpy@google.com
mathias@google.com
racarr@google.com
-steventhomas@google.com
stoza@google.com
-vhau@google.com
vishnun@google.com
per-file EndToEndNativeInputTest.cpp = svv@google.com
@@ -22,4 +20,4 @@
per-file include/gui/BufferQueue* = set noparent
per-file include/gui/BufferQueue* = jreck@google.com, sumir@google.com, alecmouri@google.com
per-file include/gui/IGraphicBuffer* = set noparent
-per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
\ No newline at end of file
+per-file include/gui/IGraphicBuffer* = jreck@google.com, sumir@google.com, alecmouri@google.com
diff --git a/libs/gui/QueueBufferInputOutput.cpp b/libs/gui/QueueBufferInputOutput.cpp
deleted file mode 100644
index 30f0ef6..0000000
--- a/libs/gui/QueueBufferInputOutput.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include <inttypes.h>
-
-#define LOG_TAG "QueueBufferInputOutput"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-//#define LOG_NDEBUG 0
-
-#include <gui/IGraphicBufferProducer.h>
-
-namespace android {
-
-constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
- return sizeof(timestamp) +
- sizeof(isAutoTimestamp) +
- sizeof(dataSpace) +
- sizeof(crop) +
- sizeof(scalingMode) +
- sizeof(transform) +
- sizeof(stickyTransform) +
- sizeof(getFrameTimestamps);
-}
-
-IGraphicBufferProducer::QueueBufferInput::QueueBufferInput(const Parcel& parcel) {
- parcel.read(*this);
-}
-
-size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
- return minFlattenedSize() +
- fence->getFlattenedSize() +
- surfaceDamage.getFlattenedSize() +
- hdrMetadata.getFlattenedSize();
-}
-
-size_t IGraphicBufferProducer::QueueBufferInput::getFdCount() const {
- return fence->getFdCount();
-}
-
-status_t IGraphicBufferProducer::QueueBufferInput::flatten(
- void*& buffer, size_t& size, int*& fds, size_t& count) const
-{
- if (size < getFlattenedSize()) {
- return NO_MEMORY;
- }
-
- FlattenableUtils::write(buffer, size, timestamp);
- FlattenableUtils::write(buffer, size, isAutoTimestamp);
- FlattenableUtils::write(buffer, size, dataSpace);
- FlattenableUtils::write(buffer, size, crop);
- FlattenableUtils::write(buffer, size, scalingMode);
- FlattenableUtils::write(buffer, size, transform);
- FlattenableUtils::write(buffer, size, stickyTransform);
- FlattenableUtils::write(buffer, size, getFrameTimestamps);
-
- status_t result = fence->flatten(buffer, size, fds, count);
- if (result != NO_ERROR) {
- return result;
- }
- result = surfaceDamage.flatten(buffer, size);
- if (result != NO_ERROR) {
- return result;
- }
- FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
- return hdrMetadata.flatten(buffer, size);
-}
-
-status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
- void const*& buffer, size_t& size, int const*& fds, size_t& count)
-{
- if (size < minFlattenedSize()) {
- return NO_MEMORY;
- }
-
- FlattenableUtils::read(buffer, size, timestamp);
- FlattenableUtils::read(buffer, size, isAutoTimestamp);
- FlattenableUtils::read(buffer, size, dataSpace);
- FlattenableUtils::read(buffer, size, crop);
- FlattenableUtils::read(buffer, size, scalingMode);
- FlattenableUtils::read(buffer, size, transform);
- FlattenableUtils::read(buffer, size, stickyTransform);
- FlattenableUtils::read(buffer, size, getFrameTimestamps);
-
- fence = new Fence();
- status_t result = fence->unflatten(buffer, size, fds, count);
- if (result != NO_ERROR) {
- return result;
- }
- result = surfaceDamage.unflatten(buffer, size);
- if (result != NO_ERROR) {
- return result;
- }
- FlattenableUtils::advance(buffer, size, surfaceDamage.getFlattenedSize());
- return hdrMetadata.unflatten(buffer, size);
-}
-
-////////////////////////////////////////////////////////////////////////
-constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
- return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
- sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount);
-}
-size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
- return minFlattenedSize() + frameTimestamps.getFlattenedSize();
-}
-
-size_t IGraphicBufferProducer::QueueBufferOutput::getFdCount() const {
- return frameTimestamps.getFdCount();
-}
-
-status_t IGraphicBufferProducer::QueueBufferOutput::flatten(
- void*& buffer, size_t& size, int*& fds, size_t& count) const
-{
- if (size < getFlattenedSize()) {
- return NO_MEMORY;
- }
-
- FlattenableUtils::write(buffer, size, width);
- FlattenableUtils::write(buffer, size, height);
- FlattenableUtils::write(buffer, size, transformHint);
- FlattenableUtils::write(buffer, size, numPendingBuffers);
- FlattenableUtils::write(buffer, size, nextFrameNumber);
- FlattenableUtils::write(buffer, size, bufferReplaced);
- FlattenableUtils::write(buffer, size, maxBufferCount);
-
- return frameTimestamps.flatten(buffer, size, fds, count);
-}
-
-status_t IGraphicBufferProducer::QueueBufferOutput::unflatten(
- void const*& buffer, size_t& size, int const*& fds, size_t& count)
-{
- if (size < minFlattenedSize()) {
- return NO_MEMORY;
- }
-
- FlattenableUtils::read(buffer, size, width);
- FlattenableUtils::read(buffer, size, height);
- FlattenableUtils::read(buffer, size, transformHint);
- FlattenableUtils::read(buffer, size, numPendingBuffers);
- FlattenableUtils::read(buffer, size, nextFrameNumber);
- FlattenableUtils::read(buffer, size, bufferReplaced);
- FlattenableUtils::read(buffer, size, maxBufferCount);
-
- return frameTimestamps.unflatten(buffer, size, fds, count);
-}
-
-} // namespace android
diff --git a/libs/gui/ScreenCaptureResults.cpp b/libs/gui/ScreenCaptureResults.cpp
new file mode 100644
index 0000000..2b29487
--- /dev/null
+++ b/libs/gui/ScreenCaptureResults.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/ScreenCaptureResults.h>
+
+namespace android::gui {
+
+status_t ScreenCaptureResults::writeToParcel(android::Parcel* parcel) const {
+ if (buffer != nullptr) {
+ SAFE_PARCEL(parcel->writeBool, true);
+ SAFE_PARCEL(parcel->write, *buffer);
+ } else {
+ SAFE_PARCEL(parcel->writeBool, false);
+ }
+ SAFE_PARCEL(parcel->writeBool, capturedSecureLayers);
+ SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(capturedDataspace));
+ SAFE_PARCEL(parcel->writeInt32, result);
+ return NO_ERROR;
+}
+
+status_t ScreenCaptureResults::readFromParcel(const android::Parcel* parcel) {
+ bool hasGraphicBuffer;
+ SAFE_PARCEL(parcel->readBool, &hasGraphicBuffer);
+ if (hasGraphicBuffer) {
+ buffer = new GraphicBuffer();
+ SAFE_PARCEL(parcel->read, *buffer);
+ }
+
+ SAFE_PARCEL(parcel->readBool, &capturedSecureLayers);
+ uint32_t dataspace = 0;
+ SAFE_PARCEL(parcel->readUint32, &dataspace);
+ capturedDataspace = static_cast<ui::Dataspace>(dataspace);
+ SAFE_PARCEL(parcel->readInt32, &result);
+ return NO_ERROR;
+}
+
+} // namespace android::gui
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 94390aa..e82f0cc 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -618,29 +618,31 @@
std::mutex mMutex;
};
+void Surface::getDequeueBufferInputLocked(
+ IGraphicBufferProducer::DequeueBufferInput* dequeueInput) {
+ LOG_ALWAYS_FATAL_IF(dequeueInput == nullptr, "input is null");
+
+ dequeueInput->width = mReqWidth ? mReqWidth : mUserWidth;
+ dequeueInput->height = mReqHeight ? mReqHeight : mUserHeight;
+
+ dequeueInput->format = mReqFormat;
+ dequeueInput->usage = mReqUsage;
+
+ dequeueInput->getTimestamps = mEnableFrameTimestamps;
+}
+
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
ATRACE_CALL();
ALOGV("Surface::dequeueBuffer");
- uint32_t reqWidth;
- uint32_t reqHeight;
- PixelFormat reqFormat;
- uint64_t reqUsage;
- bool enableFrameTimestamps;
-
+ IGraphicBufferProducer::DequeueBufferInput dqInput;
{
Mutex::Autolock lock(mMutex);
if (mReportRemovedBuffers) {
mRemovedBuffers.clear();
}
- reqWidth = mReqWidth ? mReqWidth : mUserWidth;
- reqHeight = mReqHeight ? mReqHeight : mUserHeight;
-
- reqFormat = mReqFormat;
- reqUsage = mReqUsage;
-
- enableFrameTimestamps = mEnableFrameTimestamps;
+ getDequeueBufferInputLocked(&dqInput);
if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot !=
BufferItem::INVALID_BUFFER_SLOT) {
@@ -658,16 +660,17 @@
nsecs_t startTime = systemTime();
FrameEventHistoryDelta frameTimestamps;
- status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
- reqFormat, reqUsage, &mBufferAge,
- enableFrameTimestamps ? &frameTimestamps
- : nullptr);
+ status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, dqInput.width,
+ dqInput.height, dqInput.format,
+ dqInput.usage, &mBufferAge,
+ dqInput.getTimestamps ?
+ &frameTimestamps : nullptr);
mLastDequeueDuration = systemTime() - startTime;
if (result < 0) {
ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer"
"(%d, %d, %d, %#" PRIx64 ") failed: %d",
- reqWidth, reqHeight, reqFormat, reqUsage, result);
+ dqInput.width, dqInput.height, dqInput.format, dqInput.usage, result);
return result;
}
@@ -696,7 +699,7 @@
freeAllBuffers();
}
- if (enableFrameTimestamps) {
+ if (dqInput.getTimestamps) {
mFrameEventHistory->applyDelta(frameTimestamps);
}
@@ -739,6 +742,176 @@
return OK;
}
+int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
+ using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+ using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+ using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+ using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+
+ ATRACE_CALL();
+ ALOGV("Surface::dequeueBuffers");
+
+ if (buffers->size() == 0) {
+ ALOGE("%s: must dequeue at least 1 buffer!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (mSharedBufferMode) {
+ ALOGE("%s: batch operation is not supported in shared buffer mode!",
+ __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ size_t numBufferRequested = buffers->size();
+ DequeueBufferInput input;
+
+ {
+ Mutex::Autolock lock(mMutex);
+ if (mReportRemovedBuffers) {
+ mRemovedBuffers.clear();
+ }
+
+ getDequeueBufferInputLocked(&input);
+ } // Drop the lock so that we can still touch the Surface while blocking in IGBP::dequeueBuffers
+
+ std::vector<DequeueBufferInput> dequeueInput(numBufferRequested, input);
+ std::vector<DequeueBufferOutput> dequeueOutput;
+
+ nsecs_t startTime = systemTime();
+
+ status_t result = mGraphicBufferProducer->dequeueBuffers(dequeueInput, &dequeueOutput);
+
+ mLastDequeueDuration = systemTime() - startTime;
+
+ if (result < 0) {
+ ALOGV("%s: IGraphicBufferProducer::dequeueBuffers"
+ "(%d, %d, %d, %#" PRIx64 ") failed: %d",
+ __FUNCTION__, input.width, input.height, input.format, input.usage, result);
+ return result;
+ }
+
+ std::vector<CancelBufferInput> cancelBufferInputs(numBufferRequested);
+ std::vector<status_t> cancelBufferOutputs;
+ for (size_t i = 0; i < numBufferRequested; i++) {
+ cancelBufferInputs[i].slot = dequeueOutput[i].slot;
+ cancelBufferInputs[i].fence = dequeueOutput[i].fence;
+ }
+
+ for (const auto& output : dequeueOutput) {
+ if (output.result < 0) {
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+ ALOGV("%s: IGraphicBufferProducer::dequeueBuffers"
+ "(%d, %d, %d, %#" PRIx64 ") failed: %d",
+ __FUNCTION__, input.width, input.height, input.format, input.usage,
+ output.result);
+ return output.result;
+ }
+
+ if (output.slot < 0 || output.slot >= NUM_BUFFER_SLOTS) {
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+ ALOGE("%s: IGraphicBufferProducer returned invalid slot number %d",
+ __FUNCTION__, output.slot);
+ android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
+ return FAILED_TRANSACTION;
+ }
+
+ if (input.getTimestamps && !output.timestamps.has_value()) {
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+ ALOGE("%s: no frame timestamp returns!", __FUNCTION__);
+ return FAILED_TRANSACTION;
+ }
+
+ // this should never happen
+ ALOGE_IF(output.fence == nullptr,
+ "%s: received null Fence! slot=%d", __FUNCTION__, output.slot);
+ }
+
+ Mutex::Autolock lock(mMutex);
+
+ // Write this while holding the mutex
+ mLastDequeueStartTime = startTime;
+
+ std::vector<int32_t> requestBufferSlots;
+ requestBufferSlots.reserve(numBufferRequested);
+ // handle release all buffers and request buffers
+ for (const auto& output : dequeueOutput) {
+ if (output.result & IGraphicBufferProducer::RELEASE_ALL_BUFFERS) {
+ ALOGV("%s: RELEASE_ALL_BUFFERS during batch operation", __FUNCTION__);
+ freeAllBuffers();
+ break;
+ }
+ }
+
+ for (const auto& output : dequeueOutput) {
+ // Collect slots that needs requesting buffer
+ sp<GraphicBuffer>& gbuf(mSlots[output.slot].buffer);
+ if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) || gbuf == nullptr) {
+ if (mReportRemovedBuffers && (gbuf != nullptr)) {
+ mRemovedBuffers.push_back(gbuf);
+ }
+ requestBufferSlots.push_back(output.slot);
+ }
+ }
+
+ // Batch request Buffer
+ std::vector<RequestBufferOutput> reqBufferOutput;
+ if (requestBufferSlots.size() > 0) {
+ result = mGraphicBufferProducer->requestBuffers(requestBufferSlots, &reqBufferOutput);
+ if (result != NO_ERROR) {
+ ALOGE("%s: IGraphicBufferProducer::requestBuffers failed: %d",
+ __FUNCTION__, result);
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+ return result;
+ }
+
+ // Check if we have any single failure
+ for (size_t i = 0; i < requestBufferSlots.size(); i++) {
+ if (reqBufferOutput[i].result != OK) {
+ ALOGE("%s: IGraphicBufferProducer::requestBuffers failed at %zu-th buffer, slot %d",
+ __FUNCTION__, i, requestBufferSlots[i]);
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+ return reqBufferOutput[i].result;
+ }
+ }
+
+ // Fill request buffer results to mSlots
+ for (size_t i = 0; i < requestBufferSlots.size(); i++) {
+ mSlots[requestBufferSlots[i]].buffer = reqBufferOutput[i].buffer;
+ }
+ }
+
+ for (size_t batchIdx = 0; batchIdx < numBufferRequested; batchIdx++) {
+ const auto& output = dequeueOutput[batchIdx];
+ int slot = output.slot;
+ sp<GraphicBuffer>& gbuf(mSlots[slot].buffer);
+
+ if (CC_UNLIKELY(atrace_is_tag_enabled(ATRACE_TAG_GRAPHICS))) {
+ static FenceMonitor hwcReleaseThread("HWC release");
+ hwcReleaseThread.queueFence(output.fence);
+ }
+
+ if (input.getTimestamps) {
+ mFrameEventHistory->applyDelta(output.timestamps.value());
+ }
+
+ if (output.fence->isValid()) {
+ buffers->at(batchIdx).fenceFd = output.fence->dup();
+ if (buffers->at(batchIdx).fenceFd == -1) {
+ ALOGE("%s: error duping fence: %d", __FUNCTION__, errno);
+ // dup() should never fail; something is badly wrong. Soldier on
+ // and hope for the best; the worst that should happen is some
+ // visible corruption that lasts until the next frame.
+ }
+ } else {
+ buffers->at(batchIdx).fenceFd = -1;
+ }
+
+ buffers->at(batchIdx).buffer = gbuf.get();
+ mDequeuedSlots.insert(slot);
+ }
+ return OK;
+}
+
int Surface::cancelBuffer(android_native_buffer_t* buffer,
int fenceFd) {
ATRACE_CALL();
@@ -769,15 +942,65 @@
return OK;
}
+int Surface::cancelBuffers(const std::vector<BatchBuffer>& buffers) {
+ using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+ ATRACE_CALL();
+ ALOGV("Surface::cancelBuffers");
+
+ if (mSharedBufferMode) {
+ ALOGE("%s: batch operation is not supported in shared buffer mode!",
+ __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ size_t numBuffers = buffers.size();
+ std::vector<CancelBufferInput> cancelBufferInputs(numBuffers);
+ std::vector<status_t> cancelBufferOutputs;
+ size_t numBuffersCancelled = 0;
+ int badSlotResult = 0;
+ for (size_t i = 0; i < numBuffers; i++) {
+ int slot = getSlotFromBufferLocked(buffers[i].buffer);
+ int fenceFd = buffers[i].fenceFd;
+ if (slot < 0) {
+ if (fenceFd >= 0) {
+ close(fenceFd);
+ }
+ ALOGE("%s: cannot find slot number for cancelled buffer", __FUNCTION__);
+ badSlotResult = slot;
+ } else {
+ sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);
+ cancelBufferInputs[numBuffersCancelled].slot = slot;
+ cancelBufferInputs[numBuffersCancelled++].fence = fence;
+ }
+ }
+ cancelBufferInputs.resize(numBuffersCancelled);
+ mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
+
+
+ for (size_t i = 0; i < numBuffersCancelled; i++) {
+ mDequeuedSlots.erase(cancelBufferInputs[i].slot);
+ }
+
+ if (badSlotResult != 0) {
+ return badSlotResult;
+ }
+ return OK;
+}
+
int Surface::getSlotFromBufferLocked(
android_native_buffer_t* buffer) const {
+ if (buffer == nullptr) {
+ ALOGE("%s: input buffer is null!", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
if (mSlots[i].buffer != nullptr &&
mSlots[i].buffer->handle == buffer->handle) {
return i;
}
}
- ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ ALOGE("%s: unknown buffer: %p", __FUNCTION__, buffer->handle);
return BAD_VALUE;
}
@@ -787,42 +1010,22 @@
return OK;
}
-int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
- ATRACE_CALL();
- ALOGV("Surface::queueBuffer");
- Mutex::Autolock lock(mMutex);
- int64_t timestamp;
+void Surface::getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd,
+ nsecs_t timestamp, IGraphicBufferProducer::QueueBufferInput* out) {
bool isAutoTimestamp = false;
- if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
+ if (timestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {
timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
isAutoTimestamp = true;
ALOGV("Surface::queueBuffer making up timestamp: %.2f ms",
timestamp / 1000000.0);
- } else {
- timestamp = mTimestamp;
}
- int i = getSlotFromBufferLocked(buffer);
- if (i < 0) {
- if (fenceFd >= 0) {
- close(fenceFd);
- }
- return i;
- }
- if (mSharedBufferSlot == i && mSharedBufferHasBeenQueued) {
- if (fenceFd >= 0) {
- close(fenceFd);
- }
- return OK;
- }
-
// Make sure the crop rectangle is entirely inside the buffer.
Rect crop(Rect::EMPTY_RECT);
mCrop.intersect(Rect(buffer->width, buffer->height), &crop);
sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);
- IGraphicBufferProducer::QueueBufferOutput output;
IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,
static_cast<android_dataspace>(mDataSpace), crop, mScalingMode,
mTransform ^ mStickyTransform, fence, mStickyTransform,
@@ -893,15 +1096,12 @@
input.setSurfaceDamage(flippedRegion);
}
+ *out = input;
+}
- nsecs_t now = systemTime();
- status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
- mLastQueueDuration = systemTime() - now;
- if (err != OK) {
- ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);
- }
-
- mDequeuedSlots.erase(i);
+void Surface::onBufferQueuedLocked(int slot, sp<Fence> fence,
+ const IGraphicBufferProducer::QueueBufferOutput& output) {
+ mDequeuedSlots.erase(slot);
if (mEnableFrameTimestamps) {
mFrameEventHistory->applyDelta(output.frameTimestamps);
@@ -935,7 +1135,7 @@
mDirtyRegion = Region::INVALID_REGION;
}
- if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot == i) {
+ if (mSharedBufferMode && mAutoRefresh && mSharedBufferSlot == slot) {
mSharedBufferHasBeenQueued = true;
}
@@ -945,6 +1145,89 @@
static FenceMonitor gpuCompletionThread("GPU completion");
gpuCompletionThread.queueFence(fence);
}
+}
+
+int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
+ ATRACE_CALL();
+ ALOGV("Surface::queueBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int i = getSlotFromBufferLocked(buffer);
+ if (i < 0) {
+ if (fenceFd >= 0) {
+ close(fenceFd);
+ }
+ return i;
+ }
+ if (mSharedBufferSlot == i && mSharedBufferHasBeenQueued) {
+ if (fenceFd >= 0) {
+ close(fenceFd);
+ }
+ return OK;
+ }
+
+ IGraphicBufferProducer::QueueBufferOutput output;
+ IGraphicBufferProducer::QueueBufferInput input;
+ getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
+ sp<Fence> fence = input.fence;
+
+ nsecs_t now = systemTime();
+ status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
+ mLastQueueDuration = systemTime() - now;
+ if (err != OK) {
+ ALOGE("queueBuffer: error queuing buffer, %d", err);
+ }
+
+ onBufferQueuedLocked(i, fence, output);
+ return err;
+}
+
+int Surface::queueBuffers(const std::vector<BatchQueuedBuffer>& buffers) {
+ ATRACE_CALL();
+ ALOGV("Surface::queueBuffers");
+ Mutex::Autolock lock(mMutex);
+
+ if (mSharedBufferMode) {
+ ALOGE("%s: batched operation is not supported in shared buffer mode", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ size_t numBuffers = buffers.size();
+ std::vector<IGraphicBufferProducer::QueueBufferInput> queueBufferInputs(numBuffers);
+ std::vector<IGraphicBufferProducer::QueueBufferOutput> queueBufferOutputs;
+ std::vector<int> bufferSlots(numBuffers, -1);
+ std::vector<sp<Fence>> bufferFences(numBuffers);
+
+ for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+ int i = getSlotFromBufferLocked(buffers[batchIdx].buffer);
+ if (i < 0) {
+ if (buffers[batchIdx].fenceFd >= 0) {
+ close(buffers[batchIdx].fenceFd);
+ }
+ return i;
+ }
+ bufferSlots[batchIdx] = i;
+
+ IGraphicBufferProducer::QueueBufferInput input;
+ getQueueBufferInputLocked(
+ buffers[batchIdx].buffer, buffers[batchIdx].fenceFd, buffers[batchIdx].timestamp,
+ &input);
+ bufferFences[batchIdx] = input.fence;
+ queueBufferInputs[batchIdx] = input;
+ }
+
+ nsecs_t now = systemTime();
+ status_t err = mGraphicBufferProducer->queueBuffers(queueBufferInputs, &queueBufferOutputs);
+ mLastQueueDuration = systemTime() - now;
+ if (err != OK) {
+ ALOGE("%s: error queuing buffer, %d", __FUNCTION__, err);
+ }
+
+
+ for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+ onBufferQueuedLocked(bufferSlots[batchIdx], bufferFences[batchIdx],
+ queueBufferOutputs[batchIdx]);
+ }
return err;
}
@@ -985,6 +1268,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 47a08ab..97c2693 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -125,6 +125,9 @@
return DefaultComposerClient::getComposerClient();
}
+JankDataListener::~JankDataListener() {
+}
+
// ---------------------------------------------------------------------------
// TransactionCompletedListener does not use ANDROID_SINGLETON_STATIC_INSTANCE because it needs
@@ -174,6 +177,23 @@
return callbackId;
}
+void TransactionCompletedListener::addJankListener(const sp<JankDataListener>& listener,
+ sp<SurfaceControl> surfaceControl) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mJankListeners.insert({surfaceControl->getHandle(), listener});
+}
+
+void TransactionCompletedListener::removeJankListener(const sp<JankDataListener>& listener) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ for (auto it = mJankListeners.begin(); it != mJankListeners.end();) {
+ if (it->second == listener) {
+ it = mJankListeners.erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
void TransactionCompletedListener::addSurfaceControlToCallbacks(
const sp<SurfaceControl>& surfaceControl,
const std::unordered_set<CallbackId>& callbackIds) {
@@ -189,6 +209,7 @@
void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
std::unordered_map<CallbackId, CallbackTranslation> callbacksMap;
+ std::multimap<sp<IBinder>, sp<JankDataListener>> jankListenersMap;
{
std::lock_guard<std::mutex> lock(mMutex);
@@ -204,6 +225,7 @@
* sp<SurfaceControl> that could possibly exist for the callbacks.
*/
callbacksMap = mCallbacks;
+ jankListenersMap = mJankListeners;
for (const auto& transactionStats : listenerStats.transactionStats) {
for (auto& callbackId : transactionStats.callbackIds) {
mCallbacks.erase(callbackId);
@@ -236,6 +258,13 @@
callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
surfaceControlStats);
}
+ for (const auto& surfaceStats : transactionStats.surfaceStats) {
+ if (surfaceStats.jankData.empty()) continue;
+ for (auto it = jankListenersMap.find(surfaceStats.surfaceControl);
+ it != jankListenersMap.end(); it++) {
+ it->second->onJankDataAvailable(surfaceStats.jankData);
+ }
+ }
}
}
@@ -366,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;
@@ -395,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;
@@ -468,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;
}
@@ -498,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);
@@ -574,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 &&
@@ -599,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) {
@@ -611,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() {
@@ -728,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();
@@ -1172,6 +1216,9 @@
}
s->what |= layer_state_t::eBufferChanged;
s->buffer = buffer;
+ if (mIsAutoTimestamp) {
+ mDesiredPresentTime = systemTime();
+ }
registerSurfaceControlForCallback(sc);
@@ -1266,6 +1313,7 @@
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesiredPresentTime(
nsecs_t desiredPresentTime) {
mDesiredPresentTime = desiredPresentTime;
+ mIsAutoTimestamp = false;
return *this;
}
@@ -1540,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) {
@@ -1959,6 +2013,10 @@
lightRadius);
}
+int SurfaceComposerClient::getGPUContextPriority() {
+ return ComposerService::getComposerService()->getGPUContextPriority();
+}
+
// ----------------------------------------------------------------------------
status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
diff --git a/libs/gui/aidl/android/gui/IScreenCaptureListener.aidl b/libs/gui/aidl/android/gui/IScreenCaptureListener.aidl
new file mode 100644
index 0000000..f490118
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IScreenCaptureListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.ScreenCaptureResults;
+
+/** @hide */
+oneway interface IScreenCaptureListener {
+ void onScreenCaptureComplete(in ScreenCaptureResults captureResults);
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
new file mode 100644
index 0000000..9908edd
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h";
\ No newline at end of file
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index bdf128a..0fbcbdc 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -78,10 +78,15 @@
void onBufferFreed(const wp<GraphicBuffer>&/* graphicBuffer*/) override { /* TODO */ }
void onFrameReplaced(const BufferItem& item) override;
void onFrameAvailable(const BufferItem& item) override;
+ void onFrameDequeued(const uint64_t) override;
+ void onFrameCancelled(const uint64_t) override;
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 +94,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 +102,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 +144,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;
@@ -146,6 +156,29 @@
bool mAutoRefresh GUARDED_BY(mMutex) = false;
std::queue<int64_t> mNextFrameTimelineVsyncIdQueue GUARDED_BY(mMutex);
+
+ // Last acquired buffer's scaling mode. This is used to check if we should update the blast
+ // 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 GUARDED_BY(mMutex) = new BBinder();
+
+ // Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or
+ // we will deadlock.
+ std::mutex mTimestampMutex;
+ // Tracks buffer dequeue times by the client. This info is sent to SurfaceFlinger which uses
+ // it for debugging purposes.
+ std::unordered_map<uint64_t /* bufferId */, nsecs_t> mDequeueTimestamps
+ GUARDED_BY(mTimestampMutex);
};
} // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 45e0a13..c3b9262 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -38,6 +38,9 @@
#include <android/hardware/graphics/bufferqueue/1.0/IGraphicBufferProducer.h>
#include <android/hardware/graphics/bufferqueue/2.0/IGraphicBufferProducer.h>
+#include <optional>
+#include <vector>
+
namespace android {
// ----------------------------------------------------------------------------
@@ -289,8 +292,9 @@
const sp<GraphicBuffer>& buffer) = 0;
struct QueueBufferInput : public Flattenable<QueueBufferInput> {
- friend class Flattenable<QueueBufferInput>;
- explicit inline QueueBufferInput(const Parcel& parcel);
+ explicit inline QueueBufferInput(const Parcel& parcel) {
+ parcel.read(*this);
+ }
// timestamp - a monotonically increasing value in nanoseconds
// isAutoTimestamp - if the timestamp was synthesized at queue time
@@ -304,21 +308,29 @@
// camera mode).
// getFrameTimestamps - whether or not the latest frame timestamps
// should be retrieved from the consumer.
+ // slot - the slot index to queue. This is used only by queueBuffers().
+ // queueBuffer() ignores this value and uses the argument `slot`
+ // instead.
inline QueueBufferInput(int64_t _timestamp, bool _isAutoTimestamp,
android_dataspace _dataSpace, const Rect& _crop,
int _scalingMode, uint32_t _transform, const sp<Fence>& _fence,
- uint32_t _sticky = 0, bool _getFrameTimestamps = false)
+ uint32_t _sticky = 0, bool _getFrameTimestamps = false,
+ int _slot = -1)
: timestamp(_timestamp), isAutoTimestamp(_isAutoTimestamp),
dataSpace(_dataSpace), crop(_crop), scalingMode(_scalingMode),
- transform(_transform), stickyTransform(_sticky), fence(_fence),
- surfaceDamage(), getFrameTimestamps(_getFrameTimestamps) { }
+ transform(_transform), stickyTransform(_sticky),
+ fence(_fence), surfaceDamage(),
+ getFrameTimestamps(_getFrameTimestamps), slot(_slot) { }
+
+ QueueBufferInput() = default;
inline void deflate(int64_t* outTimestamp, bool* outIsAutoTimestamp,
android_dataspace* outDataSpace,
Rect* outCrop, int* outScalingMode,
uint32_t* outTransform, sp<Fence>* outFence,
uint32_t* outStickyTransform = nullptr,
- bool* outGetFrameTimestamps = nullptr) const {
+ bool* outGetFrameTimestamps = nullptr,
+ int* outSlot = nullptr) const {
*outTimestamp = timestamp;
*outIsAutoTimestamp = bool(isAutoTimestamp);
*outDataSpace = dataSpace;
@@ -332,6 +344,9 @@
if (outGetFrameTimestamps) {
*outGetFrameTimestamps = getFrameTimestamps;
}
+ if (outSlot) {
+ *outSlot = slot;
+ }
}
// Flattenable protocol
@@ -357,6 +372,7 @@
sp<Fence> fence;
Region surfaceDamage;
bool getFrameTimestamps{false};
+ int slot{-1};
HdrMetadata hdrMetadata;
};
@@ -385,6 +401,7 @@
FrameEventHistoryDelta frameTimestamps;
bool bufferReplaced{false};
int maxBufferCount{0};
+ status_t result{NO_ERROR};
};
// queueBuffer indicates that the client has finished filling in the
@@ -404,6 +421,10 @@
// Upon success, the output will be filled with meaningful values
// (refer to the documentation below).
//
+ // Note: QueueBufferInput::slot was added to QueueBufferInput to be used by
+ // queueBuffers(), the batched version of queueBuffer(). The non-batched
+ // method (queueBuffer()) uses `slot` and ignores `input.slot`.
+ //
// Return of a value other than NO_ERROR means an error has occurred:
// * NO_INIT - the buffer queue has been abandoned or the producer is not
// connected.
@@ -639,6 +660,147 @@
// the width and height used for dequeueBuffer will be additionally swapped.
virtual status_t setAutoPrerotation(bool autoPrerotation);
+ struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
+ RequestBufferOutput() = default;
+
+ // Flattenable protocol
+ static constexpr size_t minFlattenedSize();
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ status_t result;
+ sp<GraphicBuffer> buffer;
+ };
+
+ // Batched version of requestBuffer().
+ // This method behaves like a sequence of requestBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t requestBuffers(
+ const std::vector<int32_t>& slots,
+ std::vector<RequestBufferOutput>* outputs);
+
+ struct DequeueBufferInput : public LightFlattenable<DequeueBufferInput> {
+ DequeueBufferInput() = default;
+
+ // LightFlattenable protocol
+ inline bool isFixedSize() const { return true; }
+ size_t getFlattenedSize() const;
+ status_t flatten(void* buffer, size_t size) const;
+ status_t unflatten(void const* buffer, size_t size);
+
+ uint32_t width;
+ uint32_t height;
+ PixelFormat format;
+ uint64_t usage;
+ bool getTimestamps;
+ };
+
+ struct DequeueBufferOutput : public Flattenable<DequeueBufferOutput> {
+ DequeueBufferOutput() = default;
+
+ // Flattenable protocol
+ static constexpr size_t minFlattenedSize();
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ status_t result;
+ int slot = -1;
+ sp<Fence> fence = Fence::NO_FENCE;
+ uint64_t bufferAge;
+ std::optional<FrameEventHistoryDelta> timestamps;
+ };
+
+ // Batched version of dequeueBuffer().
+ // This method behaves like a sequence of dequeueBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t dequeueBuffers(
+ const std::vector<DequeueBufferInput>& inputs,
+ std::vector<DequeueBufferOutput>* outputs);
+
+ // Batched version of detachBuffer().
+ // This method behaves like a sequence of detachBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t detachBuffers(const std::vector<int32_t>& slots,
+ std::vector<status_t>* results);
+
+
+ struct AttachBufferOutput : public LightFlattenable<AttachBufferOutput> {
+ AttachBufferOutput() = default;
+
+ // LightFlattenable protocol
+ inline bool isFixedSize() const { return true; }
+ size_t getFlattenedSize() const;
+ status_t flatten(void* buffer, size_t size) const;
+ status_t unflatten(void const* buffer, size_t size);
+
+ status_t result;
+ int slot;
+ };
+ // Batched version of attachBuffer().
+ // This method behaves like a sequence of attachBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t attachBuffers(
+ const std::vector<sp<GraphicBuffer>>& buffers,
+ std::vector<AttachBufferOutput>* outputs);
+
+ // Batched version of queueBuffer().
+ // This method behaves like a sequence of queueBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ //
+ // Note: QueueBufferInput::slot was added to QueueBufferInput to include the
+ // `slot` input argument of the non-batched method queueBuffer().
+ virtual status_t queueBuffers(const std::vector<QueueBufferInput>& inputs,
+ std::vector<QueueBufferOutput>* outputs);
+
+ struct CancelBufferInput : public Flattenable<CancelBufferInput> {
+ CancelBufferInput() = default;
+
+ // Flattenable protocol
+ static constexpr size_t minFlattenedSize();
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ int slot;
+ sp<Fence> fence;
+ };
+ // Batched version of cancelBuffer().
+ // This method behaves like a sequence of cancelBuffer() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t cancelBuffers(
+ const std::vector<CancelBufferInput>& inputs,
+ std::vector<status_t>* results);
+
+ struct QueryOutput : public LightFlattenable<QueryOutput> {
+ QueryOutput() = default;
+
+ // LightFlattenable protocol
+ inline bool isFixedSize() const { return true; }
+ size_t getFlattenedSize() const;
+ status_t flatten(void* buffer, size_t size) const;
+ status_t unflatten(void const* buffer, size_t size);
+
+ status_t result;
+ int64_t value;
+ };
+ // Batched version of query().
+ // This method behaves like a sequence of query() calls.
+ // The return value of the batched method will only be about the
+ // transaction. For a local call, the return value will always be NO_ERROR.
+ virtual status_t query(const std::vector<int32_t> inputs,
+ std::vector<QueryOutput>* outputs);
+
#ifndef NO_BINDER
// Static method exports any IGraphicBufferProducer object to a parcel. It
// handles null producer as well.
diff --git a/libs/gui/include/gui/IScreenCaptureListener.h b/libs/gui/include/gui/IScreenCaptureListener.h
deleted file mode 100644
index a2ddc9f..0000000
--- a/libs/gui/include/gui/IScreenCaptureListener.h
+++ /dev/null
@@ -1,45 +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 <binder/Binder.h>
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-#include <binder/SafeInterface.h>
-
-namespace android {
-
-struct ScreenCaptureResults;
-
-// TODO(b/166271443): Convert to AIDL
-class IScreenCaptureListener : public IInterface {
-public:
- DECLARE_META_INTERFACE(ScreenCaptureListener)
-
- virtual status_t onScreenCaptureComplete(const ScreenCaptureResults& captureResults) = 0;
-};
-
-class BnScreenCaptureListener : public SafeBnInterface<IScreenCaptureListener> {
-public:
- BnScreenCaptureListener()
- : SafeBnInterface<IScreenCaptureListener>("BnScreenCaptureListener") {}
-
- status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags = 0) override;
-};
-
-} // namespace android
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 86e3a25..86f3c60 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -22,8 +22,8 @@
#include <binder/IBinder.h>
#include <binder/IInterface.h>
+#include <android/gui/IScreenCaptureListener.h>
#include <android/gui/ITransactionTraceListener.h>
-#include <gui/IScreenCaptureListener.h>
#include <gui/ITransactionCompletedListener.h>
#include <input/Flags.h>
@@ -59,7 +59,6 @@
struct DisplayState;
struct InputWindowCommands;
struct LayerCaptureArgs;
-struct ScreenCaptureResults;
class LayerDebugInfo;
class HdrCapabilities;
class IDisplayEventConnection;
@@ -69,6 +68,8 @@
class Rect;
enum class FrameEvent;
+using gui::IScreenCaptureListener;
+
namespace ui {
struct DisplayState;
@@ -166,7 +167,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 +505,11 @@
*/
virtual status_t addTransactionTraceListener(
const sp<gui::ITransactionTraceListener>& listener) = 0;
+
+ /**
+ * Gets priority of the RenderEngine in SurfaceFlinger.
+ */
+ virtual int getGPUContextPriority() = 0;
};
// ----------------------------------------------------------------------------
@@ -565,6 +571,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 c58634b..cb17cee 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -16,6 +16,8 @@
#pragma once
+#include "JankInfo.h"
+
#include <binder/IInterface.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
@@ -57,6 +59,26 @@
nsecs_t dequeueReadyTime;
};
+/**
+ * Jank information representing SurfaceFlinger's jank classification about frames for a specific
+ * surface.
+ */
+class JankData : public Parcelable {
+public:
+ status_t writeToParcel(Parcel* output) const override;
+ status_t readFromParcel(const Parcel* input) override;
+
+ JankData();
+ JankData(int64_t frameVsyncId, int32_t jankType)
+ : frameVsyncId(frameVsyncId), jankType(jankType) {}
+
+ // Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
+ int64_t frameVsyncId;
+
+ // Bitmask of janks that occurred
+ int32_t jankType;
+};
+
class SurfaceStats : public Parcelable {
public:
status_t writeToParcel(Parcel* output) const override;
@@ -64,18 +86,21 @@
SurfaceStats() = default;
SurfaceStats(const sp<IBinder>& sc, nsecs_t time, const sp<Fence>& prevReleaseFence,
- uint32_t hint, FrameEventHistoryStats frameEventStats)
+ uint32_t hint, FrameEventHistoryStats frameEventStats,
+ std::vector<JankData> jankData)
: surfaceControl(sc),
acquireTime(time),
previousReleaseFence(prevReleaseFence),
transformHint(hint),
- eventStats(frameEventStats) {}
+ eventStats(frameEventStats),
+ jankData(std::move(jankData)) {}
sp<IBinder> surfaceControl;
nsecs_t acquireTime = -1;
sp<Fence> previousReleaseFence;
uint32_t transformHint = 0;
FrameEventHistoryStats eventStats;
+ std::vector<JankData> jankData;
};
class TransactionStats : public Parcelable {
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
new file mode 100644
index 0000000..fc91714
--- /dev/null
+++ b/libs/gui/include/gui/JankInfo.h
@@ -0,0 +1,48 @@
+/*
+ * 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
+
+namespace android {
+
+// 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 that occurs in the layers below SurfaceFlinger
+ DisplayHAL = 0x1,
+ // SF took too long on the CPU
+ SurfaceFlingerCpuDeadlineMissed = 0x2,
+ // SF took too long on the GPU
+ SurfaceFlingerGpuDeadlineMissed = 0x4,
+ // Either App or GPU took too long on the frame
+ AppDeadlineMissed = 0x8,
+ // 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/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index ac48aef..41982c2 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -29,6 +29,7 @@
METADATA_MOUSE_CURSOR = 4,
METADATA_ACCESSIBILITY_ID = 5,
METADATA_OWNER_PID = 6,
+ METADATA_DEQUEUE_TIME = 7
};
struct LayerMetadata : public Parcelable {
@@ -51,6 +52,8 @@
bool has(uint32_t key) const;
int32_t getInt32(uint32_t key, int32_t fallback) const;
void setInt32(uint32_t key, int32_t value);
+ std::optional<int64_t> getInt64(uint32_t key) const;
+ void setInt64(uint32_t key, int64_t value);
std::string itemToString(uint32_t key, const char* separator) const;
};
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index f1c5d67..4a291ae 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -362,16 +362,6 @@
status_t read(const Parcel& input) override;
};
-struct ScreenCaptureResults {
- sp<GraphicBuffer> buffer;
- bool capturedSecureLayers{false};
- ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB};
- status_t result = OK;
-
- status_t write(Parcel& output) const;
- status_t read(const Parcel& input);
-};
-
}; // namespace android
#endif // ANDROID_SF_LAYER_STATE_H
diff --git a/libs/gui/include/gui/ScreenCaptureResults.h b/libs/gui/include/gui/ScreenCaptureResults.h
new file mode 100644
index 0000000..fdb4b69
--- /dev/null
+++ b/libs/gui/include/gui/ScreenCaptureResults.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define SAFE_PARCEL(FUNC, ...) \
+ { \
+ status_t error = FUNC(__VA_ARGS__); \
+ if (error) { \
+ ALOGE("ERROR(%d). Failed to call parcel %s(%s)", error, #FUNC, #__VA_ARGS__); \
+ return error; \
+ } \
+ }
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android::gui {
+
+struct ScreenCaptureResults : public Parcelable {
+public:
+ ScreenCaptureResults() = default;
+ virtual ~ScreenCaptureResults() = default;
+ status_t writeToParcel(android::Parcel* parcel) const override;
+ status_t readFromParcel(const android::Parcel* parcel) override;
+
+ sp<GraphicBuffer> buffer;
+ bool capturedSecureLayers{false};
+ ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB};
+ status_t result = OK;
+};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 82bc5c9..43b5dcd 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -344,6 +344,23 @@
static status_t attachAndQueueBufferWithDataspace(Surface* surface, sp<GraphicBuffer> buffer,
ui::Dataspace dataspace);
+ // Batch version of dequeueBuffer, cancelBuffer and queueBuffer
+ // Note that these batched operations are not supported when shared buffer mode is being used.
+ struct BatchBuffer {
+ ANativeWindowBuffer* buffer = nullptr;
+ int fenceFd = -1;
+ };
+ virtual int dequeueBuffers(std::vector<BatchBuffer>* buffers);
+ virtual int cancelBuffers(const std::vector<BatchBuffer>& buffers);
+
+ struct BatchQueuedBuffer {
+ ANativeWindowBuffer* buffer = nullptr;
+ int fenceFd = -1;
+ nsecs_t timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+ };
+ virtual int queueBuffers(
+ const std::vector<BatchQueuedBuffer>& buffers);
+
protected:
enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };
enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 };
@@ -373,6 +390,14 @@
void freeAllBuffers();
int getSlotFromBufferLocked(android_native_buffer_t* buffer) const;
+ void getDequeueBufferInputLocked(IGraphicBufferProducer::DequeueBufferInput* dequeueInput);
+
+ void getQueueBufferInputLocked(android_native_buffer_t* buffer, int fenceFd, nsecs_t timestamp,
+ IGraphicBufferProducer::QueueBufferInput* out);
+
+ void onBufferQueuedLocked(int slot, sp<Fence> fence,
+ const IGraphicBufferProducer::QueueBufferOutput& output);
+
struct BufferSlot {
sp<GraphicBuffer> buffer;
Region dirtyRegion;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2eb97f2..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);
@@ -619,6 +637,12 @@
// ---------------------------------------------------------------------------
+class JankDataListener : public VirtualLightRefBase {
+public:
+ virtual ~JankDataListener() = 0;
+ virtual void onJankDataAvailable(const std::vector<JankData>& jankData) = 0;
+};
+
class TransactionCompletedListener : public BnTransactionCompletedListener {
TransactionCompletedListener();
@@ -637,6 +661,7 @@
};
std::unordered_map<CallbackId, CallbackTranslation> mCallbacks GUARDED_BY(mMutex);
+ std::multimap<sp<IBinder>, sp<JankDataListener>> mJankListeners GUARDED_BY(mMutex);
public:
static sp<TransactionCompletedListener> getInstance();
@@ -652,6 +677,18 @@
void addSurfaceControlToCallbacks(const sp<SurfaceControl>& surfaceControl,
const std::unordered_set<CallbackId>& callbackIds);
+ /*
+ * Adds a jank listener to be informed about SurfaceFlinger's jank classification for a specific
+ * surface. Jank classifications arrive as part of the transaction callbacks about previous
+ * frames submitted to this Surface.
+ */
+ void addJankListener(const sp<JankDataListener>& listener, sp<SurfaceControl> surfaceControl);
+
+ /**
+ * Removes a jank listener previously added to addJankCallback.
+ */
+ void removeJankListener(const sp<JankDataListener>& listener);
+
// Overrides BnTransactionCompletedListener's onTransactionCompleted
void onTransactionCompleted(ListenerStats stats) override;
};
diff --git a/libs/gui/include/gui/SyncScreenCaptureListener.h b/libs/gui/include/gui/SyncScreenCaptureListener.h
index 2857996..8620b77 100644
--- a/libs/gui/include/gui/SyncScreenCaptureListener.h
+++ b/libs/gui/include/gui/SyncScreenCaptureListener.h
@@ -16,16 +16,19 @@
#pragma once
+#include <android/gui/BnScreenCaptureListener.h>
#include <gui/SurfaceComposerClient.h>
#include <future>
namespace android {
-class SyncScreenCaptureListener : public BnScreenCaptureListener {
+using gui::ScreenCaptureResults;
+
+struct SyncScreenCaptureListener : gui::BnScreenCaptureListener {
public:
- status_t onScreenCaptureComplete(const ScreenCaptureResults& captureResults) override {
+ binder::Status onScreenCaptureComplete(const ScreenCaptureResults& captureResults) override {
resultsPromise.set_value(captureResults);
- return NO_ERROR;
+ return binder::Status::ok();
}
ScreenCaptureResults waitForResults() {
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 c39b0b5..3965ea0 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -95,6 +95,15 @@
return std::make_unique<InputSurface>(surfaceControl, width, height);
}
+ static std::unique_ptr<InputSurface> makeBlastInputSurface(const sp<SurfaceComposerClient> &scc,
+ int width, int height) {
+ sp<SurfaceControl> surfaceControl =
+ scc->createSurface(String8("Test Buffer Surface"), width, height,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState);
+ return std::make_unique<InputSurface>(surfaceControl, width, height);
+ }
+
static std::unique_ptr<InputSurface> makeContainerInputSurface(
const sp<SurfaceComposerClient> &scc, int width, int height) {
sp<SurfaceControl> surfaceControl =
@@ -180,13 +189,13 @@
t.apply(true);
}
- void showAt(int x, int y) {
+ void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) {
SurfaceComposerClient::Transaction t;
t.show(mSurfaceControl);
t.setInputWindowInfo(mSurfaceControl, mInputInfo);
t.setLayer(mSurfaceControl, LAYER_BASE);
t.setPosition(mSurfaceControl, x, y);
- t.setCrop_legacy(mSurfaceControl, Rect(0, 0, 100, 100));
+ t.setCrop_legacy(mSurfaceControl, crop);
t.setAlpha(mSurfaceControl, 1);
t.apply(true);
}
@@ -629,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);
@@ -684,4 +696,34 @@
surface->expectTap(1, 10);
}
+TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_bql) {
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+
+ std::unique_ptr<InputSurface> bufferSurface =
+ InputSurface::makeBufferInputSurface(mComposerClient, 0, 0);
+ bufferSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+ bufferSurface->mInputInfo.ownerUid = 22222;
+
+ surface->showAt(10, 10);
+ bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
+
+ injectTap(11, 11);
+ surface->expectTap(1, 1);
+}
+
+TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_blast) {
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+
+ std::unique_ptr<InputSurface> bufferSurface =
+ InputSurface::makeBlastInputSurface(mComposerClient, 0, 0);
+ bufferSurface->mInputInfo.flags = InputWindowInfo::Flag::NOT_TOUCHABLE;
+ bufferSurface->mInputInfo.ownerUid = 22222;
+
+ surface->showAt(10, 10);
+ bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
+
+ injectTap(11, 11);
+ surface->expectTap(1, 1);
+}
+
} // namespace android::test
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index 15bd32d..2af2fe1 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -70,6 +70,9 @@
const int QUEUE_BUFFER_INPUT_SCALING_MODE = 0;
const int QUEUE_BUFFER_INPUT_TRANSFORM = 0;
const sp<Fence> QUEUE_BUFFER_INPUT_FENCE = Fence::NO_FENCE;
+ const uint32_t QUEUE_BUFFER_INPUT_STICKY_TRANSFORM = 0;
+ const bool QUEUE_BUFFER_INPUT_GET_TIMESTAMPS = 0;
+ const int QUEUE_BUFFER_INPUT_SLOT = -1;
// Enums to control which IGraphicBufferProducer backend to test.
enum IGraphicBufferProducerTestCode {
@@ -156,6 +159,9 @@
scalingMode = QUEUE_BUFFER_INPUT_SCALING_MODE;
transform = QUEUE_BUFFER_INPUT_TRANSFORM;
fence = QUEUE_BUFFER_INPUT_FENCE;
+ stickyTransform = QUEUE_BUFFER_INPUT_STICKY_TRANSFORM;
+ getTimestamps = QUEUE_BUFFER_INPUT_GET_TIMESTAMPS;
+ slot = QUEUE_BUFFER_INPUT_SLOT;
}
IGraphicBufferProducer::QueueBufferInput build() {
@@ -166,7 +172,10 @@
crop,
scalingMode,
transform,
- fence);
+ fence,
+ stickyTransform,
+ getTimestamps,
+ slot);
}
QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
@@ -204,6 +213,21 @@
return *this;
}
+ QueueBufferInputBuilder& setStickyTransform(uint32_t stickyTransform) {
+ this->stickyTransform = stickyTransform;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setGetTimestamps(bool getTimestamps) {
+ this->getTimestamps = getTimestamps;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setSlot(int slot) {
+ this->slot = slot;
+ return *this;
+ }
+
private:
int64_t timestamp;
bool isAutoTimestamp;
@@ -212,17 +236,17 @@
int scalingMode;
uint32_t transform;
sp<Fence> fence;
+ uint32_t stickyTransform;
+ bool getTimestamps;
+ int slot;
}; // struct QueueBufferInputBuilder
- // To easily store dequeueBuffer results into containers
- struct DequeueBufferResult {
- int slot;
- sp<Fence> fence;
- };
-
- status_t dequeueBuffer(uint32_t w, uint32_t h, uint32_t format, uint32_t usage, DequeueBufferResult* result) {
- return mProducer->dequeueBuffer(&result->slot, &result->fence, w, h, format, usage,
- nullptr, nullptr);
+ status_t dequeueBuffer(uint32_t w, uint32_t h, uint32_t format, uint32_t usage,
+ IGraphicBufferProducer::DequeueBufferOutput* result) {
+ result->result =
+ mProducer->dequeueBuffer(&result->slot, &result->fence, w, h, format, usage,
+ &result->bufferAge, nullptr);
+ return result->result;
}
void setupDequeueRequestBuffer(int *slot, sp<Fence> *fence,
@@ -336,6 +360,27 @@
EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, value);
+ { // Test the batched version
+ std::vector<int32_t> inputs = {
+ NATIVE_WINDOW_WIDTH,
+ NATIVE_WINDOW_HEIGHT,
+ NATIVE_WINDOW_FORMAT,
+ NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND,
+ NATIVE_WINDOW_CONSUMER_USAGE_BITS };
+ using QueryOutput = IGraphicBufferProducer::QueryOutput;
+ std::vector<QueryOutput> outputs;
+ EXPECT_OK(mProducer->query(inputs, &outputs));
+ EXPECT_EQ(DEFAULT_WIDTH, static_cast<uint32_t>(outputs[0].value));
+ EXPECT_EQ(DEFAULT_HEIGHT, static_cast<uint32_t>(outputs[1].value));
+ EXPECT_EQ(DEFAULT_FORMAT, outputs[2].value);
+ EXPECT_LE(0, outputs[3].value);
+ EXPECT_FALSE(outputs[4].value);
+ EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, outputs[5].value);
+ for (const QueryOutput& output : outputs) {
+ EXPECT_OK(output.result);
+ }
+ }
}
TEST_P(IGraphicBufferProducerTest, Query_ReturnsError) {
@@ -358,6 +403,24 @@
EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));
// TODO: Consider documented the above enums as unsupported or make a new enum for IGBP
+ { // Test the batched version
+ std::vector<int32_t> inputs = {
+ -1,
+ static_cast<int32_t>(0xDEADBEEF),
+ NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE,
+ NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER,
+ NATIVE_WINDOW_CONCRETE_TYPE,
+ NATIVE_WINDOW_DEFAULT_WIDTH,
+ NATIVE_WINDOW_DEFAULT_HEIGHT,
+ NATIVE_WINDOW_TRANSFORM_HINT};
+ using QueryOutput = IGraphicBufferProducer::QueryOutput;
+ std::vector<QueryOutput> outputs;
+ EXPECT_OK(mProducer->query(inputs, &outputs));
+ for (const QueryOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
+
// Value was NULL
EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/nullptr));
@@ -416,29 +479,113 @@
// Buffer was not in the dequeued state
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test batched methods
+ constexpr size_t BATCH_SIZE = 4;
+
+ ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+ // Dequeue
+ using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+ using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+ DequeueBufferInput dequeueInput;
+ dequeueInput.width = DEFAULT_WIDTH;
+ dequeueInput.height = DEFAULT_HEIGHT;
+ dequeueInput.format = DEFAULT_FORMAT;
+ dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+ dequeueInput.getTimestamps = false;
+ std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+ std::vector<DequeueBufferOutput> dequeueOutputs;
+ EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+ ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+ // Request
+ std::vector<int32_t> requestInputs;
+ requestInputs.reserve(BATCH_SIZE);
+ for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+ ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+ dequeueOutput.result);
+ requestInputs.emplace_back(dequeueOutput.slot);
+ }
+ using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+ std::vector<RequestBufferOutput> requestOutputs;
+ EXPECT_OK(mProducer->requestBuffers(requestInputs, &requestOutputs));
+ ASSERT_EQ(requestInputs.size(), requestOutputs.size());
+ for (const RequestBufferOutput& requestOutput : requestOutputs) {
+ EXPECT_OK(requestOutput.result);
+ }
+
+ // Queue
+ using QueueBufferInput = IGraphicBufferProducer::QueueBufferInput;
+ using QueueBufferOutput = IGraphicBufferProducer::QueueBufferOutput;
+ std::vector<QueueBufferInput> queueInputs;
+ queueInputs.reserve(BATCH_SIZE);
+ for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+ queueInputs.emplace_back(CreateBufferInput()).slot =
+ dequeueOutput.slot;
+ }
+ std::vector<QueueBufferOutput> queueOutputs;
+ EXPECT_OK(mProducer->queueBuffers(queueInputs, &queueOutputs));
+ ASSERT_EQ(queueInputs.size(), queueOutputs.size());
+ for (const QueueBufferOutput& queueOutput : queueOutputs) {
+ EXPECT_OK(queueOutput.result);
+ }
+
+ // Re-queue
+ EXPECT_OK(mProducer->queueBuffers(queueInputs, &queueOutputs));
+ ASSERT_EQ(queueInputs.size(), queueOutputs.size());
+ for (const QueueBufferOutput& queueOutput : queueOutputs) {
+ EXPECT_EQ(BAD_VALUE, queueOutput.result);
+ }
+ }
}
TEST_P(IGraphicBufferProducerTest, Queue_ReturnsError) {
ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+ using QueueBufferInput = IGraphicBufferProducer::QueueBufferInput;
+ using QueueBufferOutput = IGraphicBufferProducer::QueueBufferOutput;
// Invalid slot number
{
// A generic "valid" input
- IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
- IGraphicBufferProducer::QueueBufferOutput output;
+ QueueBufferInput input = CreateBufferInput();
+ QueueBufferOutput output;
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/-1, input, &output));
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0xDEADBEEF, input, &output));
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueue::NUM_BUFFER_SLOTS,
input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = -1;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Slot was not in the dequeued state (all slots start out in Free state)
{
- IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
- IGraphicBufferProducer::QueueBufferOutput output;
+ QueueBufferInput input = CreateBufferInput();
+ QueueBufferOutput output;
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = 0;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Put the slot into the "dequeued" state for the rest of the test
@@ -453,10 +600,22 @@
// Slot was enqueued without requesting a buffer
{
- IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
- IGraphicBufferProducer::QueueBufferOutput output;
+ QueueBufferInput input = CreateBufferInput();
+ QueueBufferOutput output;
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Request the buffer so that the rest of the tests don't fail on earlier checks.
@@ -467,11 +626,23 @@
{
sp<Fence> nullFence = nullptr;
- IGraphicBufferProducer::QueueBufferInput input =
+ QueueBufferInput input =
QueueBufferInputBuilder().setFence(nullFence).build();
- IGraphicBufferProducer::QueueBufferOutput output;
+ QueueBufferOutput output;
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Scaling mode was unknown
@@ -482,9 +653,33 @@
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
+
input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Crop rect is out of bounds of the buffer dimensions
@@ -495,6 +690,18 @@
IGraphicBufferProducer::QueueBufferOutput output;
EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(BAD_VALUE, output.result);
+ }
+ }
}
// Abandon the buffer queue so that the last test fails
@@ -507,6 +714,18 @@
// TODO(b/73267953): Make BufferHub honor producer and consumer connection.
EXPECT_EQ(NO_INIT, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ { // Test with the batched version
+ constexpr size_t BATCH_SIZE = 16;
+ input.slot = dequeuedSlot;
+ std::vector<QueueBufferInput> inputs(BATCH_SIZE, input);
+ std::vector<QueueBufferOutput> outputs;
+ EXPECT_OK(mProducer->queueBuffers(inputs, &outputs));
+ ASSERT_EQ(inputs.size(), outputs.size());
+ for (const QueueBufferOutput& output : outputs) {
+ EXPECT_EQ(NO_INIT, output.result);
+ }
+ }
}
}
@@ -525,6 +744,44 @@
// No return code, but at least test that it doesn't blow up...
// TODO: add a return code
mProducer->cancelBuffer(dequeuedSlot, dequeuedFence);
+
+ { // Test batched methods
+ constexpr size_t BATCH_SIZE = 4;
+ ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+
+ // Dequeue
+ using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+ using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+ DequeueBufferInput dequeueInput;
+ dequeueInput.width = DEFAULT_WIDTH;
+ dequeueInput.height = DEFAULT_HEIGHT;
+ dequeueInput.format = DEFAULT_FORMAT;
+ dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+ dequeueInput.getTimestamps = false;
+ std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+ std::vector<DequeueBufferOutput> dequeueOutputs;
+ EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+ ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+ // Cancel
+ using CancelBufferInput = IGraphicBufferProducer::CancelBufferInput;
+ std::vector<CancelBufferInput> cancelInputs;
+ cancelInputs.reserve(BATCH_SIZE);
+ for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+ ASSERT_EQ(OK,
+ ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+ dequeueOutput.result);
+ CancelBufferInput& cancelInput = cancelInputs.emplace_back();
+ cancelInput.slot = dequeueOutput.slot;
+ cancelInput.fence = dequeueOutput.fence;
+ }
+ std::vector<status_t> cancelOutputs;
+ EXPECT_OK(mProducer->cancelBuffers(cancelInputs, &cancelOutputs));
+ ASSERT_EQ(cancelInputs.size(), cancelOutputs.size());
+ for (status_t result : cancelOutputs) {
+ EXPECT_OK(result);
+ }
+ }
}
TEST_P(IGraphicBufferProducerTest, SetMaxDequeuedBufferCount_Succeeds) {
@@ -541,11 +798,11 @@
<< "bufferCount: " << minBuffers;
// Should now be able to dequeue up to minBuffers times
- DequeueBufferResult result;
+ IGraphicBufferProducer::DequeueBufferOutput result;
for (int i = 0; i < minBuffers; ++i) {
EXPECT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
(dequeueBuffer(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
- TEST_PRODUCER_USAGE_BITS, &result)))
+ TEST_PRODUCER_USAGE_BITS, &result)))
<< "iteration: " << i << ", slot: " << result.slot;
}
@@ -558,7 +815,6 @@
ASSERT_OK(mProducer->requestBuffer(result.slot, &buffer));
ASSERT_OK(mProducer->queueBuffer(result.slot, input, &output));
-
// Should now be able to dequeue up to maxBuffers times
int dequeuedSlot = -1;
sp<Fence> dequeuedFence;
@@ -794,6 +1050,71 @@
EXPECT_OK(mProducer->attachBuffer(&slot, buffer));
EXPECT_OK(buffer->initCheck());
+
+ ASSERT_OK(mProducer->detachBuffer(slot));
+
+ { // Test batched methods
+ constexpr size_t BATCH_SIZE = 4;
+ ASSERT_OK(mProducer->setMaxDequeuedBufferCount(BATCH_SIZE));
+
+ // Dequeue
+ using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
+ using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
+ DequeueBufferInput dequeueInput;
+ dequeueInput.width = DEFAULT_WIDTH;
+ dequeueInput.height = DEFAULT_HEIGHT;
+ dequeueInput.format = DEFAULT_FORMAT;
+ dequeueInput.usage = TEST_PRODUCER_USAGE_BITS;
+ dequeueInput.getTimestamps = false;
+ std::vector<DequeueBufferInput> dequeueInputs(BATCH_SIZE, dequeueInput);
+ std::vector<DequeueBufferOutput> dequeueOutputs;
+ EXPECT_OK(mProducer->dequeueBuffers(dequeueInputs, &dequeueOutputs));
+ ASSERT_EQ(dequeueInputs.size(), dequeueOutputs.size());
+
+ // Request
+ std::vector<int32_t> requestInputs;
+ requestInputs.reserve(BATCH_SIZE);
+ for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+ ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
+ dequeueOutput.result);
+ requestInputs.emplace_back(dequeueOutput.slot);
+ }
+ using RequestBufferOutput = IGraphicBufferProducer::RequestBufferOutput;
+ std::vector<RequestBufferOutput> requestOutputs;
+ EXPECT_OK(mProducer->requestBuffers(requestInputs, &requestOutputs));
+ ASSERT_EQ(requestInputs.size(), requestOutputs.size());
+ for (const RequestBufferOutput& requestOutput : requestOutputs) {
+ EXPECT_OK(requestOutput.result);
+ }
+
+ // Detach
+ std::vector<int32_t> detachInputs;
+ detachInputs.reserve(BATCH_SIZE);
+ for (const DequeueBufferOutput& dequeueOutput : dequeueOutputs) {
+ detachInputs.emplace_back(dequeueOutput.slot);
+ }
+ std::vector<status_t> detachOutputs;
+ EXPECT_OK(mProducer->detachBuffers(detachInputs, &detachOutputs));
+ ASSERT_EQ(detachInputs.size(), detachOutputs.size());
+ for (status_t result : detachOutputs) {
+ EXPECT_OK(result);
+ }
+
+ // Attach
+ using AttachBufferOutput = IGraphicBufferProducer::AttachBufferOutput;
+ std::vector<sp<GraphicBuffer>> attachInputs;
+ attachInputs.reserve(BATCH_SIZE);
+ for (const RequestBufferOutput& requestOutput : requestOutputs) {
+ attachInputs.emplace_back(requestOutput.buffer);
+ }
+ std::vector<AttachBufferOutput> attachOutputs;
+ EXPECT_OK(mProducer->attachBuffers(attachInputs, &attachOutputs));
+ ASSERT_EQ(attachInputs.size(), attachOutputs.size());
+ for (const AttachBufferOutput& attachOutput : attachOutputs) {
+ EXPECT_OK(attachOutput.result);
+ EXPECT_NE(-1, attachOutput.slot);
+ }
+ }
}
#if USE_BUFFER_HUB_AS_BUFFER_QUEUE
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index ce3afa2..63db9a7 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; }
@@ -2022,4 +2024,86 @@
EXPECT_EQ(BufferQueueDefs::NUM_BUFFER_SLOTS, count);
}
+TEST_F(SurfaceTest, BatchOperations) {
+ const int BUFFER_COUNT = 16;
+ const int BATCH_SIZE = 8;
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
+ sp<Surface> surface = new Surface(producer);
+ sp<ANativeWindow> window(surface);
+ sp<StubProducerListener> listener = new StubProducerListener();
+
+ ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
+ /*reportBufferRemoval*/false));
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+
+ std::vector<Surface::BatchBuffer> buffers(BATCH_SIZE);
+
+ // Batch dequeued buffers can be queued individually
+ ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+ for (size_t i = 0; i < BATCH_SIZE; i++) {
+ ANativeWindowBuffer* buffer = buffers[i].buffer;
+ int fence = buffers[i].fenceFd;
+ ASSERT_EQ(NO_ERROR, window->queueBuffer(window.get(), buffer, fence));
+ }
+
+ // Batch dequeued buffers can be canceled individually
+ ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+ for (size_t i = 0; i < BATCH_SIZE; i++) {
+ ANativeWindowBuffer* buffer = buffers[i].buffer;
+ int fence = buffers[i].fenceFd;
+ ASSERT_EQ(NO_ERROR, window->cancelBuffer(window.get(), buffer, fence));
+ }
+
+ // Batch dequeued buffers can be batch cancelled
+ ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+ ASSERT_EQ(NO_ERROR, surface->cancelBuffers(buffers));
+
+ // Batch dequeued buffers can be batch queued
+ ASSERT_EQ(NO_ERROR, surface->dequeueBuffers(&buffers));
+ std::vector<Surface::BatchQueuedBuffer> queuedBuffers(BATCH_SIZE);
+ for (size_t i = 0; i < BATCH_SIZE; i++) {
+ queuedBuffers[i].buffer = buffers[i].buffer;
+ queuedBuffers[i].fenceFd = buffers[i].fenceFd;
+ queuedBuffers[i].timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+ }
+ ASSERT_EQ(NO_ERROR, surface->queueBuffers(queuedBuffers));
+
+ ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
+}
+
+TEST_F(SurfaceTest, BatchIllegalOperations) {
+ const int BUFFER_COUNT = 16;
+ const int BATCH_SIZE = 8;
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
+ sp<Surface> surface = new Surface(producer);
+ sp<ANativeWindow> window(surface);
+ sp<StubProducerListener> listener = new StubProducerListener();
+
+ ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
+ /*reportBufferRemoval*/false));
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+
+ std::vector<Surface::BatchBuffer> buffers(BATCH_SIZE);
+ std::vector<Surface::BatchQueuedBuffer> queuedBuffers(BATCH_SIZE);
+
+ // Batch operations are invalid in shared buffer mode
+ surface->setSharedBufferMode(true);
+ ASSERT_EQ(INVALID_OPERATION, surface->dequeueBuffers(&buffers));
+ ASSERT_EQ(INVALID_OPERATION, surface->cancelBuffers(buffers));
+ ASSERT_EQ(INVALID_OPERATION, surface->queueBuffers(queuedBuffers));
+ surface->setSharedBufferMode(false);
+
+ ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
+}
+
} // namespace android
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/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 82c220f..6316b59 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -21,4 +21,11 @@
interface IInputConstants
{
const int DEFAULT_DISPATCHING_TIMEOUT_MILLIS = 5000; // 5 seconds
+
+ // Compatibility changes.
+ /**
+ * TODO(b/157929241): remove this before closing the bug. This is needed temporarily
+ * to identify apps that are using this flag.
+ */
+ const long BLOCK_FLAG_SLIPPERY = 157929241;
}
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/Android.bp b/libs/renderengine/Android.bp
index b878150..0e74c63 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -81,6 +81,9 @@
"skia/AutoBackendTexture.cpp",
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
+ "skia/debug/CaptureTimer.cpp",
+ "skia/debug/CommonPool.cpp",
+ "skia/debug/SkiaCapture.cpp",
"skia/filters/BlurFilter.cpp",
"skia/filters/LinearEffect.cpp",
],
@@ -94,6 +97,7 @@
cflags: [
"-fvisibility=hidden",
"-Werror=format",
+ "-Wno-unused-parameter",
],
srcs: [
":librenderengine_sources",
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 3e65d9a..45db31c 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -41,6 +41,9 @@
if (strcmp(prop, "skiagl") == 0) {
renderEngineType = RenderEngineType::SKIA_GL;
}
+ if (strcmp(prop, "skiaglthreaded") == 0) {
+ renderEngineType = RenderEngineType::SKIA_GL_THREADED;
+ }
switch (renderEngineType) {
case RenderEngineType::THREADED:
@@ -49,6 +52,10 @@
[args]() { return android::renderengine::gl::GLESRenderEngine::create(args); });
case RenderEngineType::SKIA_GL:
return renderengine::skia::SkiaGLRenderEngine::create(args);
+ case RenderEngineType::SKIA_GL_THREADED:
+ return renderengine::threaded::RenderEngineThreaded::create([args]() {
+ return android::renderengine::skia::SkiaGLRenderEngine::create(args);
+ });
case RenderEngineType::GLES:
default:
ALOGD("RenderEngine with GLES Backend");
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 11b8e44..506f81e 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -31,10 +31,17 @@
#include <ui/Transform.h>
/**
- * Allows to set RenderEngine backend to GLES (default) or Vulkan (NOT yet supported).
+ * Allows to set RenderEngine backend to GLES (default) or SkiaGL (NOT yet supported).
*/
#define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
+/**
+ * Turns on recording of skia commands in SkiaGL version of the RE. This property
+ * defines number of milliseconds for the recording to take place. A non zero value
+ * turns on the recording.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS "debug.renderengine.capture_skia_ms"
+
struct ANativeWindowBuffer;
namespace android {
@@ -68,12 +75,14 @@
LOW = 1,
MEDIUM = 2,
HIGH = 3,
+ REALTIME = 4,
};
enum class RenderEngineType {
GLES = 1,
THREADED = 2,
SKIA_GL = 3,
+ SKIA_GL_THREADED = 4,
};
static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
@@ -174,6 +183,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/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index d126c27..2ffb547 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -72,6 +72,7 @@
AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
bool isRender) {
+ ATRACE_CALL();
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index a0660fb..d9495a9 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -15,27 +15,14 @@
*/
//#define LOG_NDEBUG 0
-#include <cstdint>
-#include <memory>
-
-#include "SkImageInfo.h"
-#include "log/log_main.h"
-#include "system/graphics-base-v1.0.h"
#undef LOG_TAG
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include "SkiaGLRenderEngine.h"
+
#include <EGL/egl.h>
#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <sync/sync.h>
-#include <ui/BlurRegion.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/Trace.h>
-#include "../gl/GLExtensions.h"
-#include "SkiaGLRenderEngine.h"
-#include "filters/BlurFilter.h"
-
#include <GrContextOptions.h>
#include <SkCanvas.h>
#include <SkColorFilter.h>
@@ -47,17 +34,22 @@
#include <SkSurface.h>
#include <gl/GrGLInterface.h>
#include <sync/sync.h>
+#include <ui/BlurRegion.h>
#include <ui/GraphicBuffer.h>
#include <utils/Trace.h>
#include <cmath>
+#include <cstdint>
+#include <memory>
#include "../gl/GLExtensions.h"
-#include "SkiaGLRenderEngine.h"
+#include "SkBlendMode.h"
+#include "SkImageInfo.h"
#include "filters/BlurFilter.h"
#include "filters/LinearEffect.h"
-
-extern "C" EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
+#include "log/log_main.h"
+#include "skia/debug/SkiaCapture.h"
+#include "system/graphics-base-v1.0.h"
bool checkGlError(const char* op, int lineNumber);
@@ -155,16 +147,16 @@
LOG_ALWAYS_FATAL("failed to initialize EGL");
}
- const auto eglVersion = eglQueryStringImplementationANDROID(display, EGL_VERSION);
+ const auto eglVersion = eglQueryString(display, EGL_VERSION);
if (!eglVersion) {
checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryStringImplementationANDROID(EGL_VERSION) failed");
+ LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
}
- const auto eglExtensions = eglQueryStringImplementationANDROID(display, EGL_EXTENSIONS);
+ const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
if (!eglExtensions) {
checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryStringImplementationANDROID(EGL_EXTENSIONS) failed");
+ LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
}
auto& extensions = gl::GLExtensions::getInstance();
@@ -177,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");
@@ -427,6 +418,32 @@
mProtectedTextureCache.erase(bufferId);
}
+sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> shader,
+ const LayerSettings* layer,
+ const DisplaySettings& display,
+ bool undoPremultipliedAlpha) {
+ if (mUseColorManagement &&
+ needsLinearEffect(layer->colorTransform, layer->sourceDataspace, display.outputDataspace)) {
+ LinearEffect effect = LinearEffect{.inputDataspace = layer->sourceDataspace,
+ .outputDataspace = display.outputDataspace,
+ .undoPremultipliedAlpha = undoPremultipliedAlpha};
+
+ auto effectIter = mRuntimeEffects.find(effect);
+ sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
+ if (effectIter == mRuntimeEffects.end()) {
+ runtimeEffect = buildRuntimeEffect(effect);
+ mRuntimeEffects.insert({effect, runtimeEffect});
+ } else {
+ runtimeEffect = effectIter->second;
+ }
+ return createLinearEffectShader(shader, effect, runtimeEffect, layer->colorTransform,
+ display.maxLuminance,
+ layer->source.buffer.maxMasteringLuminance,
+ layer->source.buffer.maxContentLuminance);
+ }
+ return shader;
+}
+
status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
const std::vector<const LayerSettings*>& layers,
const sp<GraphicBuffer>& buffer,
@@ -484,18 +501,30 @@
: ui::Dataspace::SRGB,
mGrContext.get());
- auto canvas = surface->getCanvas();
+ 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.
canvas->clipRect(getSkRect(display.physicalDisplay));
- SkMatrix screenTransform;
- screenTransform.setTranslate(display.physicalDisplay.left, display.physicalDisplay.top);
+ canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);
const auto clipWidth = display.clip.width();
const auto clipHeight = display.clip.height();
@@ -509,31 +538,42 @@
static_cast<SkScalar>(rotatedClipWidth);
const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
static_cast<SkScalar>(rotatedClipHeight);
- screenTransform.preScale(scaleX, scaleY);
+ canvas->scale(scaleX, scaleY);
// Canvas rotation is done by centering the clip window at the origin, rotating, translating
// back so that the top left corner of the clip is at (0, 0).
- screenTransform.preTranslate(rotatedClipWidth / 2, rotatedClipHeight / 2);
- screenTransform.preRotate(toDegrees(display.orientation));
- screenTransform.preTranslate(-clipWidth / 2, -clipHeight / 2);
- screenTransform.preTranslate(-display.clip.left, -display.clip.top);
+ canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
+ canvas->rotate(toDegrees(display.orientation));
+ canvas->translate(-clipWidth / 2, -clipHeight / 2);
+ canvas->translate(-display.clip.left, -display.clip.top);
for (const auto& layer : layers) {
- const SkMatrix drawTransform = getDrawTransform(layer, screenTransform);
+ 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());
SkPaint paint;
const auto& bounds = layer->geometry.boundaries;
const auto dest = getSkRect(bounds);
+ const auto layerRect = canvas->getTotalMatrix().mapRect(dest);
std::unordered_map<uint32_t, sk_sp<SkSurface>> cachedBlurs;
-
if (mBlurFilter) {
- const auto layerRect = drawTransform.mapRect(dest);
if (layer->backgroundBlurRadius > 0) {
ATRACE_NAME("BackgroundBlur");
auto blurredSurface = mBlurFilter->generate(canvas, surface,
layer->backgroundBlurRadius, layerRect);
cachedBlurs[layer->backgroundBlurRadius] = blurredSurface;
- drawBlurRegion(canvas, getBlurRegion(layer), drawTransform, blurredSurface);
+ drawBlurRegion(canvas, getBlurRegion(layer), layerRect, blurredSurface);
}
if (layer->blurRegions.size() > 0) {
for (auto region : layer->blurRegions) {
@@ -551,8 +591,6 @@
if (layer->source.buffer.buffer) {
ATRACE_NAME("DrawImage");
const auto& item = layer->source.buffer;
- const auto bufferWidth = item.buffer->getBounds().width();
- const auto bufferHeight = item.buffer->getBounds().height();
std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
auto iter = mTextureCache.find(item.buffer->getId());
if (iter != mTextureCache.end()) {
@@ -584,50 +622,23 @@
? kPremul_SkAlphaType
: kUnpremul_SkAlphaType),
mGrContext.get());
- SkMatrix matrix;
- if (layer->geometry.roundedCornersRadius > 0) {
- const auto roundedRect = getRoundedRect(layer);
- matrix.setTranslate(roundedRect.getBounds().left() - dest.left(),
- roundedRect.getBounds().top() - dest.top());
- } else {
- matrix.setIdentity();
- }
auto texMatrix = getSkM44(item.textureTransform).asM33();
-
- // b/171404534, scale to fix the layer
- matrix.postScale(bounds.getWidth() / bufferWidth, bounds.getHeight() / bufferHeight);
-
// textureTansform was intended to be passed directly into a shader, so when
// building the total matrix with the textureTransform we need to first
// normalize it, then apply the textureTransform, then scale back up.
- matrix.postScale(1.0f / bufferWidth, 1.0f / bufferHeight);
+ texMatrix.preScale(1.0f / bounds.getWidth(), 1.0f / bounds.getHeight());
+ texMatrix.postScale(image->width(), image->height());
- auto rotatedBufferWidth = bufferWidth;
- auto rotatedBufferHeight = bufferHeight;
-
- // Swap the buffer width and height if we're rotating, so that we
- // scale back up by the correct factors post-rotation.
- if (texMatrix.getSkewX() <= -0.5f || texMatrix.getSkewX() >= 0.5f) {
- std::swap(rotatedBufferWidth, rotatedBufferHeight);
- // TODO: clean this up.
- // GLESRenderEngine specifies its texture coordinates in
- // CW orientation under OpenGL conventions, when they probably should have
- // been CCW instead. The net result is that orientation
- // transforms are applied in the reverse
- // direction to render the correct result, because SurfaceFlinger uses the inverse
- // of the display transform to correct for that. But this means that
- // the tex transform passed by SkiaGLRenderEngine will rotate
- // individual layers in the reverse orientation. Hack around it
- // by injected a 180 degree rotation, but ultimately this is
- // a bug in how SurfaceFlinger invokes the RenderEngine
- // interface, so the proper fix should live there, and GLESRenderEngine
- // should be fixed accordingly.
- matrix.postRotate(180, 0.5, 0.5);
+ SkMatrix matrix;
+ if (!texMatrix.invert(&matrix)) {
+ matrix = texMatrix;
}
+ // The shader does not respect the translation, so we add it to the texture
+ // transform for the SkImage. This will make sure that the correct layer contents
+ // are drawn in the correct part of the screen.
+ matrix.postTranslate(layer->geometry.boundaries.left, layer->geometry.boundaries.top);
- matrix.postConcat(texMatrix);
- matrix.postScale(rotatedBufferWidth, rotatedBufferHeight);
sk_sp<SkShader> shader;
if (layer->source.buffer.useTextureFiltering) {
@@ -636,75 +647,85 @@
{SkFilterMode::kLinear, SkMipmapMode::kNone}),
&matrix);
} else {
- shader = image->makeShader(matrix);
+ shader = image->makeShader(SkSamplingOptions(), matrix);
}
- if (mUseColorManagement &&
- needsLinearEffect(layer->colorTransform, layer->sourceDataspace,
- display.outputDataspace)) {
- LinearEffect effect = LinearEffect{.inputDataspace = layer->sourceDataspace,
- .outputDataspace = display.outputDataspace,
- .undoPremultipliedAlpha = !item.isOpaque &&
- item.usePremultipliedAlpha};
+ paint.setShader(
+ createRuntimeEffectShader(shader, layer, display,
+ !item.isOpaque && item.usePremultipliedAlpha));
- auto effectIter = mRuntimeEffects.find(effect);
- sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
- if (effectIter == mRuntimeEffects.end()) {
- runtimeEffect = buildRuntimeEffect(effect);
- mRuntimeEffects.insert({effect, runtimeEffect});
- } else {
- runtimeEffect = effectIter->second;
- }
-
- paint.setShader(createLinearEffectShader(shader, effect, runtimeEffect,
- layer->colorTransform,
- display.maxLuminance,
- layer->source.buffer.maxMasteringLuminance,
- layer->source.buffer.maxContentLuminance));
- } else {
- paint.setShader(shader);
- }
// Make sure to take into the account the alpha set on the layer.
paint.setAlphaf(layer->alpha);
} else {
ATRACE_NAME("DrawColor");
const auto color = layer->source.solidColor;
- paint.setShader(SkShaders::Color(SkColor4f{.fR = color.r,
- .fG = color.g,
- .fB = color.b,
- layer->alpha},
- nullptr));
+ sk_sp<SkShader> shader = SkShaders::Color(SkColor4f{.fR = color.r,
+ .fG = color.g,
+ .fB = color.b,
+ layer->alpha},
+ nullptr);
+ paint.setShader(createRuntimeEffectShader(shader, layer, display,
+ /* undoPremultipliedAlpha */ false));
}
- paint.setColorFilter(SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform)));
+ sk_sp<SkColorFilter> filter =
+ SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
+
+ // Handle opaque images - it's a little nonstandard how we do this.
+ // Fundamentally we need to support SurfaceControl.Builder#setOpaque:
+ // https://developer.android.com/reference/android/view/SurfaceControl.Builder#setOpaque(boolean)
+ // The important language is that when isOpaque is set, opacity is not sampled from the
+ // alpha channel, but blending may still be supported on a transaction via setAlpha. So,
+ // here's the conundrum:
+ // 1. We can't force the SkImage alpha type to kOpaque_SkAlphaType, because it's treated as
+ // an internal hint - composition is undefined when there are alpha bits present.
+ // 2. We can try to lie about the pixel layout, but that only works for RGBA8888 buffers,
+ // i.e., treating them as RGBx8888 instead. But we can't do the same for RGBA1010102 because
+ // RGBx1010102 is not supported as a pixel layout for SkImages. It's also not clear what to
+ // use for F16 either, and lying about the pixel layout is a bit of a hack anyways.
+ // 3. We can't change the blendmode to src, because while this satisfies the requirement for
+ // ignoring the alpha channel, it doesn't quite satisfy the blending requirement because
+ // src always clobbers the destination content.
+ //
+ // So, what we do here instead is an additive blend mode where we compose the input image
+ // with a solid black. This might need to be reassess if this does not support FP16
+ // incredibly well, but FP16 end-to-end isn't well supported anyway at the moment.
+ if (layer->source.buffer.buffer && layer->source.buffer.isOpaque) {
+ filter = SkColorFilters::Compose(filter,
+ SkColorFilters::Blend(SK_ColorBLACK,
+ SkBlendMode::kPlus));
+ }
+
+ paint.setColorFilter(filter);
for (const auto effectRegion : layer->blurRegions) {
- drawBlurRegion(canvas, effectRegion, drawTransform,
- cachedBlurs[effectRegion.blurRadius]);
+ drawBlurRegion(canvas, effectRegion, layerRect, cachedBlurs[effectRegion.blurRadius]);
}
- canvas->save();
- canvas->concat(drawTransform);
if (layer->shadow.length > 0) {
const auto rect = layer->geometry.roundedCornersRadius > 0
? getSkRect(layer->geometry.roundedCornersCrop)
: dest;
drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
- }
-
- if (layer->geometry.roundedCornersRadius > 0) {
- canvas->drawRRect(getRoundedRect(layer), paint);
} 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);
}
-
canvas->restore();
}
+ canvas->restore();
+ mCapture.endCapture();
{
ATRACE_NAME("flush surface");
surface->flush();
}
- canvas->restore();
if (drawFence != nullptr) {
*drawFence = flush();
@@ -771,13 +792,6 @@
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);
}
-inline SkMatrix SkiaGLRenderEngine::getDrawTransform(const LayerSettings* layer,
- const SkMatrix& screenTransform) {
- // Layers have a local transform matrix that should be applied to them.
- const auto layerTransform = getSkM44(layer->geometry.positionTransform).asM33();
- return SkMatrix::Concat(screenTransform, layerTransform);
-}
-
inline SkPoint3 SkiaGLRenderEngine::getSkPoint3(const vec3& vector) {
return SkPoint3::Make(vector.x, vector.y, vector.z);
}
@@ -807,18 +821,20 @@
}
void SkiaGLRenderEngine::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
- const SkMatrix& drawTransform,
- sk_sp<SkSurface> blurredSurface) {
+ const SkRect& layerRect, sk_sp<SkSurface> blurredSurface) {
ATRACE_CALL();
SkPaint paint;
paint.setAlpha(static_cast<int>(effectRegion.alpha * 255));
- const auto matrix = mBlurFilter->getShaderMatrix();
- paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(matrix));
+ const auto matrix = getBlurShaderTransform(canvas, layerRect);
+ paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(
+ SkTileMode::kClamp,
+ SkTileMode::kClamp,
+ SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+ &matrix));
auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
effectRegion.bottom);
- drawTransform.mapRect(&rect);
if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
@@ -835,8 +851,27 @@
}
}
+SkMatrix SkiaGLRenderEngine::getBlurShaderTransform(const SkCanvas* canvas,
+ const SkRect& layerRect) {
+ // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
+ auto matrix = mBlurFilter->getShaderMatrix();
+ // 2. Since the blurred surface has the size of the layer, we align it with the
+ // top left corner of the layer position.
+ matrix.postConcat(SkMatrix::Translate(layerRect.fLeft, layerRect.fTop));
+ // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
+ // original surface orientation. The inverse matrix has to be applied to align the blur
+ // surface with the current orientation/position of the canvas.
+ SkMatrix drawInverse;
+ if (canvas->getTotalMatrix().invert(&drawInverse)) {
+ matrix.postConcat(drawInverse);
+ }
+
+ return matrix;
+}
+
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) {
@@ -859,9 +894,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);
@@ -886,6 +935,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) {
@@ -910,6 +982,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 f5eed1e..5384ec8 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -35,6 +35,7 @@
#include "SkiaRenderEngine.h"
#include "android-base/macros.h"
#include "filters/BlurFilter.h"
+#include "skia/debug/SkiaCapture.h"
#include "skia/filters/LinearEffect.h"
namespace android {
@@ -55,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;
@@ -67,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);
@@ -77,15 +82,20 @@
inline BlurRegion getBlurRegion(const LayerSettings* layer);
inline SkColor getSkColor(const vec4& color);
inline SkM44 getSkM44(const mat4& matrix);
- inline SkMatrix getDrawTransform(const LayerSettings* layer, const SkMatrix& screenTransform);
inline SkPoint3 getSkPoint3(const vec3& vector);
base::unique_fd flush();
bool waitFence(base::unique_fd fenceFd);
void drawShadow(SkCanvas* canvas, const SkRect& casterRect, float casterCornerRadius,
const ShadowSettings& shadowSettings);
- void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion,
- const SkMatrix& drawTransform, sk_sp<SkSurface> blurrendSurface);
+ void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& layerRect,
+ sk_sp<SkSurface> blurredSurface);
+ SkMatrix getBlurShaderTransform(const SkCanvas* canvas, const SkRect& layerRect);
+ // If mUseColorManagement is correct and layer needsLinearEffect, it returns a linear runtime
+ // shader. Otherwise it returns the input shader.
+ sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader, const LayerSettings* layer,
+ const DisplaySettings& display,
+ bool undoPremultipliedAlpha);
EGLDisplay mEGLDisplay;
EGLContext mEGLContext;
@@ -116,6 +126,8 @@
sk_sp<GrDirectContext> mProtectedGrContext;
bool mInProtectedContext = false;
+ // Object to capture commands send to Skia.
+ SkiaCapture mCapture;
};
} // namespace skia
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/CaptureTimer.cpp b/libs/renderengine/skia/debug/CaptureTimer.cpp
new file mode 100644
index 0000000..11bcdb8
--- /dev/null
+++ b/libs/renderengine/skia/debug/CaptureTimer.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "CaptureTimer.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "CommonPool.h"
+
+#include <thread>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+void CaptureTimer::setTimeout(TimeoutCallback function, std::chrono::milliseconds delay) {
+ this->clear = false;
+ CommonPool::post([=]() {
+ if (this->clear) return;
+ std::this_thread::sleep_for(delay);
+ if (this->clear) return;
+ function();
+ });
+}
+
+void CaptureTimer::stop() {
+ this->clear = true;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CaptureTimer.h b/libs/renderengine/skia/debug/CaptureTimer.h
new file mode 100644
index 0000000..a0aa302
--- /dev/null
+++ b/libs/renderengine/skia/debug/CaptureTimer.h
@@ -0,0 +1,43 @@
+/*
+ * 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 <chrono>
+#include <functional>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * Simple timer that times out after a given delay and executes a void
+ * callback function.
+ */
+class CaptureTimer {
+ bool clear = false;
+
+public:
+ using TimeoutCallback = std::function<void()>;
+ // Start the timeout.
+ void setTimeout(TimeoutCallback function, std::chrono::milliseconds delay);
+ // Stop and clean up.
+ void stop();
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CommonPool.cpp b/libs/renderengine/skia/debug/CommonPool.cpp
new file mode 100644
index 0000000..bf15300
--- /dev/null
+++ b/libs/renderengine/skia/debug/CommonPool.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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 "CommonPool.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <sys/resource.h>
+#include <utils/Trace.h>
+
+#include <system/thread_defs.h>
+#include <array>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+CommonPool::CommonPool() {
+ ATRACE_CALL();
+
+ CommonPool* pool = this;
+ // Create 2 workers
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ std::thread worker([pool, i] {
+ {
+ std::array<char, 20> name{"reTask"};
+ snprintf(name.data(), name.size(), "reTask%d", i);
+ auto self = pthread_self();
+ pthread_setname_np(self, name.data());
+ setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_FOREGROUND);
+ }
+ pool->workerLoop();
+ });
+ worker.detach();
+ }
+}
+
+CommonPool& CommonPool::instance() {
+ static CommonPool pool;
+ return pool;
+}
+
+void CommonPool::post(Task&& task) {
+ instance().enqueue(std::move(task));
+}
+
+void CommonPool::enqueue(Task&& task) {
+ std::unique_lock lock(mLock);
+ while (mWorkQueue.size() > QUEUE_SIZE) {
+ lock.unlock();
+ ALOGW("Queue is full: %d, waiting before adding more tasks.", QUEUE_SIZE);
+ usleep(100);
+ lock.lock();
+ }
+ mWorkQueue.push(std::move(task));
+ if (mWaitingThreads == THREAD_COUNT || (mWaitingThreads > 0 && mWorkQueue.size() > 1)) {
+ mCondition.notify_one();
+ }
+}
+
+void CommonPool::workerLoop() {
+ std::unique_lock lock(mLock);
+ while (true) {
+ if (mWorkQueue.size() == 0) {
+ mWaitingThreads++;
+ mCondition.wait(lock);
+ mWaitingThreads--;
+ }
+ // Need to double-check that work is still available now that we have the lock
+ // It may have already been grabbed by a different thread
+ while (mWorkQueue.size() > 0) {
+ auto work = mWorkQueue.front();
+ mWorkQueue.pop();
+ lock.unlock();
+ work();
+ lock.lock();
+ }
+ }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/debug/CommonPool.h b/libs/renderengine/skia/debug/CommonPool.h
new file mode 100644
index 0000000..7fc3d23
--- /dev/null
+++ b/libs/renderengine/skia/debug/CommonPool.h
@@ -0,0 +1,70 @@
+/*
+ * 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 <log/log.h>
+
+#include <condition_variable>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <queue>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+namespace {
+#define PREVENT_COPY_AND_ASSIGN(Type) \
+private: \
+ Type(const Type&) = delete; \
+ void operator=(const Type&) = delete
+} // namespace
+
+/**
+ * Shamelessly copied from HWUI to execute Skia Capturing on the back thread in
+ * a safe manner.
+ */
+class CommonPool {
+ PREVENT_COPY_AND_ASSIGN(CommonPool);
+
+public:
+ using Task = std::function<void()>;
+ static constexpr auto THREAD_COUNT = 2;
+ static constexpr auto QUEUE_SIZE = 128;
+
+ static void post(Task&& func);
+
+private:
+ static CommonPool& instance();
+
+ CommonPool();
+ ~CommonPool() {}
+
+ void enqueue(Task&&);
+
+ void workerLoop();
+
+ std::mutex mLock;
+ std::condition_variable mCondition;
+ int mWaitingThreads = 0;
+ std::queue<Task> mWorkQueue;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/debug/README.md b/libs/renderengine/skia/debug/README.md
new file mode 100644
index 0000000..4719e34
--- /dev/null
+++ b/libs/renderengine/skia/debug/README.md
@@ -0,0 +1,17 @@
+This library turns on recording of skia commands in SkiaGL version of the RE.
+The debug property defines number of milliseconds for the recording to take place.
+A non zero value turns on the recording. The recording will stop after MS specified.
+To reset the recording, set the capture_skia_ms flag to a new time. When recording
+is finished, the capture_skia_ms flag will be set to 0 to avoid circular recording.
+
+In order to allow the process to write files onto the device run:
+adb shell setenforce 0
+
+To start recording run:
+adb shell setprop debug.renderengine.capture_skia_ms 1000
+
+File will be stored in the /data/user/ directory on the device:
+adb shell ls -al /data/user/
+
+To retrieve the data from the device:
+adb pull /data/user/re_skiacapture_<timestamp>.mskp
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
new file mode 100644
index 0000000..e9cfead
--- /dev/null
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -0,0 +1,150 @@
+/*
+ * 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 "SkiaCapture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <utils/Trace.h>
+
+#include "CommonPool.h"
+#include "src/utils/SkMultiPictureDocument.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// The root of the filename to write a recorded SKP to. In order for this file to
+// be written to /data/user/, user must run 'adb shell setenforce 0' in the device.
+static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+
+SkiaCapture::~SkiaCapture() {
+ mTimer.stop();
+}
+
+SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) {
+ ATRACE_CALL();
+
+ // If we are not running yet, set up.
+ if (!mCaptureRunning) {
+ mTimerInterval = std::chrono::milliseconds(
+ base::GetIntProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, 0));
+ // Set up the multi-frame capture. If we fail to set it up, then just return canvas.
+ // If interval is 0, return surface.
+ if (CC_LIKELY(mTimerInterval == 0ms || !setupMultiFrameCapture())) {
+ return surface->getCanvas();
+ }
+ // Start the new timer. When timer expires, write to file.
+ mTimer.setTimeout(
+ [this] {
+ endCapture();
+ writeToFile();
+ // To avoid going in circles, set the flag to 0. This way the capture can be
+ // restarted just by setting the flag and without restarting the process.
+ base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0");
+ },
+ mTimerInterval);
+ }
+
+ // Create a canvas pointer, fill it.
+ SkCanvas* pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height());
+
+ // Setting up an nway canvas is common to any kind of capture.
+ mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+ mNwayCanvas->addCanvas(surface->getCanvas());
+ mNwayCanvas->addCanvas(pictureCanvas);
+
+ return mNwayCanvas.get();
+}
+
+void SkiaCapture::endCapture() {
+ ATRACE_CALL();
+ // Don't end anything if we are not running.
+ if (!mCaptureRunning) {
+ return;
+ }
+ // Reset the canvas pointer.
+ mNwayCanvas.reset();
+ // End page.
+ if (mMultiPic) {
+ mMultiPic->endPage();
+ }
+}
+
+void SkiaCapture::writeToFile() {
+ ATRACE_CALL();
+ // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will
+ // handle the heavyweight serialization work and destroy them.
+ // mOpenMultiPicStream is released to a bare pointer because keeping it in
+ // a smart pointer makes the lambda non-copyable. The lambda is only called
+ // once, so this is safe.
+ SkFILEWStream* stream = mOpenMultiPicStream.release();
+ CommonPool::post([doc = std::move(mMultiPic), stream] {
+ ALOGD("Finalizing multi frame SKP");
+ doc->close();
+ delete stream;
+ ALOGD("Multi frame SKP complete.");
+ });
+ mCaptureRunning = false;
+}
+
+bool SkiaCapture::setupMultiFrameCapture() {
+ ATRACE_CALL();
+ ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
+
+ std::string captureFile;
+ // Attach a timestamp to the file.
+ base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+ std::chrono::steady_clock::now().time_since_epoch().count());
+ auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str());
+ // We own this stream and need to hold it until close() finishes.
+ if (stream->isValid()) {
+ mOpenMultiPicStream = std::move(stream);
+ mSerialContext.reset(new SkSharingSerialContext());
+ SkSerialProcs procs;
+ procs.fImageProc = SkSharingSerialContext::serializeImage;
+ procs.fImageCtx = mSerialContext.get();
+ procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
+ return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
+ };
+ // SkDocuments don't take ownership of the streams they write.
+ // we need to keep it until after mMultiPic.close()
+ // procs is passed as a pointer, but just as a method of having an optional default.
+ // procs doesn't need to outlive this Make call
+ // The last argument is a callback for the endPage behavior.
+ // See SkSharingProc.h for more explanation of this callback.
+ mMultiPic = SkMakeMultiPictureDocument(
+ mOpenMultiPicStream.get(), &procs,
+ [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
+ SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
+ });
+ mCaptureRunning = true;
+ return true;
+ } else {
+ ALOGE("Could not open \"%s\" for writing.", captureFile.c_str());
+ return false;
+ }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
new file mode 100644
index 0000000..eaaf598
--- /dev/null
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -0,0 +1,72 @@
+/*
+ * 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 <SkDocument.h>
+#include <SkNWayCanvas.h>
+#include <SkSurface.h>
+#include <chrono>
+#include "CaptureTimer.h"
+#include "tools/SkSharingProc.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+using namespace std::chrono_literals;
+
+/**
+ * Class that captures frames that are sent to Skia in Render Engine. It sets up
+ * a multi frame capture and writes it into a file on the device. The capture is
+ * done based on a timer.
+ */
+class SkiaCapture {
+ using Interval = std::chrono::milliseconds;
+
+public:
+ SkiaCapture() {}
+ virtual ~SkiaCapture();
+ // Called every frame. Normally returns early with screen canvas.
+ // But when capture is enabled, returns an nwaycanvas where commands are also recorded.
+ 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.
+ bool setupMultiFrameCapture();
+
+ // Closes the recording and serializes sequence to a file.
+ void writeToFile();
+
+ // Multi frame serialization stream and writer used when serializing more than one frame.
+ std::unique_ptr<SkFILEWStream> mOpenMultiPicStream;
+ sk_sp<SkDocument> mMultiPic;
+ std::unique_ptr<SkSharingSerialContext> mSerialContext;
+ std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+
+ // Capturing and interval control.
+ bool mCaptureRunning = false;
+ CaptureTimer mTimer;
+ Interval mTimerInterval = 0ms;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index f19b64a..8927be8 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -66,11 +66,10 @@
float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
float radiusByPasses = tmpRadius / (float)numberOfPasses;
- SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale,
- (float)input->height() * kInputScale);
+ SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)rect.width() * kInputScale,
+ (float)rect.height() * kInputScale);
+ SkRect scaledRect = SkRect::MakeWH(scaledInfo.width(), scaledInfo.height());
- SkRect scaledRect = {rect.fLeft * kInputScale, rect.fTop * kInputScale,
- rect.fRight * kInputScale, rect.fBottom * kInputScale};
auto drawSurface = canvas->makeSurface(scaledInfo);
const float stepX = radiusByPasses;
@@ -80,7 +79,8 @@
SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
blurBuilder.child("input") =
- input->makeImageSnapshot()->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+ input->makeImageSnapshot(rect.round())
+ ->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
blurBuilder.uniform("in_inverseScale") = kInverseInputScale;
blurBuilder.uniform("in_blurOffset") =
SkV2{stepX * kInverseInputScale, stepY * kInverseInputScale};
@@ -127,10 +127,6 @@
lastDrawTarget = readSurface;
}
- {
- ATRACE_NAME("Flush Offscreen Surfaces");
- lastDrawTarget->flushAndSubmit();
- }
return lastDrawTarget;
}
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 0e11c99..2b8063e 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>
@@ -88,6 +89,45 @@
}
};
+class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
+public:
+ std::string name() override { return "SkiaGLESRenderEngineFactory"; }
+
+ std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
+ renderengine::RenderEngineCreationArgs reCreationArgs =
+ renderengine::RenderEngineCreationArgs::Builder()
+ .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+ .setImageCacheSize(1)
+ .setEnableProtectedContext(false)
+ .setPrecacheToneMapperShaderOnly(false)
+ .setSupportsBackgroundBlur(true)
+ .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+ .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::SKIA_GL)
+ .build();
+ return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+ }
+};
+
+class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
+public:
+ std::string name() override { return "SkiaGLESCMRenderEngineFactory"; }
+
+ std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
+ renderengine::RenderEngineCreationArgs reCreationArgs =
+ renderengine::RenderEngineCreationArgs::Builder()
+ .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+ .setImageCacheSize(1)
+ .setEnableProtectedContext(false)
+ .setPrecacheToneMapperShaderOnly(false)
+ .setSupportsBackgroundBlur(true)
+ .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+ .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::SKIA_GL)
+ .setUseColorManagerment(true)
+ .build();
+ return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+ }
+};
+
class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
public:
static sp<GraphicBuffer> allocateDefaultBuffer() {
@@ -266,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;
@@ -367,6 +427,12 @@
void fillBufferColorTransform();
template <typename SourceVariant>
+ void fillBufferWithColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
+ void fillBufferColorTransformZeroLayerAlpha();
+
+ template <typename SourceVariant>
void fillRedBufferWithRoundedCorners();
template <typename SourceVariant>
@@ -401,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
@@ -726,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();
@@ -1043,9 +1143,42 @@
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>()));
+ std::make_shared<GLESCMRenderEngineFactory>(),
+ std::make_shared<SkiaGLESRenderEngineFactory>(),
+ std::make_shared<SkiaGLESCMRenderEngineFactory>()));
TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
const auto& renderEngineFactory = GetParam();
@@ -1053,6 +1186,37 @@
drawEmptyLayers();
}
+TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = fullscreenRect();
+
+ // 255, 255, 255, 255 is full opaque white.
+ const ubyte4 backgroundColor(255.f, 255.f, 255.f, 255.f);
+ // Create layer with given color.
+ renderengine::LayerSettings bgLayer;
+ bgLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ bgLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+ bgLayer.source.solidColor = half3(backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
+ backgroundColor.b / 255.0f);
+ bgLayer.alpha = backgroundColor.a / 255.0f;
+ // Transform the red color.
+ bgLayer.colorTransform = mat4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+
+ std::vector<const renderengine::LayerSettings*> layers;
+ layers.push_back(&bgLayer);
+
+ invokeDraw(settings, layers, mBuffer);
+
+ // Expect to see full opaque pixel (with inverted red from the transform).
+ expectBufferColor(Rect(0, 0, 1, 1), 0.f, backgroundColor.g, backgroundColor.b,
+ backgroundColor.a);
+}
+
TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
const auto& renderEngineFactory = GetParam();
mRE = renderEngineFactory->createRenderEngine();
@@ -1199,6 +1363,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();
@@ -1296,6 +1467,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();
@@ -1395,6 +1572,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();
@@ -1511,6 +1695,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();
@@ -1694,7 +1894,57 @@
EXPECT_TRUE(mRE->isTextureNameKnownForTesting(texName));
}
+TEST_P(RenderEngineTest, testRoundedCornersCrop) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
+ renderengine::DisplaySettings settings;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+ std::vector<const renderengine::LayerSettings*> layers;
+
+ renderengine::LayerSettings redLayer;
+ redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+ redLayer.geometry.roundedCornersRadius = 5.0f;
+ redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
+ // Red background.
+ redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+ redLayer.alpha = 1.0f;
+
+ layers.push_back(&redLayer);
+
+ // Green layer with 1/3 size.
+ renderengine::LayerSettings greenLayer;
+ greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ greenLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+ greenLayer.geometry.roundedCornersRadius = 5.0f;
+ // Bottom right corner is not going to be rounded.
+ greenLayer.geometry.roundedCornersCrop =
+ Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT,
+ DEFAULT_DISPLAY_HEIGHT)
+ .toFloatRect();
+ greenLayer.source.solidColor = half3(0.0f, 1.0f, 0.0f);
+ greenLayer.alpha = 1.0f;
+
+ layers.push_back(&greenLayer);
+
+ invokeDraw(settings, layers, mBuffer);
+
+ // Corners should be ignored...
+ // Screen size: width is 128, height is 256.
+ expectBufferColor(Rect(0, 0, 1, 1), 0, 0, 0, 0);
+ expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH - 1, 0, DEFAULT_DISPLAY_WIDTH, 1), 0, 0, 0, 0);
+ expectBufferColor(Rect(0, DEFAULT_DISPLAY_HEIGHT - 1, 1, DEFAULT_DISPLAY_HEIGHT), 0, 0, 0, 0);
+ // Bottom right corner is kept out of the clipping, and it's green.
+ expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1,
+ DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
+ 0, 255, 0, 255);
+}
+
} // 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/sensorprivacy/OWNERS b/libs/sensorprivacy/OWNERS
new file mode 100644
index 0000000..be955f5
--- /dev/null
+++ b/libs/sensorprivacy/OWNERS
@@ -0,0 +1,3 @@
+cbrubaker@google.com
+evanseverson@google.com
+mpgroover@google.com
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index f973cba..7bddee6 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -64,6 +64,15 @@
}
}
+void SensorPrivacyManager::addIndividualSensorPrivacyListener(int userId, int sensor,
+ const sp<hardware::ISensorPrivacyListener>& listener)
+{
+ sp<hardware::ISensorPrivacyManager> service = getService();
+ if (service != nullptr) {
+ service->addIndividualSensorPrivacyListener(userId, sensor, listener);
+ }
+}
+
void SensorPrivacyManager::removeSensorPrivacyListener(
const sp<hardware::ISensorPrivacyListener>& listener)
{
@@ -85,6 +94,18 @@
return false;
}
+bool SensorPrivacyManager::isIndividualSensorPrivacyEnabled(int userId, int sensor)
+{
+ sp<hardware::ISensorPrivacyManager> service = getService();
+ if (service != nullptr) {
+ bool result;
+ service->isIndividualSensorPrivacyEnabled(userId, sensor, &result);
+ return result;
+ }
+ // if the SensorPrivacyManager is not available then assume sensor privacy is disabled
+ return false;
+}
+
status_t SensorPrivacyManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient)
{
sp<hardware::ISensorPrivacyManager> service = getService();
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index 4c2d5db..629b8c2 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -22,9 +22,17 @@
interface ISensorPrivacyManager {
void addSensorPrivacyListener(in ISensorPrivacyListener listener);
+ void addIndividualSensorPrivacyListener(int userId, int sensor, in ISensorPrivacyListener listener);
+
void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
boolean isSensorPrivacyEnabled();
+ boolean isIndividualSensorPrivacyEnabled(int userId, int sensor);
+
void setSensorPrivacy(boolean enable);
+
+ void setIndividualSensorPrivacy(int userId, int sensor, boolean enable);
+
+ void setIndividualSensorPrivacyForProfileGroup(int userId, int sensor, boolean enable);
}
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index 2546a68..bd7c726 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -28,11 +28,19 @@
class SensorPrivacyManager
{
public:
+ enum {
+ INDIVIDUAL_SENSOR_MICROPHONE = 1,
+ INDIVIDUAL_SENSOR_CAMERA = 2
+ };
+
SensorPrivacyManager();
void addSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
+ void addIndividualSensorPrivacyListener(int userId, int sensor,
+ const sp<hardware::ISensorPrivacyListener>& listener);
void removeSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
bool isSensorPrivacyEnabled();
+ bool isIndividualSensorPrivacyEnabled(int userId, int sensor);
status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
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/libs/ui/include/ui/FatVector.h b/libs/ui/include/ui/FatVector.h
index 25fe3a0..cb61e6a 100644
--- a/libs/ui/include/ui/FatVector.h
+++ b/libs/ui/include/ui/FatVector.h
@@ -82,6 +82,12 @@
this->reserve(SIZE);
}
+ FatVector(std::initializer_list<T> init)
+ : std::vector<T, InlineStdAllocator<T, SIZE>>(init,
+ InlineStdAllocator<T, SIZE>(mAllocation)) {
+ this->reserve(SIZE);
+ }
+
explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); }
private:
diff --git a/libs/vibrator/OWNERS b/libs/vibrator/OWNERS
new file mode 100644
index 0000000..0997e9f
--- /dev/null
+++ b/libs/vibrator/OWNERS
@@ -0,0 +1,2 @@
+lsandrade@google.com
+michaelwr@google.com
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/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 398efc0..f576660 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -128,6 +128,7 @@
"EGL_KHR_stream_producer_eglsurface "
"EGL_KHR_surfaceless_context "
"EGL_KHR_wait_sync " // strongly recommended
+ "EGL_NV_context_priority_realtime "
"EGL_NV_system_time "
;
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index 6235f06..0d17265 100644
--- a/services/audiomanager/IAudioManager.cpp
+++ b/services/audiomanager/IAudioManager.cpp
@@ -84,11 +84,13 @@
return remote()->transact(PLAYER_ATTRIBUTES, data, &reply, IBinder::FLAG_ONEWAY);
}
- virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event) {
+ virtual status_t playerEvent(audio_unique_id_t piid, player_state_t event,
+ audio_port_handle_t deviceId) {
Parcel data, reply;
data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor());
data.writeInt32((int32_t) piid);
data.writeInt32((int32_t) event);
+ data.writeInt32((int32_t) deviceId);
return remote()->transact(PLAYER_EVENT, data, &reply, IBinder::FLAG_ONEWAY);
}
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/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
index c5f8859..e916221 100644
--- a/services/gpuservice/tests/unittests/GpuMemTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -59,7 +59,6 @@
}
void SetUp() override {
- SKIP_IF_BPF_NOT_SUPPORTED;
bpf::setrlimitForTest();
mGpuMem = std::make_unique<GpuMem>();
@@ -87,8 +86,6 @@
};
TEST_F(GpuMemTest, validGpuMemTotalBpfPaths) {
- SKIP_IF_BPF_NOT_SUPPORTED;
-
EXPECT_EQ(mTestableGpuMem.getGpuMemTraceGroup(), "gpu_mem");
EXPECT_EQ(mTestableGpuMem.getGpuMemTotalTracepoint(), "gpu_mem_total");
EXPECT_EQ(mTestableGpuMem.getGpuMemTotalProgPath(),
@@ -97,20 +94,16 @@
}
TEST_F(GpuMemTest, bpfInitializationFailed) {
- SKIP_IF_BPF_NOT_SUPPORTED;
-
EXPECT_EQ(dumpsys(), "Failed to initialize GPU memory eBPF\n");
}
TEST_F(GpuMemTest, gpuMemTotalMapEmpty) {
- SKIP_IF_BPF_NOT_SUPPORTED;
mTestableGpuMem.setGpuMemTotalMap(mTestMap);
EXPECT_EQ(dumpsys(), "GPU memory total usage map is empty\n");
}
TEST_F(GpuMemTest, globalMemTotal) {
- SKIP_IF_BPF_NOT_SUPPORTED;
ASSERT_RESULT_OK(mTestMap.writeValue(TEST_GLOBAL_KEY, TEST_GLOBAL_VAL, BPF_ANY));
mTestableGpuMem.setGpuMemTotalMap(mTestMap);
@@ -118,7 +111,6 @@
}
TEST_F(GpuMemTest, missingGlobalMemTotal) {
- SKIP_IF_BPF_NOT_SUPPORTED;
ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_1, TEST_PROC_VAL_1, BPF_ANY));
mTestableGpuMem.setGpuMemTotalMap(mTestMap);
@@ -126,7 +118,6 @@
}
TEST_F(GpuMemTest, procMemTotal) {
- SKIP_IF_BPF_NOT_SUPPORTED;
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);
@@ -146,7 +137,6 @@
}
TEST_F(GpuMemTest, traverseGpuMemTotals) {
- 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));
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/Android.bp b/services/inputflinger/Android.bp
index 96e6207..b640e9c 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -58,6 +58,7 @@
"libstatslog",
"libutils",
"libui",
+ "lib-platform-compat-native-api",
],
static_libs: [
"libattestation",
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/VibrationElement.cpp b/services/inputflinger/VibrationElement.cpp
index aaf5834..17e1ad4 100644
--- a/services/inputflinger/VibrationElement.cpp
+++ b/services/inputflinger/VibrationElement.cpp
@@ -24,13 +24,28 @@
using android::base::StringPrintf;
namespace android {
+// VibrationElement implementations
+VibrationElement::VibrationElement(size_t channelNum) {
+ channels.reserve(channelNum);
+}
+
+VibrationElement::VibrationElement(const VibrationElement& other) {
+ duration = other.duration;
+ channels.resize(other.channels.size());
+ for (size_t i = 0; i < other.channels.size(); i++) {
+ channels[i].first = other.channels[i].first;
+ channels[i].second = other.channels[i].second;
+ }
+}
const std::string VibrationElement::toString() const {
std::string dump;
dump += StringPrintf("[duration=%lldms, channels=[", duration.count());
for (auto it = channels.begin(); it != channels.end(); ++it) {
- dump += std::to_string(*it);
+ dump += std::to_string(it->first);
+ dump += " : ";
+ dump += std::to_string(it->second);
if (std::next(it) != channels.end()) {
dump += ", ";
}
@@ -40,17 +55,79 @@
return dump;
}
-uint16_t VibrationElement::getMagnitude(size_t channelIdx) const {
- if (channelIdx >= channels.size()) {
+uint16_t VibrationElement::getMagnitude(int32_t vibratorId) const {
+ auto it =
+ std::find_if(channels.begin(), channels.end(),
+ [vibratorId](const std::pair<int32_t /*vibratorId*/, uint8_t /*amplitude*/>
+ pair) { return pair.first == vibratorId; });
+ if (it == channels.end()) {
return 0;
}
// convert range [0,255] to [0,65535] (android framework to linux ff ranges)
- return static_cast<uint16_t>(channels[channelIdx]) << 8;
+ return static_cast<uint16_t>(it->second) << 8;
}
bool VibrationElement::isOn() const {
return std::any_of(channels.begin(), channels.end(),
- [](uint16_t channel) { return channel != 0; });
+ [](const auto& channel) { return channel.second != 0; });
+}
+
+void VibrationElement::addChannel(int32_t vibratorId, uint8_t amplitude) {
+ channels.push_back(std::make_pair(vibratorId, amplitude));
+}
+
+bool VibrationElement::operator==(const VibrationElement& other) const {
+ if (duration != other.duration || channels.size() != other.channels.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < CHANNEL_SIZE; i++) {
+ if (channels[i] != other.channels[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VibrationElement::operator!=(const VibrationElement& other) const {
+ return !(*this == other);
+}
+
+// VibrationSequence implementations
+VibrationSequence::VibrationSequence(size_t length) {
+ pattern.reserve(length);
+}
+
+void VibrationSequence::operator=(const VibrationSequence& other) {
+ pattern = other.pattern;
+}
+
+bool VibrationSequence::operator==(const VibrationSequence& other) const {
+ if (pattern.size() != other.pattern.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < pattern.size(); i++) {
+ if (pattern[i] != other.pattern[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void VibrationSequence::addElement(VibrationElement element) {
+ pattern.push_back(element);
+}
+
+const std::string VibrationSequence::toString() const {
+ std::string dump;
+ dump += "[";
+
+ for (const auto& element : pattern) {
+ dump += element.toString();
+ dump += " ";
+ }
+
+ dump += "]";
+ return dump;
}
} // namespace android
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 9abf8b1..bd275a7 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -16,6 +16,7 @@
"libstatslog",
"libui",
"libutils",
+ "lib-platform-compat-native-api",
],
static_libs: [
"libattestation",
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 9fea298..887bdd4 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -55,17 +55,30 @@
ALOGE("There is no focused window for %s", applicationHandle->getName().c_str());
}
- void notifyConnectionUnresponsive(const sp<IBinder>& connectionToken,
- const std::string& reason) override {
- ALOGE("Connection is not responding: %s", reason.c_str());
+ void notifyWindowUnresponsive(const sp<IBinder>& connectionToken,
+ const std::string& reason) override {
+ ALOGE("Window is not responding: %s", reason.c_str());
}
- void notifyConnectionResponsive(const sp<IBinder>& connectionToken) override {}
+ void notifyWindowResponsive(const sp<IBinder>& connectionToken) override {}
+
+ void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override {
+ ALOGE("Monitor is not responding: %s", reason.c_str());
+ }
+
+ void notifyMonitorResponsive(int32_t pid) override {}
void notifyInputChannelBroken(const sp<IBinder>&) override {}
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 +109,8 @@
void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+ void setPointerCapture(bool enabled) override {}
+
InputDispatcherConfiguration mConfig;
};
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index ff9aac9..d467692 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -47,6 +47,7 @@
"libstatslog",
"libui",
"libutils",
+ "lib-platform-compat-native-api",
],
static_libs: [
"libattestation",
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..26b641d 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,8 @@
sp<IBinder> oldToken;
sp<IBinder> newToken;
std::string obscuringPackage;
+ bool enabled;
+ int32_t pid;
};
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 91e9536..cdc74f3 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -47,12 +47,12 @@
// 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>
#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <com/android/internal/compat/IPlatformCompatNative.h>
#include <input/InputDevice.h>
#include <input/InputWindow.h>
#include <log/log.h>
@@ -71,6 +71,7 @@
#include <sstream>
#include "Connection.h"
+#include "InputDispatcher.h"
#define INDENT " "
#define INDENT2 " "
@@ -79,8 +80,10 @@
using android::base::StringPrintf;
using android::os::BlockUntrustedTouchesMode;
+using android::os::IInputConstants;
using android::os::InputEventInjectionResult;
using android::os::InputEventInjectionSync;
+using com::android::internal::compat::IPlatformCompatNative;
namespace android::inputdispatcher {
@@ -318,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,
@@ -422,6 +436,37 @@
return *lhs == *rhs;
}
+static sp<IPlatformCompatNative> getCompatService() {
+ sp<IBinder> service(defaultServiceManager()->getService(String16("platform_compat_native")));
+ if (service == nullptr) {
+ ALOGE("Failed to link to compat service");
+ return nullptr;
+ }
+ 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;
+}
+
+static std::optional<int32_t> findMonitorPidByToken(
+ const std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay,
+ const sp<IBinder>& token) {
+ for (const auto& it : monitorsByDisplay) {
+ const std::vector<Monitor>& monitors = it.second;
+ for (const Monitor& monitor : monitors) {
+ if (monitor.inputChannel->getConnectionToken() == token) {
+ return monitor.pid;
+ }
+ }
+ }
+ return std::nullopt;
+}
+
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -440,7 +485,10 @@
// initialize it here anyways.
mInTouchMode(true),
mMaximumObscuringOpacityForTouch(1.0f),
- mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
+ mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+ mFocusedWindowRequestedPointerCapture(false),
+ mWindowTokenWithPointerCapture(nullptr),
+ mCompatService(getCompatService()) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -581,7 +629,7 @@
connection->responsive = false;
// Stop waking up for this unresponsive connection
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
- onAnrLocked(*connection);
+ onAnrLocked(connection);
return LONG_LONG_MIN;
}
@@ -699,6 +747,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) {
@@ -734,6 +790,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) {
@@ -848,7 +921,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;
}
@@ -858,7 +933,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();
}
@@ -954,6 +1032,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;
@@ -977,10 +1059,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;
}
}
@@ -1162,6 +1250,61 @@
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;
+ if (mFocusedWindowRequestedPointerCapture) {
+ mFocusedWindowRequestedPointerCapture = false;
+ setPointerCaptureLocked(false);
+ }
+ }
+
+ auto channel = getInputChannelLocked(token);
+ if (channel == nullptr) {
+ // Window has gone away, clean up Pointer Capture state.
+ mWindowTokenWithPointerCapture = nullptr;
+ if (mFocusedWindowRequestedPointerCapture) {
+ mFocusedWindowRequestedPointerCapture = false;
+ setPointerCaptureLocked(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.
@@ -1284,6 +1427,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();
@@ -1469,10 +1657,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;
}
}
@@ -1526,7 +1716,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;
}
@@ -1551,7 +1741,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
@@ -1863,6 +2053,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;
@@ -2352,8 +2544,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);
@@ -2391,9 +2585,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;
}
}
@@ -2437,7 +2633,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) {
@@ -2603,13 +2799,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;
}
}
@@ -2807,6 +3008,7 @@
reportTouchEventForStatistics(motionEntry);
break;
}
+
case EventEntry::Type::FOCUS: {
const FocusEntry& focusEntry = static_cast<const FocusEntry&>(eventEntry);
status = connection->inputPublisher.publishFocusEvent(dispatchEntry->seq,
@@ -2816,10 +3018,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;
}
}
@@ -3033,8 +3245,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);
}
}
@@ -3110,14 +3322,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;
}
}
@@ -3171,9 +3386,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;
}
}
@@ -3236,7 +3453,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;
@@ -3506,6 +3725,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;
}
@@ -3548,7 +3795,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(
@@ -4010,8 +4267,8 @@
const std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>& handlesPerDisplay) {
{ // acquire lock
std::scoped_lock _l(mLock);
- for (auto const& i : handlesPerDisplay) {
- setInputWindowsLocked(i.second, i.first);
+ for (const auto& [displayId, handles] : handlesPerDisplay) {
+ setInputWindowsLocked(handles, displayId);
}
}
// Wake up poll loop since it may need to make new input dispatching choices.
@@ -4113,6 +4370,18 @@
ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
}
oldWindowHandle->releaseChannel();
+ // To avoid making too many calls into the compat framework, only
+ // check for window flags when windows are going away.
+ // TODO(b/157929241) : delete this. This is only needed temporarily
+ // in order to gather some data about the flag usage
+ if (oldWindowHandle->getInfo()->flags.test(InputWindowInfo::Flag::SLIPPERY)) {
+ ALOGW("%s has FLAG_SLIPPERY. Please report this in b/157929241",
+ oldWindowHandle->getName().c_str());
+ if (mCompatService != nullptr) {
+ mCompatService->reportChangeByUid(IInputConstants::BLOCK_FLAG_SLIPPERY,
+ oldWindowHandle->getInfo()->ownerUid);
+ }
+ }
}
}
}
@@ -4430,6 +4699,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));
@@ -4453,6 +4740,7 @@
dump += dumpFocusedWindowsLocked();
dump += dumpPendingFocusRequestsLocked();
+ dump += dumpPointerCaptureStateLocked();
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
@@ -4685,7 +4973,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);
@@ -4709,7 +4997,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);
}
@@ -4834,6 +5122,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) {
@@ -4847,6 +5168,14 @@
return std::nullopt;
}
+std::optional<int32_t> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
+ std::optional<int32_t> gesturePid = findMonitorPidByToken(mGestureMonitorsByDisplay, token);
+ if (gesturePid.has_value()) {
+ return gesturePid;
+ }
+ return findMonitorPidByToken(mGlobalMonitorsByDisplay, token);
+}
+
sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
if (inputConnectionToken == nullptr) {
return nullptr;
@@ -4907,12 +5236,15 @@
postCommandLocked(std::move(commandEntry));
}
-void InputDispatcher::onAnrLocked(const Connection& connection) {
+void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
+ if (connection == nullptr) {
+ LOG_ALWAYS_FATAL("Caller must check for nullness");
+ }
// Since we are allowing the policy to extend the timeout, maybe the waitQueue
// is already healthy again. Don't raise ANR in this situation
- if (connection.waitQueue.empty()) {
+ if (connection->waitQueue.empty()) {
ALOGI("Not raising ANR because the connection %s has recovered",
- connection.inputChannel->getName().c_str());
+ connection->inputChannel->getName().c_str());
return;
}
/**
@@ -4923,21 +5255,20 @@
* processes the events linearly. So providing information about the oldest entry seems to be
* most useful.
*/
- DispatchEntry* oldestEntry = *connection.waitQueue.begin();
+ DispatchEntry* oldestEntry = *connection->waitQueue.begin();
const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
std::string reason =
android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
- connection.inputChannel->getName().c_str(),
+ connection->inputChannel->getName().c_str(),
ns2ms(currentWait),
oldestEntry->eventEntry->getDescription().c_str());
- sp<IBinder> connectionToken = connection.inputChannel->getConnectionToken();
+ sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
- std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
- &InputDispatcher::doNotifyConnectionUnresponsiveLockedInterruptible);
- commandEntry->connectionToken = connectionToken;
- commandEntry->reason = std::move(reason);
- postCommandLocked(std::move(commandEntry));
+ processConnectionUnresponsiveLocked(*connection, std::move(reason));
+
+ // Stop waking up for events on this connection, it is already unresponsive
+ cancelEventsForAnrLocked(connection);
}
void InputDispatcher::onAnrLocked(std::shared_ptr<InputApplicationHandle> application) {
@@ -5022,26 +5353,34 @@
mLock.lock();
}
-void InputDispatcher::doNotifyConnectionUnresponsiveLockedInterruptible(
- CommandEntry* commandEntry) {
+void InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible(CommandEntry* commandEntry) {
mLock.unlock();
- mPolicy->notifyConnectionUnresponsive(commandEntry->connectionToken, commandEntry->reason);
+ mPolicy->notifyWindowUnresponsive(commandEntry->connectionToken, commandEntry->reason);
mLock.lock();
-
- // stop waking up for events in this connection, it is already not responding
- sp<Connection> connection = getConnectionLocked(commandEntry->connectionToken);
- if (connection == nullptr) {
- return;
- }
- cancelEventsForAnrLocked(connection);
}
-void InputDispatcher::doNotifyConnectionResponsiveLockedInterruptible(CommandEntry* commandEntry) {
+void InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible(CommandEntry* commandEntry) {
mLock.unlock();
- mPolicy->notifyConnectionResponsive(commandEntry->connectionToken);
+ mPolicy->notifyMonitorUnresponsive(commandEntry->pid, commandEntry->reason);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyWindowResponsiveLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyWindowResponsive(commandEntry->connectionToken);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyMonitorResponsiveLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyMonitorResponsive(commandEntry->pid);
mLock.lock();
}
@@ -5146,13 +5485,8 @@
if (!connection->responsive) {
connection->responsive = isConnectionResponsive(*connection);
if (connection->responsive) {
- // The connection was unresponsive, and now it's responsive. Tell the policy
- // about it so that it can stop ANR.
- std::unique_ptr<CommandEntry> connectionResponsiveCommand =
- std::make_unique<CommandEntry>(
- &InputDispatcher::doNotifyConnectionResponsiveLockedInterruptible);
- connectionResponsiveCommand->connectionToken = connectionToken;
- postCommandLocked(std::move(connectionResponsiveCommand));
+ // The connection was unresponsive, and now it's responsive.
+ processConnectionResponsiveLocked(*connection);
}
}
traceWaitQueueLength(connection);
@@ -5168,6 +5502,82 @@
startDispatchCycleLocked(now(), connection);
}
+void InputDispatcher::sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) {
+ std::unique_ptr<CommandEntry> monitorUnresponsiveCommand = std::make_unique<CommandEntry>(
+ &InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible);
+ monitorUnresponsiveCommand->pid = pid;
+ monitorUnresponsiveCommand->reason = std::move(reason);
+ postCommandLocked(std::move(monitorUnresponsiveCommand));
+}
+
+void InputDispatcher::sendWindowUnresponsiveCommandLocked(sp<IBinder> connectionToken,
+ std::string reason) {
+ std::unique_ptr<CommandEntry> windowUnresponsiveCommand = std::make_unique<CommandEntry>(
+ &InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible);
+ windowUnresponsiveCommand->connectionToken = std::move(connectionToken);
+ windowUnresponsiveCommand->reason = std::move(reason);
+ postCommandLocked(std::move(windowUnresponsiveCommand));
+}
+
+void InputDispatcher::sendMonitorResponsiveCommandLocked(int32_t pid) {
+ std::unique_ptr<CommandEntry> monitorResponsiveCommand = std::make_unique<CommandEntry>(
+ &InputDispatcher::doNotifyMonitorResponsiveLockedInterruptible);
+ monitorResponsiveCommand->pid = pid;
+ postCommandLocked(std::move(monitorResponsiveCommand));
+}
+
+void InputDispatcher::sendWindowResponsiveCommandLocked(sp<IBinder> connectionToken) {
+ std::unique_ptr<CommandEntry> windowResponsiveCommand = std::make_unique<CommandEntry>(
+ &InputDispatcher::doNotifyWindowResponsiveLockedInterruptible);
+ windowResponsiveCommand->connectionToken = std::move(connectionToken);
+ postCommandLocked(std::move(windowResponsiveCommand));
+}
+
+/**
+ * Tell the policy that a connection has become unresponsive so that it can start ANR.
+ * Check whether the connection of interest is a monitor or a window, and add the corresponding
+ * command entry to the command queue.
+ */
+void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
+ std::string reason) {
+ const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+ if (connection.monitor) {
+ ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+ reason.c_str());
+ std::optional<int32_t> pid = findMonitorPidByTokenLocked(connectionToken);
+ if (!pid.has_value()) {
+ ALOGE("Could not find unresponsive monitor for connection %s",
+ connection.inputChannel->getName().c_str());
+ return;
+ }
+ sendMonitorUnresponsiveCommandLocked(pid.value(), std::move(reason));
+ return;
+ }
+ // If not a monitor, must be a window
+ ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+ reason.c_str());
+ sendWindowUnresponsiveCommandLocked(connectionToken, std::move(reason));
+}
+
+/**
+ * Tell the policy that a connection has become responsive so that it can stop ANR.
+ */
+void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) {
+ const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+ if (connection.monitor) {
+ std::optional<int32_t> pid = findMonitorPidByTokenLocked(connectionToken);
+ if (!pid.has_value()) {
+ ALOGE("Could not find responsive monitor for connection %s",
+ connection.inputChannel->getName().c_str());
+ return;
+ }
+ sendMonitorResponsiveCommandLocked(pid.value());
+ return;
+ }
+ // If not a monitor, must be a window
+ sendWindowResponsiveCommandLocked(connectionToken);
+}
+
bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& connection,
DispatchEntry* dispatchEntry,
KeyEntry& keyEntry, bool handled) {
@@ -5358,14 +5768,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.
@@ -5552,11 +5954,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
@@ -5600,4 +6041,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 9aaae74..c7299e9 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -32,6 +32,7 @@
#include "TouchedWindow.h"
#include <attestation/HmacKeyManager.h>
+#include <com/android/internal/compat/IPlatformCompatNative.h>
#include <input/Input.h>
#include <input/InputApplication.h>
#include <input/InputTransport.h>
@@ -93,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;
@@ -122,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;
@@ -137,6 +143,7 @@
DISABLED,
BLOCKED,
STALE,
+ NO_POINTER_CAPTURE,
};
enum class FocusResult {
@@ -226,6 +233,8 @@
// Finds the display ID of the gesture monitor identified by the provided token.
std::optional<int32_t> findGestureMonitorDisplayByTokenLocked(const sp<IBinder>& token)
REQUIRES(mLock);
+ // Find a monitor pid by the provided token.
+ std::optional<int32_t> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
// Input channels that will receive a copy of all input events sent to the provided display.
std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
@@ -350,6 +359,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);
@@ -369,9 +393,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);
@@ -407,6 +435,34 @@
void processNoFocusedWindowAnrLocked() REQUIRES(mLock);
/**
+ * Tell policy about a window or a monitor that just became unresponsive. Starts ANR.
+ */
+ void processConnectionUnresponsiveLocked(const Connection& connection, std::string reason)
+ REQUIRES(mLock);
+ /**
+ * Tell policy about a window or a monitor that just became responsive.
+ */
+ void processConnectionResponsiveLocked(const Connection& connection) REQUIRES(mLock);
+
+ /**
+ * Post `doNotifyMonitorUnresponsiveLockedInterruptible` command.
+ */
+ void sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) REQUIRES(mLock);
+ /**
+ * Post `doNotifyWindowUnresponsiveLockedInterruptible` command.
+ */
+ void sendWindowUnresponsiveCommandLocked(sp<IBinder> connectionToken, std::string reason)
+ REQUIRES(mLock);
+ /**
+ * Post `doNotifyMonitorResponsiveLockedInterruptible` command.
+ */
+ void sendMonitorResponsiveCommandLocked(int32_t pid) REQUIRES(mLock);
+ /**
+ * Post `doNotifyWindowResponsiveLockedInterruptible` command.
+ */
+ void sendWindowResponsiveCommandLocked(sp<IBinder> connectionToken) REQUIRES(mLock);
+
+ /**
* This map will store the pending focus requests that cannot be currently processed. This can
* happen if the window requested to be focused is not currently visible. Such a window might
* become visible later, and these requests would be processed at that time.
@@ -532,6 +588,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);
@@ -550,7 +607,7 @@
int32_t displayId, std::string_view reason) REQUIRES(mLock);
void notifyFocusChangedLocked(const sp<IBinder>& oldFocus, const sp<IBinder>& newFocus)
REQUIRES(mLock);
- void onAnrLocked(const Connection& connection) REQUIRES(mLock);
+ void onAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
void onAnrLocked(std::shared_ptr<InputApplicationHandle> application) REQUIRES(mLock);
void onUntrustedTouchLocked(const std::string& obscuringPackage) REQUIRES(mLock);
void updateLastAnrStateLocked(const sp<InputWindowHandle>& window, const std::string& reason)
@@ -565,15 +622,19 @@
REQUIRES(mLock);
void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
void doNotifyFocusChangedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ // ANR-related callbacks - start
void doNotifyNoFocusedWindowAnrLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
- void doNotifyConnectionUnresponsiveLockedInterruptible(CommandEntry* commandEntry)
- REQUIRES(mLock);
- void doNotifyConnectionResponsiveLockedInterruptible(CommandEntry* commandEntry)
- REQUIRES(mLock);
+ void doNotifyWindowUnresponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doNotifyMonitorUnresponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doNotifyWindowResponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ void doNotifyMonitorResponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
+ // ANR-related callbacks - end
+ 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);
@@ -581,7 +642,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.
@@ -596,6 +656,7 @@
void traceWaitQueueLength(const sp<Connection>& connection);
sp<InputReporterInterface> mReporter;
+ sp<com::android::internal::compat::IPlatformCompatNative> mCompatService;
};
} // namespace android::inputdispatcher
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..b976129 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -50,24 +50,40 @@
virtual void notifyNoFocusedWindowAnr(
const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) = 0;
- /* Notifies the system that a connection just became unresponsive. This indicates that ANR
- * should be raised for this connection. The connection is identified via token.
+ /* Notifies the system that a window just became unresponsive. This indicates that ANR
+ * should be raised for this window. The window is identified via token.
* The string reason contains information about the input event that we haven't received
* a response for.
*/
- virtual void notifyConnectionUnresponsive(const sp<IBinder>& token,
- const std::string& reason) = 0;
+ virtual void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) = 0;
+ /* Notifies the system that a monitor just became unresponsive. This indicates that ANR
+ * should be raised for this monitor. The monitor is identified via its pid.
+ * The string reason contains information about the input event that we haven't received
+ * a response for.
+ */
+ virtual void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) = 0;
- /* Notifies the system that a connection just became responsive. This is only called after the
- * connection was first marked "unresponsive". This indicates that ANR dialog (if any) should
- * no longer should be shown to the user. The connection is eligible to cause a new ANR in the
+ /* Notifies the system that a window just became responsive. This is only called after the
+ * window was first marked "unresponsive". This indicates that ANR dialog (if any) should
+ * no longer should be shown to the user. The window is eligible to cause a new ANR in the
* future.
*/
- virtual void notifyConnectionResponsive(const sp<IBinder>& token) = 0;
+ virtual void notifyWindowResponsive(const sp<IBinder>& token) = 0;
+ /* Notifies the system that a monitor just became responsive. This is only called after the
+ * monitor was first marked "unresponsive". This indicates that ANR dialog (if any) should
+ * no longer should be shown to the user. The monitor is eligible to cause a new ANR in the
+ * future.
+ */
+ virtual void notifyMonitorResponsive(int32_t pid) = 0;
/* 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 +150,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 ffd8bf2..ea9b483 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -101,12 +101,27 @@
virtual void requestRefreshConfiguration(uint32_t changes) = 0;
/* Controls the vibrator of a particular input device. */
- virtual void vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern,
- ssize_t repeat, int32_t token) = 0;
+ virtual void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token) = 0;
virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
+ virtual bool isVibrating(int32_t deviceId) = 0;
+
+ virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
+
/* Return true if the device can send input events to the specified display. */
virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
+
+ /* Enable sensor in input reader mapper. */
+ virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
+ std::chrono::microseconds samplingPeriod,
+ std::chrono::microseconds maxBatchReportLatency) = 0;
+
+ /* Disable sensor in input reader mapper. */
+ virtual void disableSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+ /* Flush sensor data in input reader mapper. */
+ virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
};
// --- InputReaderConfiguration ---
diff --git a/services/inputflinger/include/VibrationElement.h b/services/inputflinger/include/VibrationElement.h
index b60ffac..736041e 100644
--- a/services/inputflinger/include/VibrationElement.h
+++ b/services/inputflinger/include/VibrationElement.h
@@ -21,6 +21,7 @@
#include <chrono>
#include <cstdint>
#include <string>
+#include <vector>
namespace android {
@@ -32,13 +33,43 @@
struct VibrationElement {
std::chrono::milliseconds duration;
// Channel amplitude range 0-255.
- std::array<uint8_t, CHANNEL_SIZE> channels = {0, 0};
+ std::vector<std::pair<int32_t /*vibratorId*/, uint8_t /*amplitude*/>> channels;
+
+ explicit VibrationElement(size_t channelNum);
+
+ VibrationElement(const VibrationElement& other);
+
+ bool operator==(const VibrationElement& other) const;
+
+ bool operator!=(const VibrationElement& other) const;
+
+ void addChannel(int32_t vibratorId, uint8_t amplitude);
const std::string toString() const;
- uint16_t getMagnitude(size_t channelIndex) const;
+
+ uint16_t getMagnitude(int32_t vibratorId) const;
+
bool isOn() const;
};
+/*
+ * Describes a sequence of rumble effect
+ */
+struct VibrationSequence {
+ // Pattern of vibration elements
+ std::vector<VibrationElement> pattern;
+
+ explicit VibrationSequence(size_t length);
+
+ void operator=(const VibrationSequence& other);
+
+ bool operator==(const VibrationSequence& other) const;
+
+ void addElement(VibrationElement element);
+
+ const std::string toString() const;
+};
+
} // namespace android
#endif // _VIBRATION_ELEMENT_H
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 c5210b5..b97ff90 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -61,8 +61,8 @@
// v4l2 devices go directly into /dev
static const char* VIDEO_DEVICE_PATH = "/dev";
-static constexpr size_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
-static constexpr size_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
+static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
+static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
static inline const char* toString(bool value) {
return value ? "true" : "false";
@@ -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));
}
@@ -475,25 +492,25 @@
}
InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->identifier : InputDeviceIdentifier();
}
Flags<InputDeviceClass> EventHub::getDeviceClasses(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->classes : Flags<InputDeviceClass>(0);
}
int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->controllerNumber : 0;
}
void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->configuration) {
*outConfiguration = *device->configuration;
@@ -507,7 +524,7 @@
outAxisInfo->clear();
if (axis >= 0 && axis <= ABS_MAX) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -534,7 +551,7 @@
bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
if (axis >= 0 && axis <= REL_MAX) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->relBitmask.test(axis) : false;
}
@@ -542,7 +559,7 @@
}
bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
return property >= 0 && property <= INPUT_PROP_MAX && device != nullptr
@@ -550,9 +567,18 @@
: 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) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->keyBitmask.test(scanCode)) {
@@ -565,7 +591,7 @@
}
int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
@@ -588,7 +614,7 @@
int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
if (sw >= 0 && sw <= SW_MAX) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->swBitmask.test(sw)) {
@@ -604,7 +630,7 @@
*outValue = 0;
if (axis >= 0 && axis <= ABS_MAX) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -624,7 +650,7 @@
bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -652,7 +678,7 @@
status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
status_t status = NAME_NOT_FOUND;
@@ -692,7 +718,7 @@
}
status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -705,14 +731,25 @@
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) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
mExcludedDevices = devices;
}
bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && scanCode >= 0 && scanCode <= KEY_MAX) {
return device->keyBitmask.test(scanCode);
@@ -721,7 +758,7 @@
}
bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
int32_t sc;
if (device != nullptr && device->mapLed(led, &sc) == NO_ERROR) {
@@ -731,7 +768,7 @@
}
void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
device->setLedStateLocked(led, on);
@@ -742,7 +779,7 @@
std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
outVirtualKeys.clear();
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->virtualKeyMap) {
const std::vector<VirtualKeyDefinition> virtualKeys =
@@ -752,7 +789,7 @@
}
const std::shared_ptr<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr) {
return device->getKeyCharacterMap();
@@ -761,7 +798,7 @@
}
bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && map != nullptr && device->keyMap.keyCharacterMap != nullptr) {
device->keyMap.keyCharacterMap->combine(*map);
@@ -822,7 +859,7 @@
}
void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
ff_effect effect;
@@ -857,7 +894,7 @@
}
void EventHub::cancelVibrate(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
if (device->ffEffectPlaying) {
@@ -878,6 +915,18 @@
}
}
+std::vector<int32_t> EventHub::getVibratorIds(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
+ std::vector<int32_t> vibrators;
+ Device* device = getDeviceLocked(deviceId);
+ if (device != nullptr && device->hasValidFd() &&
+ device->classes.test(InputDeviceClass::VIBRATOR)) {
+ vibrators.push_back(FF_STRONG_MAGNITUDE_CHANNEL_IDX);
+ vibrators.push_back(FF_WEAK_MAGNITUDE_CHANNEL_IDX);
+ }
+ return vibrators;
+}
+
EventHub::Device* EventHub::getDeviceByDescriptorLocked(const std::string& descriptor) const {
for (const auto& [id, device] : mDevices) {
if (descriptor == device->identifier.descriptor) {
@@ -930,7 +979,7 @@
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
ALOG_ASSERT(bufferSize >= 1);
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
struct input_event readBuffer[bufferSize];
@@ -1184,7 +1233,7 @@
}
std::vector<TouchVideoFrame> EventHub::getVideoFrames(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr || !device->videoDevice) {
@@ -1393,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
@@ -1457,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)) {
@@ -1481,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();
}
@@ -1593,7 +1650,7 @@
}
bool EventHub::isDeviceEnabled(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1603,7 +1660,7 @@
}
status_t EventHub::enableDevice(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1625,7 +1682,7 @@
}
status_t EventHub::disableDevice(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1809,7 +1866,7 @@
void EventHub::requestReopenDevices() {
ALOGV("requestReopenDevices() called");
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
mNeedToReopenDevices = true;
}
@@ -1817,7 +1874,7 @@
dump += "Event Hub State:\n";
{ // acquire lock
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
dump += StringPrintf(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
@@ -1868,8 +1925,7 @@
void EventHub::monitor() {
// Acquire and release the lock to ensure that the event hub has not deadlocked.
- mLock.lock();
- mLock.unlock();
+ std::unique_lock<std::mutex> lock(mLock);
}
}; // namespace android
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 271bc2f..3e6910d 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));
@@ -429,10 +436,9 @@
return result;
}
-void InputDevice::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
- int32_t token) {
- for_each_mapper([pattern, repeat, token](InputMapper& mapper) {
- mapper.vibrate(pattern, repeat, token);
+void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {
+ for_each_mapper([sequence, repeat, token](InputMapper& mapper) {
+ mapper.vibrate(sequence, repeat, token);
});
}
@@ -440,6 +446,46 @@
for_each_mapper([token](InputMapper& mapper) { mapper.cancelVibrate(token); });
}
+bool InputDevice::isVibrating() {
+ bool vibrating = false;
+ for_each_mapper([&vibrating](InputMapper& mapper) { vibrating |= mapper.isVibrating(); });
+ return vibrating;
+}
+
+/* There's no guarantee the IDs provided by the different mappers are unique, so if we have two
+ * different vibration mappers then we could have duplicate IDs.
+ * Alternatively, if we have a merged device that has multiple evdev nodes with FF_* capabilities,
+ * we would definitely have duplicate IDs.
+ */
+std::vector<int32_t> InputDevice::getVibratorIds() {
+ std::vector<int32_t> vibrators;
+ for_each_mapper([&vibrators](InputMapper& mapper) {
+ std::vector<int32_t> devVibs = mapper.getVibratorIds();
+ vibrators.reserve(vibrators.size() + devVibs.size());
+ vibrators.insert(vibrators.end(), devVibs.begin(), devVibs.end());
+ });
+ 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); });
}
@@ -459,8 +505,7 @@
}
void InputDevice::notifyReset(nsecs_t when) {
- NotifyDeviceResetArgs args(mContext->getNextId(), when, mId);
- mContext->getListener()->notifyDeviceReset(&args);
+ mContext->notifyDeviceReset(when, mId);
}
std::optional<int32_t> InputDevice::getAssociatedDisplayId() {
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index e263f01..7c448e4 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -56,7 +56,7 @@
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
refreshConfigurationLocked(0);
updateGlobalMetaStateLocked();
@@ -87,8 +87,9 @@
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
+ std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
@@ -107,8 +108,8 @@
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
- AutoMutex _l(mLock);
- mReaderIsAliveCondition.broadcast();
+ std::scoped_lock _l(mLock);
+ mReaderIsAliveCondition.notify_all();
if (count) {
processEventsLocked(mEventBuffer, count);
@@ -127,12 +128,13 @@
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
+ inputDevices = getInputDevicesLocked();
}
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
- mPolicy->notifyInputDevicesChanged(getInputDevicesLocked());
+ mPolicy->notifyInputDevicesChanged(inputDevices);
}
// Flush queued events out to the listener.
@@ -216,7 +218,14 @@
bumpGenerationLocked();
if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
- notifyExternalStylusPresenceChanged();
+ 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);
}
}
@@ -256,7 +265,7 @@
device->removeEventHubDevice(eventHubId);
if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
- notifyExternalStylusPresenceChanged();
+ notifyExternalStylusPresenceChangedLocked();
}
if (device->hasEventHubDevices()) {
@@ -301,7 +310,7 @@
device->process(rawEvents, count);
}
-InputDevice* InputReader::findInputDevice(int32_t deviceId) {
+InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) {
auto deviceIt =
std::find_if(mDevices.begin(), mDevices.end(), [deviceId](const auto& devicePair) {
return devicePair.second->getId() == deviceId;
@@ -330,8 +339,7 @@
updateGlobalMetaStateLocked();
// Enqueue configuration changed.
- NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
- mQueuedListener->notifyConfigurationChanged(&args);
+ mContext.notifyConfigurationChanged(when);
}
void InputReader::refreshConfigurationLocked(uint32_t changes) {
@@ -358,9 +366,7 @@
}
if (changes & InputReaderConfiguration::CHANGE_POINTER_CAPTURE) {
- const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
- mConfig.pointerCapture);
- mQueuedListener->notifyPointerCaptureChanged(&args);
+ mContext.notifyPointerCaptureChanged(now, mConfig.pointerCapture);
}
}
@@ -389,7 +395,7 @@
return mLedMetaState;
}
-void InputReader::notifyExternalStylusPresenceChanged() {
+void InputReader::notifyExternalStylusPresenceChangedLocked() {
refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
}
@@ -405,6 +411,7 @@
}
void InputReader::dispatchExternalStylusState(const StylusState& state) {
+ std::scoped_lock _l(mLock);
for (auto& devicePair : mDevices) {
std::shared_ptr<InputDevice>& device = devicePair.second;
device->updateExternalStylusState(state);
@@ -479,7 +486,7 @@
}
std::vector<InputDeviceInfo> InputReader::getInputDevices() const {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
return getInputDevicesLocked();
}
@@ -498,19 +505,19 @@
}
int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
}
int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
}
int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
}
@@ -519,7 +526,7 @@
GetStateFunc getStateFunc) {
int32_t result = AKEY_STATE_UNKNOWN;
if (deviceId >= 0) {
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
result = (device->*getStateFunc)(sourceMask, code);
}
@@ -542,7 +549,8 @@
}
void InputReader::toggleCapsLockState(int32_t deviceId) {
- InputDevice* device = findInputDevice(deviceId);
+ std::scoped_lock _l(mLock);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (!device) {
ALOGW("Ignoring toggleCapsLock for unknown deviceId %" PRId32 ".", deviceId);
return;
@@ -557,7 +565,7 @@
bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
memset(outFlags, 0, numCodes);
return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
@@ -568,7 +576,7 @@
uint8_t* outFlags) {
bool result = false;
if (deviceId >= 0) {
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
}
@@ -584,7 +592,7 @@
}
void InputReader::requestRefreshConfiguration(uint32_t changes) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
if (changes) {
bool needWake = !mConfigurationChangesToRefresh;
@@ -596,28 +604,79 @@
}
}
-void InputReader::vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern,
- ssize_t repeat, int32_t token) {
- AutoMutex _l(mLock);
- InputDevice* device = findInputDevice(deviceId);
+void InputReader::vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
- device->vibrate(pattern, repeat, token);
+ device->vibrate(sequence, repeat, token);
}
}
void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
device->cancelVibrate(token);
}
}
-bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
- AutoMutex _l(mLock);
+bool InputReader::isVibrating(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->isVibrating();
+ }
+ return false;
+}
+
+std::vector<int32_t> InputReader::getVibratorIds(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->getVibratorIds();
+ }
+ 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);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
return device->isEnabled();
}
@@ -626,9 +685,9 @@
}
bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (!device) {
ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
return false;
@@ -654,7 +713,7 @@
}
void InputReader::dump(std::string& dump) {
- AutoMutex _l(mLock);
+ std::scoped_lock _l(mLock);
mEventHub->dump(dump);
dump += "\n";
@@ -729,11 +788,9 @@
void InputReader::monitor() {
// Acquire and release the lock to ensure that the reader has not deadlocked.
- mLock.lock();
+ std::unique_lock<std::mutex> lock(mLock);
mEventHub->wake();
- mReaderIsAliveCondition.wait(mLock);
- mLock.unlock();
-
+ mReaderIsAliveCondition.wait(lock);
// Check the EventHub
mEventHub->monitor();
}
@@ -808,16 +865,64 @@
return mReader->mPolicy.get();
}
-InputListenerInterface* InputReader::ContextImpl::getListener() {
- return mReader->mQueuedListener.get();
-}
-
EventHubInterface* InputReader::ContextImpl::getEventHub() {
return mReader->mEventHub.get();
}
-int32_t InputReader::ContextImpl::getNextId() {
- return mIdGenerator.nextId();
+void InputReader::ContextImpl::notifyConfigurationChanged(nsecs_t when) {
+ NotifyConfigurationChangedArgs args(mIdGenerator.nextId(), when);
+ mReader->mQueuedListener->notifyConfigurationChanged(&args);
+}
+
+void InputReader::ContextImpl::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ int32_t displayId, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime) {
+ NotifyKeyArgs args(mIdGenerator.nextId(), eventTime, deviceId, source, displayId, policyFlags,
+ action, flags, keyCode, scanCode, metaState, downTime);
+ mReader->mQueuedListener->notifyKey(&args);
+}
+void InputReader::ContextImpl::notifyMotion(
+ nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+ uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+ int32_t metaState, int32_t buttonState, MotionClassification classification,
+ int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
+ float xCursorPosition, float yCursorPosition, nsecs_t downTime,
+ const std::vector<TouchVideoFrame>& videoFrames) {
+ NotifyMotionArgs args(mIdGenerator.nextId(), eventTime, deviceId, source, displayId,
+ policyFlags, action, actionButton, flags, metaState, buttonState,
+ classification, edgeFlags, pointerCount, pointerProperties, pointerCoords,
+ xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime,
+ videoFrames);
+ mReader->mQueuedListener->notifyMotion(&args);
+}
+
+void InputReader::ContextImpl::notifySensor(nsecs_t when, int32_t deviceId,
+ InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy,
+ bool accuracyChanged, nsecs_t timestamp,
+ std::vector<float> values) {
+ NotifySensorArgs args(mIdGenerator.nextId(), when, deviceId, AINPUT_SOURCE_SENSOR, sensorType,
+ accuracy, accuracyChanged, timestamp, std::move(values));
+ mReader->mQueuedListener->notifySensor(&args);
+}
+
+void InputReader::ContextImpl::notifySwitch(nsecs_t eventTime, uint32_t switchValues,
+ uint32_t switchMask) {
+ NotifySwitchArgs args(mIdGenerator.nextId(), eventTime, 0 /*policyFlags*/, switchValues,
+ switchMask);
+ mReader->mQueuedListener->notifySwitch(&args);
+}
+
+void InputReader::ContextImpl::notifyDeviceReset(nsecs_t when, int32_t deviceId) {
+ NotifyDeviceResetArgs args(mIdGenerator.nextId(), when, deviceId);
+ mReader->mQueuedListener->notifyDeviceReset(&args);
+}
+
+void InputReader::ContextImpl::notifyPointerCaptureChanged(nsecs_t when, bool hasCapture) {
+ const NotifyPointerCaptureChangedArgs args(mIdGenerator.nextId(), when, hasCapture);
+ mReader->mQueuedListener->notifyPointerCaptureChanged(&args);
}
} // namespace android
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index edb82d3..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.
@@ -233,6 +240,7 @@
/* Control the vibrator. */
virtual void vibrate(int32_t deviceId, const VibrationElement& effect) = 0;
virtual void cancelVibrate(int32_t deviceId) = 0;
+ virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
/* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
virtual void requestReopenDevices() = 0;
@@ -345,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;
@@ -352,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;
@@ -381,6 +394,7 @@
void vibrate(int32_t deviceId, const VibrationElement& effect) override final;
void cancelVibrate(int32_t deviceId) override final;
+ std::vector<int32_t> getVibratorIds(int32_t deviceId) override final;
void requestReopenDevices() override final;
@@ -418,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;
@@ -502,7 +517,7 @@
void releaseControllerNumberLocked(int32_t num);
// Protect all internal state.
- mutable Mutex mLock;
+ mutable std::mutex mLock;
// The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
// EventHub remaps the built-in keyboard to id 0 externally as required by the API.
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 7d160eb..5af76b7 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -82,9 +82,15 @@
int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags);
- void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat, int32_t token);
+ void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
void cancelVibrate(int32_t token);
+ 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);
@@ -227,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,
@@ -238,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);
@@ -272,6 +285,8 @@
}
inline void cancelVibrate() { return mEventHub->cancelVibrate(mId); }
+ inline std::vector<int32_t> getVibratorIds() { return mEventHub->getVibratorIds(mId); }
+
inline bool hasAbsoluteAxis(int32_t code) const {
RawAbsoluteAxisInfo info;
mEventHub->getAbsoluteAxisInfo(mId, code, &info);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 563018a..7be932a 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -18,6 +18,7 @@
#define _UI_INPUTREADER_INPUT_READER_H
#include <PointerControllerInterface.h>
+#include <android-base/thread_annotations.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
@@ -76,16 +77,29 @@
void requestRefreshConfiguration(uint32_t changes) override;
- void vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern, ssize_t repeat,
+ void vibrate(int32_t deviceId, const VibrationSequence& sequence, ssize_t repeat,
int32_t token) override;
void cancelVibrate(int32_t deviceId, int32_t token) override;
+ bool isVibrating(int32_t deviceId) override;
+
+ std::vector<int32_t> getVibratorIds(int32_t deviceId) override;
+
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, const InputDeviceIdentifier& identifier);
+ virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier)
+ REQUIRES(mLock);
// With each iteration of the loop, InputReader reads and processes one incoming message from
// the EventHub.
@@ -97,33 +111,56 @@
public:
explicit ContextImpl(InputReader* reader);
+ // lock is already held by the input loop
+ void updateGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t getGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+ void disableVirtualKeysUntil(nsecs_t time) NO_THREAD_SAFETY_ANALYSIS override;
+ bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode,
+ int32_t scanCode) NO_THREAD_SAFETY_ANALYSIS override;
+ void fadePointer() NO_THREAD_SAFETY_ANALYSIS override;
+ std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ void requestTimeoutAtTime(nsecs_t when) NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
+ void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ void dispatchExternalStylusState(const StylusState& outState)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ InputReaderPolicyInterface* getPolicy() NO_THREAD_SAFETY_ANALYSIS override;
+ EventHubInterface* getEventHub() NO_THREAD_SAFETY_ANALYSIS override;
+ void updateLedMetaState(int32_t metaState) NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t getLedMetaState() NO_THREAD_SAFETY_ANALYSIS override;
- void updateGlobalMetaState() override;
- int32_t getGlobalMetaState() override;
- void disableVirtualKeysUntil(nsecs_t time) override;
- bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override;
- void fadePointer() override;
- std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override;
- void requestTimeoutAtTime(nsecs_t when) override;
- int32_t bumpGeneration() override;
- void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override;
- void dispatchExternalStylusState(const StylusState& outState) override;
- InputReaderPolicyInterface* getPolicy() override;
- InputListenerInterface* getListener() override;
- EventHubInterface* getEventHub() override;
- int32_t getNextId() override;
- void updateLedMetaState(int32_t metaState) override;
- int32_t getLedMetaState() override;
+ // Send events to InputListener interface
+ void notifyConfigurationChanged(nsecs_t when) override;
+ void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+ int32_t scanCode, int32_t metaState, nsecs_t downTime) override;
+ void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+ uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+ int32_t metaState, int32_t buttonState,
+ MotionClassification classification, int32_t edgeFlags,
+ uint32_t pointerCount, const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
+ float xCursorPosition, float yCursorPosition, nsecs_t downTime,
+ const std::vector<TouchVideoFrame>& videoFrames) override;
+ void notifySwitch(nsecs_t eventTime, uint32_t switchValues, uint32_t switchMask) override;
+ void notifySensor(nsecs_t when, int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
+ nsecs_t timestamp, std::vector<float> values) override;
+ void notifyDeviceReset(nsecs_t when, int32_t deviceId) override;
+ void notifyPointerCaptureChanged(nsecs_t when, bool hasCapture) override;
+
} mContext;
friend class ContextImpl;
+ // Test cases need to override the locked functions
+ mutable std::mutex mLock;
private:
std::unique_ptr<InputThread> mThread;
- mutable Mutex mLock;
-
- Condition mReaderIsAliveCondition;
+ std::condition_variable mReaderIsAliveCondition;
// This could be unique_ptr, but due to the way InputReader tests are written,
// it is made shared_ptr here. In the tests, an EventHub reference is retained by the test
@@ -132,76 +169,79 @@
sp<InputReaderPolicyInterface> mPolicy;
sp<QueuedInputListener> mQueuedListener;
- InputReaderConfiguration mConfig;
+ InputReaderConfiguration mConfig GUARDED_BY(mLock);
// The event queue.
static const int EVENT_BUFFER_SIZE = 256;
- RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+ RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);
// An input device can represent a collection of EventHub devices. This map provides a way
// to lookup the input device instance from the EventHub device id.
- std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices;
+ std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices
+ GUARDED_BY(mLock);
// An input device contains one or more eventHubId, this map provides a way to lookup the
// EventHubIds contained in the input device from the input device instance.
std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
- mDeviceToEventHubIdsMap;
+ mDeviceToEventHubIdsMap GUARDED_BY(mLock);
// low-level input event decoding and device management
- void processEventsLocked(const RawEvent* rawEvents, size_t count);
+ void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock);
- void addDeviceLocked(nsecs_t when, int32_t eventHubId);
- void removeDeviceLocked(nsecs_t when, int32_t eventHubId);
- void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count);
- void timeoutExpiredLocked(nsecs_t when);
+ void addDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+ void removeDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+ void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count)
+ REQUIRES(mLock);
+ void timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock);
- void handleConfigurationChangedLocked(nsecs_t when);
+ void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock);
- int32_t mGlobalMetaState;
- void updateGlobalMetaStateLocked();
- int32_t getGlobalMetaStateLocked();
+ int32_t mGlobalMetaState GUARDED_BY(mLock);
+ void updateGlobalMetaStateLocked() REQUIRES(mLock);
+ int32_t getGlobalMetaStateLocked() REQUIRES(mLock);
- int32_t mLedMetaState;
- void updateLedMetaStateLocked(int32_t metaState);
- int32_t getLedMetaStateLocked();
+ int32_t mLedMetaState GUARDED_BY(mLock);
+ void updateLedMetaStateLocked(int32_t metaState) REQUIRES(mLock);
+ int32_t getLedMetaStateLocked() REQUIRES(mLock);
- void notifyExternalStylusPresenceChanged();
- void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices);
+ void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock);
+ void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) REQUIRES(mLock);
void dispatchExternalStylusState(const StylusState& state);
// The PointerController that is shared among all the input devices that need it.
std::weak_ptr<PointerControllerInterface> mPointerController;
- std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId);
- void updatePointerDisplayLocked();
- void fadePointerLocked();
+ std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
+ REQUIRES(mLock);
+ void updatePointerDisplayLocked() REQUIRES(mLock);
+ void fadePointerLocked() REQUIRES(mLock);
- int32_t mGeneration;
- int32_t bumpGenerationLocked();
+ int32_t mGeneration GUARDED_BY(mLock);
+ int32_t bumpGenerationLocked() REQUIRES(mLock);
- int32_t mNextInputDeviceId;
- int32_t nextInputDeviceIdLocked();
+ int32_t mNextInputDeviceId GUARDED_BY(mLock);
+ int32_t nextInputDeviceIdLocked() REQUIRES(mLock);
- std::vector<InputDeviceInfo> getInputDevicesLocked() const;
+ std::vector<InputDeviceInfo> getInputDevicesLocked() const REQUIRES(mLock);
- nsecs_t mDisableVirtualKeysTimeout;
- void disableVirtualKeysUntilLocked(nsecs_t time);
- bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode);
+ nsecs_t mDisableVirtualKeysTimeout GUARDED_BY(mLock);
+ void disableVirtualKeysUntilLocked(nsecs_t time) REQUIRES(mLock);
+ bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode) REQUIRES(mLock);
- nsecs_t mNextTimeout;
- void requestTimeoutAtTimeLocked(nsecs_t when);
+ nsecs_t mNextTimeout GUARDED_BY(mLock);
+ void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock);
- uint32_t mConfigurationChangesToRefresh;
- void refreshConfigurationLocked(uint32_t changes);
+ uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock);
+ void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock);
// state queries
typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
- GetStateFunc getStateFunc);
+ GetStateFunc getStateFunc) REQUIRES(mLock);
bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
- const int32_t* keyCodes, uint8_t* outFlags);
+ const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock);
// find an InputDevice from an InputDevice id
- InputDevice* findInputDevice(int32_t deviceId);
+ InputDevice* findInputDeviceLocked(int32_t deviceId) REQUIRES(mLock);
};
} // namespace android
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index dc807f7..edab312 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -55,13 +55,33 @@
virtual void dispatchExternalStylusState(const StylusState& outState) = 0;
virtual InputReaderPolicyInterface* getPolicy() = 0;
- virtual InputListenerInterface* getListener() = 0;
virtual EventHubInterface* getEventHub() = 0;
- virtual int32_t getNextId() = 0;
-
virtual void updateLedMetaState(int32_t metaState) = 0;
virtual int32_t getLedMetaState() = 0;
+
+ // Send events to InputListener interface
+
+ virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+ virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+ int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
+ virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ int32_t displayId, uint32_t policyFlags, int32_t action,
+ int32_t actionButton, int32_t flags, int32_t metaState,
+ int32_t buttonState, MotionClassification classification,
+ int32_t edgeFlags, uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, float xPrecision,
+ float yPrecision, float xCursorPosition, float yCursorPosition,
+ nsecs_t downTime,
+ const std::vector<TouchVideoFrame>& videoFrames) = 0;
+ virtual void notifySwitch(nsecs_t eventTime, uint32_t switchValues, uint32_t switchMask) = 0;
+ virtual void notifySensor(nsecs_t when, int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
+ nsecs_t timestamp, std::vector<float> values) = 0;
+ virtual void notifyDeviceReset(nsecs_t when, int32_t deviceId) = 0;
+ virtual void notifyPointerCaptureChanged(nsecs_t when, bool hasCapture) = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 254b64b..7f7b33c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -175,8 +175,7 @@
}
bumpGeneration();
if (changes) {
- NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
- getListener()->notifyDeviceReset(&args);
+ getContext()->notifyDeviceReset(when, getDeviceId());
}
}
@@ -383,40 +382,35 @@
while (!released.isEmpty()) {
int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
buttonState &= ~actionButton;
- NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, getDeviceId(),
- mSource, displayId, policyFlags,
- AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
- metaState, buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
- &pointerCoords, mXPrecision, mYPrecision,
- xCursorPosition, yCursorPosition, downTime,
- /* videoFrames */ {});
- getListener()->notifyMotion(&releaseArgs);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
+ metaState, buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+ &pointerCoords, mXPrecision, mYPrecision,
+ xCursorPosition, yCursorPosition, downTime,
+ /* videoFrames */ {});
}
}
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, motionEventAction, 0, 0, metaState, currentButtonState,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
- &pointerProperties, &pointerCoords, mXPrecision, mYPrecision,
- xCursorPosition, yCursorPosition, downTime,
- /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ motionEventAction, 0, 0, metaState, currentButtonState,
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &pointerProperties, &pointerCoords, mXPrecision, mYPrecision,
+ xCursorPosition, yCursorPosition, downTime,
+ /* videoFrames */ {});
if (buttonsPressed) {
BitSet32 pressed(buttonsPressed);
while (!pressed.isEmpty()) {
int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
buttonState |= actionButton;
- NotifyMotionArgs pressArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags,
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
metaState, buttonState, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
&pointerCoords, mXPrecision, mYPrecision,
xCursorPosition, yCursorPosition, downTime,
/* videoFrames */ {});
- getListener()->notifyMotion(&pressArgs);
}
}
@@ -424,13 +418,12 @@
// Send hover move after UP to tell the application that the mouse is hovering now.
if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
- NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
- 0, metaState, currentButtonState, MotionClassification::NONE,
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
+ currentButtonState, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
&pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
yCursorPosition, downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&hoverArgs);
}
// Send scroll events.
@@ -438,13 +431,12 @@
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
- NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
- metaState, currentButtonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
- &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
- yCursorPosition, downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&scrollArgs);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+ currentButtonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+ &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
+ yCursorPosition, downTime, /* videoFrames */ {});
}
}
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 1db829f..1ce54ae 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -56,13 +56,30 @@
return false;
}
-void InputMapper::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
- int32_t token) {}
+void InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {}
void InputMapper::cancelVibrate(int32_t token) {}
+bool InputMapper::isVibrating() {
+ return false;
+}
+
+std::vector<int32_t> InputMapper::getVibratorIds() {
+ return {};
+}
+
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 56ab928..6ca6ec9 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -19,7 +19,6 @@
#include "EventHub.h"
#include "InputDevice.h"
-#include "InputListener.h"
#include "InputReaderContext.h"
#include "StylusState.h"
#include "VibrationElement.h"
@@ -48,7 +47,6 @@
inline const std::string getDeviceName() { return mDeviceContext.getName(); }
inline InputReaderContext* getContext() { return mDeviceContext.getContext(); }
inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); }
- inline InputListenerInterface* getListener() { return getContext()->getListener(); }
virtual uint32_t getSources() = 0;
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
@@ -63,10 +61,16 @@
virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
- virtual void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
- int32_t token);
+ virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
virtual void cancelVibrate(int32_t token);
+ 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/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index abd8aa9..ac4669c 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -337,13 +337,12 @@
// TODO: Use the input device configuration to control this behavior more finely.
uint32_t policyFlags = 0;
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_JOYSTICK,
- ADISPLAY_ID_NONE, policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
- buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
- &pointerProperties, &pointerCoords, 0, 0,
- AMOTION_EVENT_INVALID_CURSOR_POSITION,
- AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, ADISPLAY_ID_NONE,
+ policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState,
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &pointerProperties, &pointerCoords, 0, 0,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
}
void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 8b9f235..03d7405 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -350,10 +350,9 @@
policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
}
- NotifyKeyArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, getDisplayId(),
- policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
- AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
- getListener()->notifyKey(&args);
+ getContext()->notifyKey(when, getDeviceId(), mSource, getDisplayId(), policyFlags,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
}
ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 594ff42..3f8a364 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -121,13 +121,12 @@
int32_t metaState = getContext()->getGlobalMetaState();
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
- NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
- metaState, /* buttonState */ 0, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
- &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
- AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
- getListener()->notifyMotion(&scrollArgs);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+ /* buttonState */ 0, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
+ &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
}
mRotaryEncoderScrollAccumulator.finishSync();
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
new file mode 100644
index 0000000..68c1e40
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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
+ getContext()->notifySensor(when, getDeviceId(), sensorType, sensor.sensorInfo.accuracy,
+ sensor.accuracy !=
+ sensor.sensorInfo.accuracy /* accuracyChanged */,
+ timestamp /* hwTimestamp */, std::move(values));
+ 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/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 4f73681..07de244 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -56,10 +56,7 @@
void SwitchInputMapper::sync(nsecs_t when) {
if (mUpdatedSwitchMask) {
uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
- NotifySwitchArgs args(getContext()->getNextId(), when, 0 /*policyFlags*/,
- updatedSwitchValues, mUpdatedSwitchMask);
- getListener()->notifySwitch(&args);
-
+ getContext()->notifySwitch(when, updatedSwitchValues, mUpdatedSwitchMask);
mUpdatedSwitchMask = 0;
}
}
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index a86443d..ff6341f 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -66,9 +66,8 @@
(currentButtonState & buttonState)) ||
(action == AKEY_EVENT_ACTION_UP && (lastButtonState & buttonState) &&
!(currentButtonState & buttonState))) {
- NotifyKeyArgs args(context->getNextId(), when, deviceId, source, displayId, policyFlags,
- action, 0, keyCode, 0, context->getGlobalMetaState(), when);
- context->getListener()->notifyKey(&args);
+ context->notifyKey(when, deviceId, source, displayId, policyFlags, action, 0 /*flags*/,
+ keyCode, 0 /*scanCode*/, context->getGlobalMetaState(), when);
}
}
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index b620e2d..fb30b88 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -397,8 +397,7 @@
if (changes && resetNeeded) {
// Send reset, unless this is the first time the device has been configured,
// in which case the reader will call reset itself after all mappers are ready.
- NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
- getListener()->notifyDeviceReset(&args);
+ getContext()->notifyDeviceReset(when, getDeviceId());
}
}
@@ -647,6 +646,13 @@
return;
}
+ if (!newViewport->isActive) {
+ ALOGI("Disabling %s (device %i) because the associated viewport is not active",
+ getDeviceName().c_str(), getDeviceId());
+ mDeviceMode = DeviceMode::DISABLED;
+ return;
+ }
+
// Raw width and height in the natural orientation.
int32_t rawWidth = mRawPointerAxes.getRawWidth();
int32_t rawHeight = mRawPointerAxes.getRawHeight();
@@ -1822,10 +1828,9 @@
int32_t metaState = getContext()->getGlobalMetaState();
policyFlags |= POLICY_FLAG_VIRTUAL;
- NotifyKeyArgs args(getContext()->getNextId(), when, getDeviceId(), AINPUT_SOURCE_KEYBOARD,
- mViewport.displayId, policyFlags, keyEventAction, keyEventFlags, keyCode,
- scanCode, metaState, downTime);
- getListener()->notifyKey(&args);
+ getContext()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, mViewport.displayId,
+ policyFlags, keyEventAction, keyEventFlags, keyCode, scanCode,
+ metaState, downTime);
}
void TouchInputMapper::abortTouches(nsecs_t when, uint32_t policyFlags) {
@@ -2501,12 +2506,11 @@
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
const int32_t displayId = mPointerController->getDisplayId();
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
- buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
- 1, &pointerProperties, &pointerCoords, 0, 0, x, y,
- mPointerGesture.downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState, buttonState,
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &pointerProperties, &pointerCoords, 0, 0, x, y,
+ mPointerGesture.downTime, /* videoFrames */ {});
}
// Update state.
@@ -3421,28 +3425,28 @@
mPointerSimple.down = false;
// Send up.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_UP, 0, 0, metaState,
- mLastRawState.buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
- &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
- xCursorPosition, yCursorPosition, mPointerSimple.downTime,
- /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_UP, 0, 0, metaState,
+ mLastRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+ &mPointerSimple.lastCoords, mOrientedXPrecision,
+ mOrientedYPrecision, xCursorPosition, yCursorPosition,
+ mPointerSimple.downTime,
+ /* videoFrames */ {});
}
if (mPointerSimple.hovering && !hovering) {
mPointerSimple.hovering = false;
// Send hover exit.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
- mLastRawState.buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
- &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
- xCursorPosition, yCursorPosition, mPointerSimple.downTime,
- /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, 0, metaState,
+ mLastRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties,
+ &mPointerSimple.lastCoords, mOrientedXPrecision,
+ mOrientedYPrecision, xCursorPosition, yCursorPosition,
+ mPointerSimple.downTime,
+ /* videoFrames */ {});
}
if (down) {
@@ -3451,25 +3455,24 @@
mPointerSimple.downTime = when;
// Send down.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags, AMOTION_EVENT_ACTION_DOWN, 0, 0,
- metaState, mCurrentRawState.buttonState,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
- &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
- mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
- yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, metaState,
+ mCurrentRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.currentProperties,
+ &mPointerSimple.currentCoords, mOrientedXPrecision,
+ mOrientedYPrecision, xCursorPosition, yCursorPosition,
+ mPointerSimple.downTime, /* videoFrames */ {});
}
// Send move.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
- mCurrentRawState.buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
- &mPointerSimple.currentCoords, mOrientedXPrecision,
- mOrientedYPrecision, xCursorPosition, yCursorPosition,
- mPointerSimple.downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState,
+ mCurrentRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
+ yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
}
if (hovering) {
@@ -3477,25 +3480,24 @@
mPointerSimple.hovering = true;
// Send hover enter.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource,
- displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0,
- metaState, mCurrentRawState.buttonState,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
- &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
- mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
- yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, 0, metaState,
+ mCurrentRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.currentProperties,
+ &mPointerSimple.currentCoords, mOrientedXPrecision,
+ mOrientedYPrecision, xCursorPosition, yCursorPosition,
+ mPointerSimple.downTime, /* videoFrames */ {});
}
// Send hover move.
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
- mCurrentRawState.buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
- &mPointerSimple.currentCoords, mOrientedXPrecision,
- mOrientedYPrecision, xCursorPosition, yCursorPosition,
- mPointerSimple.downTime, /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0, metaState,
+ mCurrentRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
+ yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
}
if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) {
@@ -3510,14 +3512,14 @@
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
- NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
- policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
- mCurrentRawState.buttonState, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.currentProperties,
- &pointerCoords, mOrientedXPrecision, mOrientedYPrecision,
- xCursorPosition, yCursorPosition, mPointerSimple.downTime,
- /* videoFrames */ {});
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, getDeviceId(), mSource, displayId, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState,
+ mCurrentRawState.buttonState, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.currentProperties, &pointerCoords,
+ mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
+ yCursorPosition, mPointerSimple.downTime,
+ /* videoFrames */ {});
}
// Save state.
@@ -3588,12 +3590,11 @@
std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
std::for_each(frames.begin(), frames.end(),
[this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
- NotifyMotionArgs args(getContext()->getNextId(), when, deviceId, source, displayId, policyFlags,
- action, actionButton, flags, metaState, buttonState,
- MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
- pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
- downTime, std::move(frames));
- getListener()->notifyMotion(&args);
+ getContext()->notifyMotion(when, deviceId, source, displayId, policyFlags, action, actionButton,
+ flags, metaState, buttonState, MotionClassification::NONE, edgeFlags,
+ pointerCount, pointerProperties, pointerCoords, xPrecision,
+ yPrecision, xCursorPosition, yCursorPosition, downTime,
+ std::move(frames));
}
bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
@@ -3637,6 +3638,9 @@
const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale;
const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale;
+ const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale;
+ const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale;
+
// Rotate to surface coordinate.
// 0 - no swap and reverse.
// 90 - swap x/y and reverse y.
@@ -3648,16 +3652,16 @@
y = yScaled + mYTranslate;
break;
case DISPLAY_ORIENTATION_90:
- y = mSurfaceRight - xScaled;
+ y = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
x = yScaled + mYTranslate;
break;
case DISPLAY_ORIENTATION_180:
- x = mSurfaceRight - xScaled;
- y = mSurfaceBottom - yScaled;
+ x = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
+ y = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
break;
case DISPLAY_ORIENTATION_270:
y = xScaled + mXTranslate;
- x = mSurfaceBottom - yScaled;
+ x = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
break;
default:
assert(false);
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index ac7c266..f25e59a 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -21,7 +21,7 @@
namespace android {
VibratorInputMapper::VibratorInputMapper(InputDeviceContext& deviceContext)
- : InputMapper(deviceContext), mVibrating(false) {}
+ : InputMapper(deviceContext), mVibrating(false), mSequence(0) {}
VibratorInputMapper::~VibratorInputMapper() {}
@@ -39,17 +39,15 @@
// TODO: Handle FF_STATUS, although it does not seem to be widely supported.
}
-void VibratorInputMapper::vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
+void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat,
int32_t token) {
#if DEBUG_VIBRATOR
- std::string patternStr;
- dumpPattern(patternStr);
ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
- patternStr.c_str(), repeat, token);
+ sequence.toString().c_str(), repeat, token);
#endif
mVibrating = true;
- mPattern = pattern;
+ mSequence = sequence;
mRepeat = repeat;
mToken = token;
mIndex = -1;
@@ -67,6 +65,14 @@
}
}
+bool VibratorInputMapper::isVibrating() {
+ return mVibrating;
+}
+
+std::vector<int32_t> VibratorInputMapper::getVibratorIds() {
+ return getDeviceContext().getVibratorIds();
+}
+
void VibratorInputMapper::timeoutExpired(nsecs_t when) {
if (mVibrating) {
if (when >= mNextStepTime) {
@@ -79,7 +85,7 @@
void VibratorInputMapper::nextStep() {
mIndex += 1;
- if (size_t(mIndex) >= mPattern.size()) {
+ if (size_t(mIndex) >= mSequence.pattern.size()) {
if (mRepeat < 0) {
// We are done.
stopVibrating();
@@ -88,7 +94,7 @@
mIndex = mRepeat;
}
- const VibrationElement& element = mPattern[mIndex];
+ const VibrationElement& element = mSequence.pattern[mIndex];
if (element.isOn()) {
#if DEBUG_VIBRATOR
std::string description = element.toString();
@@ -125,23 +131,10 @@
dump += StringPrintf(INDENT3 "Vibrating: %s\n", toString(mVibrating));
if (mVibrating) {
dump += INDENT3 "Pattern: ";
- dumpPattern(dump);
+ dump += mSequence.toString();
dump += "\n";
dump += StringPrintf(INDENT3 "Repeat Index: %zd\n", mRepeat);
}
}
-void VibratorInputMapper::dumpPattern(std::string& dump) const {
- dump += "[";
-
- for (auto it = mPattern.begin(); it != mPattern.end(); ++it) {
- dump += it->toString();
- if (std::next(it) != mPattern.end()) {
- dump += ", ";
- }
- }
-
- dump += "]";
-}
-
} // namespace android
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index bfa5ec1..7ce621a 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -30,21 +30,21 @@
virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
virtual void process(const RawEvent* rawEvent) override;
- virtual void vibrate(const std::vector<VibrationElement>& pattern, ssize_t repeat,
- int32_t token) override;
+ virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) override;
virtual void cancelVibrate(int32_t token) override;
+ virtual bool isVibrating() override;
+ virtual std::vector<int32_t> getVibratorIds() override;
virtual void timeoutExpired(nsecs_t when) override;
virtual void dump(std::string& dump) override;
private:
bool mVibrating;
- std::vector<VibrationElement> mPattern;
+ VibrationSequence mSequence;
ssize_t mRepeat;
int32_t mToken;
ssize_t mIndex;
nsecs_t mNextStepTime;
- void dumpPattern(std::string& dump) const;
void nextStep();
void stopVibrating();
};
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 5ab2ae3..c7e05eb 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,
@@ -139,27 +140,49 @@
ASSERT_EQ(expectedApplication, application);
}
- void assertNotifyConnectionUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
- const sp<IBinder>& expectedConnectionToken) {
- sp<IBinder> connectionToken = getUnresponsiveConnectionToken(timeout);
+ void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+ const sp<IBinder>& expectedConnectionToken) {
+ sp<IBinder> connectionToken = getUnresponsiveWindowToken(timeout);
ASSERT_EQ(expectedConnectionToken, connectionToken);
}
- void assertNotifyConnectionResponsiveWasCalled(const sp<IBinder>& expectedConnectionToken) {
- sp<IBinder> connectionToken = getResponsiveConnectionToken();
+ void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedConnectionToken) {
+ sp<IBinder> connectionToken = getResponsiveWindowToken();
ASSERT_EQ(expectedConnectionToken, connectionToken);
}
- sp<IBinder> getUnresponsiveConnectionToken(std::chrono::nanoseconds timeout) {
- std::unique_lock lock(mLock);
- android::base::ScopedLockAssertion assumeLocked(mLock);
- return getAnrTokenLockedInterruptible(timeout, mAnrConnectionTokens, lock);
+ void assertNotifyMonitorUnresponsiveWasCalled(std::chrono::nanoseconds timeout) {
+ int32_t pid = getUnresponsiveMonitorPid(timeout);
+ ASSERT_EQ(MONITOR_PID, pid);
}
- sp<IBinder> getResponsiveConnectionToken() {
+ void assertNotifyMonitorResponsiveWasCalled() {
+ int32_t pid = getResponsiveMonitorPid();
+ ASSERT_EQ(MONITOR_PID, pid);
+ }
+
+ sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) {
std::unique_lock lock(mLock);
android::base::ScopedLockAssertion assumeLocked(mLock);
- return getAnrTokenLockedInterruptible(0s, mResponsiveConnectionTokens, lock);
+ return getAnrTokenLockedInterruptible(timeout, mAnrWindowTokens, lock);
+ }
+
+ sp<IBinder> getResponsiveWindowToken() {
+ std::unique_lock lock(mLock);
+ android::base::ScopedLockAssertion assumeLocked(mLock);
+ return getAnrTokenLockedInterruptible(0s, mResponsiveWindowTokens, lock);
+ }
+
+ int32_t getUnresponsiveMonitorPid(std::chrono::nanoseconds timeout) {
+ std::unique_lock lock(mLock);
+ android::base::ScopedLockAssertion assumeLocked(mLock);
+ return getAnrTokenLockedInterruptible(timeout, mAnrMonitorPids, lock);
+ }
+
+ int32_t getResponsiveMonitorPid() {
+ std::unique_lock lock(mLock);
+ android::base::ScopedLockAssertion assumeLocked(mLock);
+ return getAnrTokenLockedInterruptible(0s, mResponsiveMonitorPids, lock);
}
// All three ANR-related callbacks behave the same way, so we use this generic function to wait
@@ -181,7 +204,7 @@
const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
if (storage.empty()) {
ADD_FAILURE() << "Did not receive the ANR callback";
- return nullptr;
+ return {};
}
// Ensure that the ANR didn't get raised too early. We can't be too strict here because
// the dispatcher started counting before this function was called
@@ -200,10 +223,14 @@
void assertNotifyAnrWasNotCalled() {
std::scoped_lock lock(mLock);
ASSERT_TRUE(mAnrApplications.empty());
- ASSERT_TRUE(mAnrConnectionTokens.empty());
- ASSERT_TRUE(mResponsiveConnectionTokens.empty())
+ ASSERT_TRUE(mAnrWindowTokens.empty());
+ ASSERT_TRUE(mAnrMonitorPids.empty());
+ ASSERT_TRUE(mResponsiveWindowTokens.empty())
<< "ANR was not called, but please also consume the 'connection is responsive' "
"signal";
+ ASSERT_TRUE(mResponsiveMonitorPids.empty())
+ << "Monitor ANR was not called, but please also consume the 'monitor is responsive'"
+ " signal";
}
void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
@@ -211,6 +238,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,10 +272,15 @@
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);
- std::queue<sp<IBinder>> mResponsiveConnectionTokens GUARDED_BY(mLock);
+ std::queue<sp<IBinder>> mAnrWindowTokens GUARDED_BY(mLock);
+ std::queue<sp<IBinder>> mResponsiveWindowTokens GUARDED_BY(mLock);
+ std::queue<int32_t> mAnrMonitorPids GUARDED_BY(mLock);
+ std::queue<int32_t> mResponsiveMonitorPids GUARDED_BY(mLock);
std::condition_variable mNotifyAnr;
void notifyConfigurationChanged(nsecs_t when) override {
@@ -229,16 +288,27 @@
mConfigurationChangedTime = when;
}
- void notifyConnectionUnresponsive(const sp<IBinder>& connectionToken,
- const std::string&) override {
+ void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, const std::string&) override {
std::scoped_lock lock(mLock);
- mAnrConnectionTokens.push(connectionToken);
+ mAnrWindowTokens.push(connectionToken);
mNotifyAnr.notify_all();
}
- void notifyConnectionResponsive(const sp<IBinder>& connectionToken) override {
+ void notifyMonitorUnresponsive(int32_t pid, const std::string&) override {
std::scoped_lock lock(mLock);
- mResponsiveConnectionTokens.push(connectionToken);
+ mAnrMonitorPids.push(pid);
+ mNotifyAnr.notify_all();
+ }
+
+ void notifyWindowResponsive(const sp<IBinder>& connectionToken) override {
+ std::scoped_lock lock(mLock);
+ mResponsiveWindowTokens.push(connectionToken);
+ mNotifyAnr.notify_all();
+ }
+
+ void notifyMonitorResponsive(int32_t pid) override {
+ std::scoped_lock lock(mLock);
+ mResponsiveMonitorPids.push(pid);
mNotifyAnr.notify_all();
}
@@ -254,6 +324,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 +383,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 +424,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 +461,6 @@
}
};
-
TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
KeyEvent event;
@@ -574,9 +655,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 +753,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 +778,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 +810,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 +901,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 +913,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 +963,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 +1010,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 +1253,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 +1323,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 +1607,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 +1631,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 +1646,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 +1682,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 +1706,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 +1726,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 +1752,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 +1826,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);
}
@@ -1803,11 +2001,10 @@
std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
ASSERT_TRUE(consumeSeq);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(DISPATCHING_TIMEOUT,
- monitor.getToken());
+ mFakePolicy->assertNotifyMonitorUnresponsiveWasCalled(DISPATCHING_TIMEOUT);
monitor.finishEvent(*consumeSeq);
ASSERT_TRUE(mDispatcher->waitForIdle());
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(monitor.getToken());
+ mFakePolicy->assertNotifyMonitorResponsiveWasCalled();
}
TEST_F(InputDispatcherTest, TestMoveEvent) {
@@ -1959,6 +2156,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 +2402,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 +2615,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 +2626,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 +2738,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 +2777,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 +2849,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 +2925,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))
@@ -2926,13 +3232,13 @@
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
ASSERT_TRUE(sequenceNum);
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
mWindow->finishEvent(*sequenceNum);
mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
ADISPLAY_ID_DEFAULT, 0 /*flags*/);
ASSERT_TRUE(mDispatcher->waitForIdle());
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
}
// Send a key to the app and have the app not respond right away.
@@ -2942,7 +3248,7 @@
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
ASSERT_TRUE(sequenceNum);
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
ASSERT_TRUE(mDispatcher->waitForIdle());
}
@@ -3047,7 +3353,7 @@
// We have now sent down and up. Let's consume first event and then ANR on the second.
mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
}
// If an app is not responding to a key event, gesture monitors should continue to receive
@@ -3064,7 +3370,7 @@
// Stuck on the ACTION_UP
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
// New tap will go to the gesture monitor, but not to the window
tapOnWindow();
@@ -3073,7 +3379,7 @@
mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
mDispatcher->waitForIdle();
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
mWindow->assertNoEvents();
monitor.assertNoEvents();
}
@@ -3092,7 +3398,7 @@
mWindow->consumeMotionDown();
// Stuck on the ACTION_UP
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
// New tap will go to the gesture monitor, but not to the window
tapOnWindow();
@@ -3101,7 +3407,7 @@
mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
mDispatcher->waitForIdle();
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
mWindow->assertNoEvents();
monitor.assertNoEvents();
}
@@ -3118,19 +3424,19 @@
mWindow->consumeMotionDown();
// Block on ACTION_UP
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
mWindow->consumeMotionUp(); // Now the connection should be healthy again
mDispatcher->waitForIdle();
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
mWindow->assertNoEvents();
tapOnWindow();
mWindow->consumeMotionDown();
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
mWindow->consumeMotionUp();
mDispatcher->waitForIdle();
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
mFakePolicy->assertNotifyAnrWasNotCalled();
mWindow->assertNoEvents();
}
@@ -3143,7 +3449,7 @@
WINDOW_LOCATION));
const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(windowTimeout, mWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow->getToken());
std::this_thread::sleep_for(windowTimeout);
// 'notifyConnectionUnresponsive' should only be called once per connection
mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -3153,7 +3459,7 @@
ADISPLAY_ID_DEFAULT, 0 /*flags*/);
mWindow->assertNoEvents();
mDispatcher->waitForIdle();
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
mFakePolicy->assertNotifyAnrWasNotCalled();
}
@@ -3321,7 +3627,7 @@
const std::chrono::duration timeout =
mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
// Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
// sequence to make it consistent
mFocusedWindow->consumeMotionCancel();
@@ -3332,7 +3638,7 @@
mFocusedWindow->assertNoEvents();
mUnfocusedWindow->assertNoEvents();
ASSERT_TRUE(mDispatcher->waitForIdle());
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
mFakePolicy->assertNotifyAnrWasNotCalled();
}
@@ -3346,8 +3652,8 @@
tapOnFocusedWindow();
// we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window
- sp<IBinder> anrConnectionToken1 = mFakePolicy->getUnresponsiveConnectionToken(10ms);
- sp<IBinder> anrConnectionToken2 = mFakePolicy->getUnresponsiveConnectionToken(0ms);
+ sp<IBinder> anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms);
+ sp<IBinder> anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms);
// We don't know which window will ANR first. But both of them should happen eventually.
ASSERT_TRUE(mFocusedWindow->getToken() == anrConnectionToken1 ||
@@ -3362,8 +3668,8 @@
mFocusedWindow->consumeMotionUp();
mUnfocusedWindow->consumeMotionOutside();
- sp<IBinder> responsiveToken1 = mFakePolicy->getResponsiveConnectionToken();
- sp<IBinder> responsiveToken2 = mFakePolicy->getResponsiveConnectionToken();
+ sp<IBinder> responsiveToken1 = mFakePolicy->getResponsiveWindowToken();
+ sp<IBinder> responsiveToken2 = mFakePolicy->getResponsiveWindowToken();
// Both applications should be marked as responsive, in any order
ASSERT_TRUE(mFocusedWindow->getToken() == responsiveToken1 ||
@@ -3387,7 +3693,7 @@
ASSERT_TRUE(upEventSequenceNum);
const std::chrono::duration timeout =
mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
// Tap once again
// We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
@@ -3408,7 +3714,7 @@
// The second tap did not go to the focused window
mFocusedWindow->assertNoEvents();
// Since all events are finished, connection should be deemed healthy again
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
mFakePolicy->assertNotifyAnrWasNotCalled();
}
@@ -3520,7 +3826,7 @@
const std::chrono::duration timeout =
mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyConnectionUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
mUnfocusedWindow->consumeMotionDown();
mFocusedWindow->consumeMotionDown();
@@ -3539,7 +3845,7 @@
ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionEvent.getAction());
}
ASSERT_TRUE(mDispatcher->waitForIdle());
- mFakePolicy->assertNotifyConnectionResponsiveWasCalled(mFocusedWindow->getToken());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
mUnfocusedWindow->assertNoEvents();
mFocusedWindow->assertNoEvents();
@@ -3824,4 +4130,89 @@
// 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.
+ mFakePolicy->waitForSetPointerCapture(false);
+ 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 99eaac6..91d5864 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -22,11 +22,13 @@
#include <InputReaderFactory.h>
#include <KeyboardInputMapper.h>
#include <MultiTouchInputMapper.h>
+#include <SensorInputMapper.h>
#include <SingleTouchInputMapper.h>
#include <SwitchInputMapper.h>
#include <TestInputListener.h>
#include <TouchInputMapper.h>
#include <UinputDevice.h>
+#include <VibratorInputMapper.h>
#include <android-base/thread_annotations.h>
#include <gtest/gtest.h>
#include <inttypes.h>
@@ -223,10 +225,11 @@
}
void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
- const std::string& uniqueId, std::optional<uint8_t> physicalPort,
- ViewportType viewportType) {
- const DisplayViewport viewport = createDisplayViewport(displayId, width, height,
- orientation, uniqueId, physicalPort, viewportType);
+ bool isActive, const std::string& uniqueId,
+ std::optional<uint8_t> physicalPort, ViewportType viewportType) {
+ const DisplayViewport viewport =
+ createDisplayViewport(displayId, width, height, orientation, isActive, uniqueId,
+ physicalPort, viewportType);
mViewports.push_back(viewport);
mConfig.setDisplayViewports(mViewports);
}
@@ -294,8 +297,9 @@
private:
DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height,
- int32_t orientation, const std::string& uniqueId, std::optional<uint8_t> physicalPort,
- ViewportType type) {
+ int32_t orientation, bool isActive,
+ const std::string& uniqueId,
+ std::optional<uint8_t> physicalPort, ViewportType type) {
bool isRotated = (orientation == DISPLAY_ORIENTATION_90
|| orientation == DISPLAY_ORIENTATION_270);
DisplayViewport v;
@@ -311,6 +315,7 @@
v.physicalBottom = isRotated ? width : height;
v.deviceWidth = isRotated ? height : width;
v.deviceHeight = isRotated ? width : height;
+ v.isActive = isActive;
v.uniqueId = uniqueId;
v.physicalPort = physicalPort;
v.type = type;
@@ -360,6 +365,11 @@
uint32_t flags;
};
+ struct SensorInfo {
+ InputDeviceSensorType sensorType;
+ int32_t sensorDataIndex;
+ };
+
struct Device {
InputDeviceIdentifier identifier;
Flags<InputDeviceClass> classes;
@@ -373,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;
@@ -396,6 +408,7 @@
std::vector<std::string> mExcludedDevices;
List<RawEvent> mEvents GUARDED_BY(mLock);
std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
+ std::vector<int32_t> mVibrators = {0, 1};
public:
virtual ~FakeEventHub() {
@@ -530,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);
@@ -625,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);
@@ -664,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;
}
@@ -810,6 +861,8 @@
void cancelVibrate(int32_t) override {}
+ std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; };
+
virtual bool isExternal(int32_t) const {
return false;
}
@@ -1030,8 +1083,9 @@
using InputReader::loopOnce;
protected:
- virtual std::shared_ptr<InputDevice> createDeviceLocked(
- int32_t eventHubId, const InputDeviceIdentifier& identifier) {
+ virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
+ const InputDeviceIdentifier& identifier)
+ REQUIRES(mLock) {
if (!mNextDevices.empty()) {
std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
mNextDevices.pop();
@@ -1112,7 +1166,7 @@
// Add an internal viewport, then clear it
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId, NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId, NO_PORT,
ViewportType::INTERNAL);
// Check matching by uniqueId
@@ -1143,20 +1197,20 @@
// Add an internal viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, internalUniqueId, NO_PORT,
- ViewportType::INTERNAL);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId,
+ NO_PORT, ViewportType::INTERNAL);
// Add an external viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, externalUniqueId, NO_PORT,
- ViewportType::EXTERNAL);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId,
+ NO_PORT, ViewportType::EXTERNAL);
// Add an virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, virtualUniqueId1, NO_PORT,
- ViewportType::VIRTUAL);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1,
+ NO_PORT, ViewportType::VIRTUAL);
// Add another virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, virtualUniqueId2, NO_PORT,
- ViewportType::VIRTUAL);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId2,
+ NO_PORT, ViewportType::VIRTUAL);
// Check matching by type for internal
std::optional<DisplayViewport> internalViewport =
@@ -1205,10 +1259,12 @@
mFakePolicy->clearViewports();
// Add a viewport
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId1, NO_PORT, type);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1,
+ NO_PORT, type);
// Add another viewport
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId2, NO_PORT, type);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2,
+ NO_PORT, type);
// Check that correct display viewport was returned by comparing the display IDs.
std::optional<DisplayViewport> viewport1 =
@@ -1248,10 +1304,10 @@
// Add the default display first and ensure it gets returned.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId1, NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId2, NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
std::optional<DisplayViewport> viewport =
@@ -1263,10 +1319,10 @@
// Add the default display second to make sure order doesn't matter.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId2, NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId1, NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -1291,10 +1347,12 @@
mFakePolicy->clearViewports();
// Add a viewport that's associated with some display port that's not of interest.
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId1, hdmi3, type);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, hdmi3,
+ type);
// Add another viewport, connected to HDMI1 port
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, uniqueId2, hdmi1, type);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1,
+ type);
// Check that correct display viewport was returned by comparing the display ports.
std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
@@ -1423,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);
@@ -1698,10 +1782,10 @@
// Add default and second display.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, "local:0", NO_PORT,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, "local:0", NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, "local:1", hdmi1,
+ DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1,
ViewportType::EXTERNAL);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
mReader->loopOnce();
@@ -1815,6 +1899,31 @@
ASSERT_FALSE(args.enabled) << "Pointer Capture should be disabled.";
}
+class FakeVibratorInputMapper : public FakeInputMapper {
+public:
+ FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
+ : FakeInputMapper(deviceContext, sources) {}
+
+ std::vector<int32_t> getVibratorIds() override { return getDeviceContext().getVibratorIds(); }
+};
+
+TEST_F(InputReaderTest, VibratorGetVibratorIds) {
+ constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+ Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR;
+ constexpr int32_t eventHubId = 1;
+ const char* DEVICE_LOCATION = "BLUETOOTH";
+ std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+ FakeVibratorInputMapper& mapper =
+ device->addMapper<FakeVibratorInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ mReader->pushNextDevice(device);
+
+ ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+ ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
+
+ ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
+ ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U);
+}
+
// --- InputReaderIntegrationTest ---
// These tests create and interact with the InputReader only through its interface.
@@ -1970,8 +2079,8 @@
int32_t orientation, const std::string& uniqueId,
std::optional<uint8_t> physicalPort,
ViewportType viewportType) {
- mFakePolicy->addDisplayViewport(displayId, width, height, orientation, uniqueId,
- physicalPort, viewportType);
+ mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
+ uniqueId, physicalPort, viewportType);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
@@ -2301,7 +2410,8 @@
// Prepare displays.
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, UNIQUE_ID, hdmi, ViewportType::INTERNAL);
+ DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
+ ViewportType::INTERNAL);
mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
ASSERT_TRUE(mDevice->isEnabled());
@@ -2391,8 +2501,8 @@
void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
int32_t orientation, const std::string& uniqueId,
std::optional<uint8_t> physicalPort, ViewportType viewportType) {
- mFakePolicy->addDisplayViewport(
- displayId, width, height, orientation, uniqueId, physicalPort, viewportType);
+ mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
+ uniqueId, physicalPort, viewportType);
configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
@@ -2495,6 +2605,199 @@
ASSERT_EQ(uint32_t(0), args.policyFlags);
}
+// --- VibratorInputMapperTest ---
+class VibratorInputMapperTest : public InputMapperTest {
+protected:
+ void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::VIBRATOR); }
+};
+
+TEST_F(VibratorInputMapperTest, GetSources) {
+ VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
+}
+
+TEST_F(VibratorInputMapperTest, GetVibratorIds) {
+ VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+ ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
+}
+
+TEST_F(VibratorInputMapperTest, Vibrate) {
+ constexpr uint8_t DEFAULT_AMPLITUDE = 192;
+ VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+
+ VibrationElement pattern(2);
+ VibrationSequence sequence(2);
+ pattern.duration = std::chrono::milliseconds(200);
+ pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2},
+ {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ sequence.addElement(pattern);
+ pattern.duration = std::chrono::milliseconds(500);
+ pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4},
+ {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ sequence.addElement(pattern);
+
+ std::vector<int64_t> timings = {0, 1};
+ std::vector<uint8_t> amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2};
+
+ ASSERT_FALSE(mapper.isVibrating());
+ mapper.vibrate(sequence, -1 /* repeat */, 0 /* token */);
+ 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 ---
@@ -3987,7 +4290,8 @@
constexpr int32_t SECOND_DISPLAY_ID = 1;
const std::string SECOND_DISPLAY_UNIQUE_ID = "local:1";
mFakePolicy->addDisplayViewport(SECOND_DISPLAY_ID, 800, 480, DISPLAY_ORIENTATION_0,
- SECOND_DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::EXTERNAL);
+ true /*isActive*/, SECOND_DISPLAY_UNIQUE_ID, NO_PORT,
+ ViewportType::EXTERNAL);
mFakePolicy->setDefaultPointerDisplayId(SECOND_DISPLAY_ID);
configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -7038,6 +7342,26 @@
ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
}
+/**
+ * When the viewport is not active (isActive=false), the touch mapper should be disabled and the
+ * events should not be delivered to the listener.
+ */
+TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT,
+ ViewportType::INTERNAL);
+ configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ prepareAxes(POSITION);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+ NotifyMotionArgs motionArgs;
+ processPosition(mapper, 100, 100);
+ processSync(mapper);
+
+ mFakeListener->assertNotifyMotionWasNotCalled();
+}
+
TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
// Setup the first touch screen device.
prepareAxes(POSITION | ID | SLOT);
@@ -7638,8 +7962,8 @@
configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
- void processPositionAndVerify(MultiTouchInputMapper& mapper, int32_t xInside, int32_t yInside,
- int32_t xOutside, int32_t yOutside, int32_t xExpected,
+ void processPositionAndVerify(MultiTouchInputMapper& mapper, int32_t xOutside, int32_t yOutside,
+ int32_t xInside, int32_t yInside, int32_t xExpected,
int32_t yExpected) {
// touch on outside area should not work.
processPosition(mapper, toRawX(xOutside), toRawY(yOutside));
@@ -7721,6 +8045,34 @@
processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
}
+TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_Corner) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+ const int32_t x = 0;
+ const int32_t y = 0;
+
+ const int32_t xExpected = x;
+ const int32_t yExpected = y;
+ processPositionAndVerify(mapper, x - 1, y, x, y, xExpected, yExpected);
+
+ clearViewports();
+ prepareDisplay(DISPLAY_ORIENTATION_90);
+ // expect x/y = swap x/y then reverse y.
+ const int32_t xExpected90 = y;
+ const int32_t yExpected90 = DISPLAY_WIDTH - 1;
+ processPositionAndVerify(mapper, x - 1, y, x, y, xExpected90, yExpected90);
+
+ clearViewports();
+ prepareDisplay(DISPLAY_ORIENTATION_270);
+ // expect x/y = swap x/y then reverse x.
+ const int32_t xExpected270 = DISPLAY_HEIGHT - 1;
+ const int32_t yExpected270 = x;
+ processPositionAndVerify(mapper, x - 1, y, x, y, xExpected270, yExpected270);
+}
+
TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
// we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
std::shared_ptr<FakePointerController> fakePointerController =
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 352995c..295c6e3 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
+#include "TestInputListener.h"
#include <gtest/gtest.h>
-#include "TestInputListener.h"
-
namespace android {
// --- TestInputListener ---
@@ -28,7 +27,7 @@
: mEventHappenedTimeout(eventHappenedTimeout),
mEventDidNotHappenTimeout(eventDidNotHappenTimeout) {}
-TestInputListener::~TestInputListener() { }
+TestInputListener::~TestInputListener() {}
void TestInputListener::assertNotifyConfigurationChangedWasCalled(
NotifyConfigurationChangedArgs* outEventArgs) {
@@ -43,8 +42,7 @@
"notifyConfigurationChanged() should not be called."));
}
-void TestInputListener::assertNotifyDeviceResetWasCalled(
- NotifyDeviceResetArgs* outEventArgs) {
+void TestInputListener::assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs* outEventArgs) {
ASSERT_NO_FATAL_FAILURE(
assertCalled<
NotifyDeviceResetArgs>(outEventArgs,
@@ -73,7 +71,7 @@
void TestInputListener::assertNotifyMotionWasNotCalled() {
ASSERT_NO_FATAL_FAILURE(
- assertNotCalled<NotifySwitchArgs>("notifySwitch() should not be called."));
+ assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called."));
}
void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) {
@@ -82,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(
@@ -157,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/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 4a711ca..2f32827 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -114,14 +114,15 @@
HalResult AidlHalWrapper::setBoost(Boost boost, int32_t durationMs) {
std::unique_lock<std::mutex> lock(mBoostMutex);
+ size_t idx = static_cast<size_t>(boost);
+
// Quick return if boost is not supported by HAL
- if (boost > Boost::DISPLAY_UPDATE_IMMINENT ||
- mBoostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::OFF) {
+ if (idx >= mBoostSupportedArray.size() || mBoostSupportedArray[idx] == HalSupport::OFF) {
ALOGV("Skipped setBoost %s because Power HAL doesn't support it", toString(boost).c_str());
return HalResult::UNSUPPORTED;
}
- if (mBoostSupportedArray[static_cast<int32_t>(boost)] == HalSupport::UNKNOWN) {
+ if (mBoostSupportedArray[idx] == HalSupport::UNKNOWN) {
bool isSupported = false;
auto isSupportedRet = mHandle->isBoostSupported(boost, &isSupported);
if (!isSupportedRet.isOk()) {
@@ -130,8 +131,7 @@
return HalResult::FAILED;
}
- mBoostSupportedArray[static_cast<int32_t>(boost)] =
- isSupported ? HalSupport::ON : HalSupport::OFF;
+ mBoostSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
if (!isSupported) {
ALOGV("Skipped setBoost %s because Power HAL doesn't support it",
toString(boost).c_str());
@@ -145,14 +145,15 @@
HalResult AidlHalWrapper::setMode(Mode mode, bool enabled) {
std::unique_lock<std::mutex> lock(mModeMutex);
+ size_t idx = static_cast<size_t>(mode);
+
// Quick return if mode is not supported by HAL
- if (mode > Mode::DISPLAY_INACTIVE ||
- mModeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::OFF) {
+ if (idx >= mModeSupportedArray.size() || mModeSupportedArray[idx] == HalSupport::OFF) {
ALOGV("Skipped setMode %s because Power HAL doesn't support it", toString(mode).c_str());
return HalResult::UNSUPPORTED;
}
- if (mModeSupportedArray[static_cast<int32_t>(mode)] == HalSupport::UNKNOWN) {
+ if (mModeSupportedArray[idx] == HalSupport::UNKNOWN) {
bool isSupported = false;
auto isSupportedRet = mHandle->isModeSupported(mode, &isSupported);
if (!isSupportedRet.isOk()) {
@@ -161,8 +162,7 @@
return HalResult::FAILED;
}
- mModeSupportedArray[static_cast<int32_t>(mode)] =
- isSupported ? HalSupport::ON : HalSupport::OFF;
+ mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
if (!isSupported) {
ALOGV("Skipped setMode %s because Power HAL doesn't support it",
toString(mode).c_str());
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..424a8b3 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -389,19 +389,58 @@
mBufferInfo.mFrameLatencyNeeded = true;
}
-bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
- nsecs_t expectedPresentTime) {
- ATRACE_CALL();
-
+bool BufferLayer::shouldPresentNow(nsecs_t expectedPresentTime) const {
// If this is not a valid vsync for the layer's uid, return and try again later
const bool isVsyncValidForUid =
mFlinger->mScheduler->isVsyncValid(expectedPresentTime, mOwnerUid);
if (!isVsyncValidForUid) {
ATRACE_NAME("!isVsyncValidForUid");
- mFlinger->setTransactionFlags(eTraversalNeeded);
return false;
}
+ // AutoRefresh layers and sideband streams should always be presented
+ if (getSidebandStreamChanged() || getAutoRefresh()) {
+ return true;
+ }
+
+ // If the next planned present time is not current, return and try again later
+ if (frameIsEarly(expectedPresentTime)) {
+ ATRACE_NAME("frameIsEarly()");
+ return false;
+ }
+
+ // If this layer doesn't have a frame is shouldn't be presented
+ if (!hasFrameUpdate()) {
+ return false;
+ }
+
+ // Defer to the derived class to decide whether the next buffer is due for
+ // presentation.
+ return isBufferDue(expectedPresentTime);
+}
+
+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 (!presentTime.has_value()) {
+ return false;
+ }
+
+ 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();
+
bool refreshRequired = latchSidebandStream(recomputeVisibleRegions);
if (refreshRequired) {
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 63dfe5f..2118f4a 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -175,12 +175,24 @@
void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override;
// Transform hint provided to the producer. This must be accessed holding
- /// the mStateLock.
+ // the mStateLock.
ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0;
bool getAutoRefresh() const { return mAutoRefresh; }
bool getSidebandStreamChanged() const { return mSidebandStreamChanged; }
+ // Returns true if the next buffer should be presented at the expected present time
+ bool shouldPresentNow(nsecs_t expectedPresentTime) const final;
+
+ // Returns true if the next buffer should be presented at the expected present time,
+ // overridden by BufferStateLayer and BufferQueueLayer for implementation
+ // specific logic
+ virtual bool isBufferDue(nsecs_t /*expectedPresentTime*/) const = 0;
+
+ // Returns true if the next frame is considered too early to present
+ // at the given expectedPresentTime
+ bool frameIsEarly(nsecs_t expectedPresentTime) const;
+
std::atomic<bool> mAutoRefresh{false};
std::atomic<bool> mSidebandStreamChanged{false};
@@ -224,6 +236,13 @@
std::unique_ptr<compositionengine::LayerFECompositionState> mCompositionState;
FloatRect computeSourceBounds(const FloatRect& parentBounds) const override;
+
+ // Returns the predicted present time of the next frame if available
+ virtual std::optional<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 dc99986..32e6b10 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"
@@ -99,15 +100,7 @@
return mQueuedFrames;
}
-bool BufferQueueLayer::shouldPresentNow(nsecs_t expectedPresentTime) const {
- if (getSidebandStreamChanged() || getAutoRefresh()) {
- return true;
- }
-
- if (!hasFrameUpdate()) {
- return false;
- }
-
+bool BufferQueueLayer::isBufferDue(nsecs_t expectedPresentTime) const {
Mutex::Autolock lock(mQueueItemLock);
const int64_t addedTime = mQueueItems[0].item.mTimestamp;
@@ -222,6 +215,21 @@
return mQueuedFrames > 0;
}
+std::optional<nsecs_t> BufferQueueLayer::nextPredictedPresentTime() const {
+ Mutex::Autolock lock(mQueueItemLock);
+ if (mQueueItems.empty()) {
+ return std::nullopt;
+ }
+
+ const auto& bufferData = mQueueItems[0];
+
+ if (!bufferData.item.mIsAutoTimestamp || !bufferData.surfaceFrame) {
+ return std::nullopt;
+ }
+
+ 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 +282,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 +298,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 +329,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 +344,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,11 +445,12 @@
}
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, std::move(surfaceFrame)});
+ mQueueItems.push_back({item, surfaceFrame});
mQueuedFrames++;
// Wake up any pending callbacks
@@ -475,8 +485,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 +654,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 b45900e..0e8fdbe 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -53,7 +53,8 @@
int32_t getQueuedFrameCount() const override;
- bool shouldPresentNow(nsecs_t expectedPresentTime) const override;
+ // Returns true if the next buffer should be presented at the expected present time
+ bool isBufferDue(nsecs_t expectedPresentTime) const override;
// Implements BufferLayer.
bool fenceHasSignaled() const override;
@@ -116,6 +117,8 @@
// Temporary - Used only for LEGACY camera mode.
uint32_t getProducerStickyTransform() const;
+ std::optional<nsecs_t> nextPredictedPresentTime() const override;
+
sp<BufferLayerConsumer> mConsumer;
sp<IGraphicBufferProducer> mProducer;
@@ -129,10 +132,10 @@
Condition mQueueItemCondition;
struct BufferData {
- BufferData(BufferItem item, std::unique_ptr<frametimeline::SurfaceFrame> surfaceFrame)
- : item(item), surfaceFrame(std::move(surfaceFrame)) {}
+ BufferData(BufferItem item, std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame)
+ : item(item), surfaceFrame(surfaceFrame) {}
BufferItem item;
- std::unique_ptr<frametimeline::SurfaceFrame> surfaceFrame;
+ std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame;
};
std::vector<BufferData> mQueueItems;
std::atomic<uint64_t> mLastFrameNumberReceived{0};
@@ -146,6 +149,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 963e541..0cc15c2 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
@@ -65,10 +66,70 @@
}
}
+status_t BufferStateLayer::addReleaseFence(const sp<CallbackHandle>& ch,
+ const sp<Fence>& fence) {
+ if (ch == nullptr) {
+ return OK;
+ }
+ if (!ch->previousReleaseFence.get()) {
+ ch->previousReleaseFence = fence;
+ return OK;
+ }
+
+ // Below logic is lifted from ConsumerBase.cpp:
+ // Check status of fences first because merging is expensive.
+ // Merging an invalid fence with any other fence results in an
+ // invalid fence.
+ auto currentStatus = ch->previousReleaseFence->getStatus();
+ if (currentStatus == Fence::Status::Invalid) {
+ ALOGE("Existing fence has invalid state, layer: %s", mName.c_str());
+ return BAD_VALUE;
+ }
+
+ auto incomingStatus = fence->getStatus();
+ if (incomingStatus == Fence::Status::Invalid) {
+ ALOGE("New fence has invalid state, layer: %s", mName.c_str());
+ ch->previousReleaseFence = fence;
+ return BAD_VALUE;
+ }
+
+ // If both fences are signaled or both are unsignaled, we need to merge
+ // them to get an accurate timestamp.
+ if (currentStatus == incomingStatus) {
+ char fenceName[32] = {};
+ snprintf(fenceName, 32, "%.28s", mName.c_str());
+ sp<Fence> mergedFence = Fence::merge(
+ fenceName, ch->previousReleaseFence, fence);
+ if (!mergedFence.get()) {
+ ALOGE("failed to merge release fences, layer: %s", mName.c_str());
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ ch->previousReleaseFence = fence;
+ return BAD_VALUE;
+ }
+ ch->previousReleaseFence = mergedFence;
+ } else if (incomingStatus == Fence::Status::Unsignaled) {
+ // If one fence has signaled and the other hasn't, the unsignaled
+ // fence will approximately correspond with the correct timestamp.
+ // There's a small race if both fences signal at about the same time
+ // and their statuses are retrieved with unfortunate timing. However,
+ // by this point, they will have both signaled and only the timestamp
+ // will be slightly off; any dependencies after this point will
+ // already have been met.
+ ch->previousReleaseFence = fence;
+ }
+ // else if (currentStatus == Fence::Status::Unsignaled) is a no-op.
+
+ return OK;
+}
+
// -----------------------------------------------------------------------
// Interface implementation for Layer
// -----------------------------------------------------------------------
void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
+ if (!releaseFence->isValid()) {
+ return;
+ }
// The previous release fence notifies the client that SurfaceFlinger is done with the previous
// buffer that was presented on this layer. The first transaction that came in this frame that
// replaced the previous buffer on this layer needs this release fence, because the fence will
@@ -85,12 +146,17 @@
// buffer. It replaces the buffer in the second transaction. The buffer in the second
// transaction will now no longer be presented so it is released immediately and the third
// transaction doesn't need a previous release fence.
+ sp<CallbackHandle> ch;
for (auto& handle : mDrawingState.callbackHandles) {
if (handle->releasePreviousBuffer) {
- handle->previousReleaseFence = releaseFence;
+ ch = handle;
break;
}
}
+ auto status = addReleaseFence(ch, releaseFence);
+ if (status != OK) {
+ ALOGE("Failed to add release fence for layer %s", getName().c_str());
+ }
mPreviousReleaseFence = releaseFence;
@@ -100,14 +166,30 @@
}
}
+void BufferStateLayer::onSurfaceFrameCreated(
+ const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
+ mPendingJankClassifications.emplace_back(surfaceFrame);
+}
+
void BufferStateLayer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
for (const auto& handle : mDrawingState.callbackHandles) {
handle->transformHint = mTransformHint;
handle->dequeueReadyTime = dequeueReadyTime;
}
+ std::vector<JankData> jankData;
+ jankData.reserve(mPendingJankClassifications.size());
+ while (!mPendingJankClassifications.empty()
+ && mPendingJankClassifications.front()->getJankType()) {
+ std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
+ mPendingJankClassifications.front();
+ mPendingJankClassifications.pop_front();
+ jankData.emplace_back(
+ JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
+ }
+
mFlinger->getTransactionCompletedThread().finalizePendingCallbackHandles(
- mDrawingState.callbackHandles);
+ mDrawingState.callbackHandles, jankData);
mDrawingState.callbackHandles = {};
@@ -130,14 +212,6 @@
}
}
-bool BufferStateLayer::shouldPresentNow(nsecs_t /*expectedPresentTime*/) const {
- if (getSidebandStreamChanged() || getAutoRefresh()) {
- return true;
- }
-
- return hasFrameUpdate();
-}
-
bool BufferStateLayer::willPresentCurrentTransaction() const {
// Returns true if the most recent Transaction applied to CurrentState will be presented.
return (getSidebandStreamChanged() || getAutoRefresh() ||
@@ -260,12 +334,19 @@
}
bool BufferStateLayer::setBuffer(const sp<GraphicBuffer>& buffer, const sp<Fence>& acquireFence,
- nsecs_t postTime, nsecs_t desiredPresentTime,
- const client_cache_t& clientCacheId, uint64_t frameNumber) {
+ nsecs_t postTime, nsecs_t desiredPresentTime, bool isAutoTimestamp,
+ const client_cache_t& clientCacheId, uint64_t frameNumber,
+ std::optional<nsecs_t> /* dequeueTime */) {
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;
@@ -278,13 +359,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;
}
@@ -382,7 +463,7 @@
void BufferStateLayer::forceSendCallbacks() {
mFlinger->getTransactionCompletedThread().finalizePendingCallbackHandles(
- mCurrentState.callbackHandles);
+ mCurrentState.callbackHandles, std::vector<JankData>());
}
bool BufferStateLayer::setTransparentRegionHint(const Region& transparent) {
@@ -398,6 +479,10 @@
return Rect(getActiveWidth(s), getActiveHeight(s));
}
+ if (mBufferInfo.mBuffer == nullptr) {
+ return Rect::INVALID_RECT;
+ }
+
// if the display frame is not defined, use the parent bounds as the buffer size.
const auto& p = mDrawingParent.promote();
if (p != nullptr) {
@@ -442,7 +527,7 @@
return true;
}
- return mCurrentState.desiredPresentTime <= expectedPresentTime;
+ return mCurrentState.isAutoTimestamp || mCurrentState.desiredPresentTime <= expectedPresentTime;
}
bool BufferStateLayer::onPreComposition(nsecs_t refreshStartTime) {
@@ -510,6 +595,14 @@
return mCurrentStateModified && (c.buffer != nullptr || c.bgColorLayer != nullptr);
}
+std::optional<nsecs_t> BufferStateLayer::nextPredictedPresentTime() const {
+ if (!getDrawingState().isAutoTimestamp || !mSurfaceFrame) {
+ return std::nullopt;
+ }
+
+ return mSurfaceFrame->getPredictions().presentTime;
+}
+
status_t BufferStateLayer::updateTexImage(bool& /*recomputeVisibleRegions*/, nsecs_t latchTime,
nsecs_t /*expectedPresentTime*/) {
const State& s(getDrawingState());
@@ -545,6 +638,10 @@
return BAD_VALUE;
}
+ if (s.buffer != mBufferInfo.mBuffer) {
+ decrementPendingBufferCount();
+ }
+
mPreviousBufferId = getCurrentBufferId();
mBufferInfo.mBuffer = s.buffer;
mBufferInfo.mFence = s.acquireFence;
@@ -741,7 +838,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 42be62a..f638caa 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -45,7 +45,7 @@
void finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& glDoneFence,
const CompositorTiming& compositorTiming) override;
- bool shouldPresentNow(nsecs_t expectedPresentTime) const override;
+ bool isBufferDue(nsecs_t /*expectedPresentTime*/) const override { return true; }
uint32_t doTransactionResize(uint32_t flags, Layer::State* /*stateToCommit*/) override {
return flags;
@@ -70,8 +70,9 @@
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,
+ std::optional<nsecs_t> dequeueTime) override;
bool setAcquireFence(const sp<Fence>& fence) override;
bool setDataspace(ui::Dataspace dataspace) override;
bool setHdrMetadata(const HdrMetadata& hdrMetadata) override;
@@ -112,16 +113,25 @@
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;
+ void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame);
private:
friend class SlotGenerationTest;
+ inline void tracePendingBufferCount();
bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
nsecs_t requestedPresentTime);
+ status_t addReleaseFence(const sp<CallbackHandle>& ch, const sp<Fence>& releaseFence);
+
uint64_t getFrameNumber(nsecs_t expectedPresentTime) const override;
bool latchSidebandStream(bool& recomputeVisibleRegions) override;
@@ -143,6 +153,8 @@
bool bufferNeedsFiltering() const override;
+ std::optional<nsecs_t> nextPredictedPresentTime() const override;
+
static const std::array<float, 16> IDENTITY_MATRIX;
std::unique_ptr<renderengine::Image> mTextureImage;
@@ -158,6 +170,20 @@
bool mReleasePreviousBuffer = false;
nsecs_t mCallbackHandleAcquireTime = -1;
+ 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 5c7f12d..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>
@@ -108,6 +109,9 @@
// If set to true, change the layer settings to render a clear output.
// This may be requested by the HWC
const bool clearContent;
+
+ // If set to true, change the layer settings to not use any blurs.
+ const bool disableBlurs;
};
// A superset of LayerSettings required by RenderEngine to compose a layer
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/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3852f45..4f99495 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -167,15 +167,10 @@
// Update framebuffer space
const Rect newBounds(size);
- ScaleVector scale;
- scale = getScale(state.framebufferSpace.bounds, newBounds);
state.framebufferSpace.bounds = newBounds;
- state.framebufferSpace.content.scaleSelf(scale.x, scale.y);
// Update display space
- scale = getScale(state.displaySpace.bounds, newBounds);
state.displaySpace.bounds = newBounds;
- state.displaySpace.content.scaleSelf(scale.x, scale.y);
state.transform = state.layerStackSpace.getTransform(state.displaySpace);
// Update oriented display space
@@ -185,9 +180,7 @@
std::swap(orientedSize.width, orientedSize.height);
}
const Rect newOrientedBounds(orientedSize);
- scale = getScale(state.orientedDisplaySpace.bounds, newOrientedBounds);
state.orientedDisplaySpace.bounds = newOrientedBounds;
- state.orientedDisplaySpace.content.scaleSelf(scale.x, scale.y);
dirtyEntireOutput();
}
@@ -669,8 +662,14 @@
compositionengine::OutputLayer* Output::findLayerRequestingBackgroundComposition() const {
compositionengine::OutputLayer* layerRequestingBgComposition = nullptr;
for (auto* layer : getOutputLayersOrderedByZ()) {
- if (layer->getLayerFE().getCompositionState()->backgroundBlurRadius > 0 ||
- layer->getLayerFE().getCompositionState()->blurRegions.size() > 0) {
+ auto* compState = layer->getLayerFE().getCompositionState();
+
+ // If any layer has a sideband stream, we will disable blurs. In that case, we don't
+ // want to force client composition because of the blur.
+ if (compState->sidebandStream != nullptr) {
+ return nullptr;
+ }
+ if (compState->backgroundBlurRadius > 0 || compState->blurRegions.size() > 0) {
layerRequestingBgComposition = layer;
}
}
@@ -1022,6 +1021,8 @@
// Used when a layer clears part of the buffer.
Region stubRegion;
+ bool disableBlurs = false;
+
for (auto* layer : getOutputLayersOrderedByZ()) {
const auto& layerState = layer->getState();
const auto* layerFEState = layer->getLayerFE().getCompositionState();
@@ -1035,6 +1036,8 @@
continue;
}
+ disableBlurs |= layerFEState->sidebandStream != nullptr;
+
const bool clientComposition = layer->requiresClientComposition();
// We clear the client target for non-client composed layers if
@@ -1063,7 +1066,8 @@
.viewport = outputState.layerStackSpace.content,
.dataspace = outputDataspace,
.realContentIsVisible = realContentIsVisible,
- .clearContent = !clientComposition};
+ .clearContent = !clientComposition,
+ .disableBlurs = disableBlurs};
std::vector<LayerFE::LayerSettings> results =
layerFE.prepareClientCompositionList(targetSettings);
if (realContentIsVisible && !results.empty()) {
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/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 9badb99..376bac8 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -338,15 +338,12 @@
EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.bounds);
EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.orientation);
- EXPECT_EQ(Rect(0, 0, 900, 450), state.orientedDisplaySpace.content);
EXPECT_EQ(Rect(0, 0, 1000, 500), state.orientedDisplaySpace.bounds);
EXPECT_EQ(displayRect, state.displaySpace.bounds);
- EXPECT_EQ(Rect(0, 0, 450, 900), state.displaySpace.content);
EXPECT_EQ(ui::ROTATION_90, state.displaySpace.orientation);
EXPECT_EQ(displayRect, state.framebufferSpace.bounds);
- EXPECT_EQ(Rect(0, 0, 450, 900), state.framebufferSpace.content);
EXPECT_EQ(ui::ROTATION_90, state.framebufferSpace.orientation);
EXPECT_EQ(state.displaySpace.content, state.transform.transform(state.layerStackSpace.content));
@@ -3688,6 +3685,7 @@
kDisplayDataspace,
false /* realContentIsVisible */,
true /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -3699,6 +3697,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
@@ -3742,6 +3741,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(Rect(0, 0, 30, 30)),
@@ -3753,6 +3753,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(Rect(0, 0, 40, 201)),
@@ -3764,6 +3765,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3795,6 +3797,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -3806,6 +3809,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -3817,6 +3821,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3848,6 +3853,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
@@ -3860,6 +3866,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -3871,6 +3878,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3901,6 +3909,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -3912,6 +3921,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -3923,6 +3933,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -3951,6 +3962,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -3962,6 +3974,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -3973,6 +3986,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4092,6 +4106,7 @@
kOutputDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4109,6 +4124,7 @@
kOutputDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4142,6 +4158,7 @@
kDisplayDataspace,
false /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
LayerFE::LayerSettings mShadowSettings;
@@ -4187,6 +4204,7 @@
kDisplayDataspace,
true /* realContentIsVisible */,
false /* clearContent */,
+ false /* disabledBlurs */,
};
EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
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..7e1da252 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/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 247ee23..2ac67cb 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -72,6 +72,7 @@
mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
mProducerSlotSource(0),
mProducerBuffers(),
+ mProducerSlotNeedReallocation(0),
mQueueBufferOutput(),
mSinkBufferWidth(0),
mSinkBufferHeight(0),
@@ -337,10 +338,14 @@
dbgSourceStr(source), *sslot, pslot, result);
uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
+ // reset producer slot reallocation flag
+ mProducerSlotNeedReallocation &= ~(1ULL << pslot);
+
if ((mProducerSlotSource & (1ULL << pslot)) != sourceBit) {
// This slot was previously dequeued from the other source; must
// re-request the buffer.
- result |= BUFFER_NEEDS_REALLOCATION;
+ mProducerSlotNeedReallocation |= 1ULL << pslot;
+
mProducerSlotSource &= ~(1ULL << pslot);
mProducerSlotSource |= sourceBit;
}
@@ -362,6 +367,9 @@
dbgSourceStr(source), pslot, mProducerBuffers[pslot].get(),
mProducerBuffers[pslot]->getPixelFormat(),
mProducerBuffers[pslot]->getUsage());
+
+ // propagate reallocation to VDS consumer
+ mProducerSlotNeedReallocation |= 1ULL << pslot;
}
return result;
@@ -436,6 +444,11 @@
if (outBufferAge) {
*outBufferAge = 0;
}
+
+ if ((mProducerSlotNeedReallocation & (1ULL << *pslot)) != 0) {
+ result |= BUFFER_NEEDS_REALLOCATION;
+ }
+
return result;
}
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 1974625..fba0e3b 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -176,6 +176,10 @@
uint64_t mProducerSlotSource;
sp<GraphicBuffer> mProducerBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
+ // Need to propagate reallocation to VDS consumer.
+ // Each bit corresponds to a producer slot.
+ uint64_t mProducerSlotNeedReallocation;
+
// The QueueBufferOutput with the latest info from the sink, and with the
// transform hint cleared. Since we defer queueBuffer from the GPU driver
// to the sink, we have to return the previous version.
diff --git a/services/surfaceflinger/Fps.h b/services/surfaceflinger/Fps.h
new file mode 100644
index 0000000..38a9af0
--- /dev/null
+++ b/services/surfaceflinger/Fps.h
@@ -0,0 +1,112 @@
+/*
+ * 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 <cmath>
+#include <ostream>
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// Value which represents "frames per second". This class is a wrapper around
+// float, providing some useful utilities, such as comparisons with tolerance
+// and converting between period duruation and frequency.
+class Fps {
+public:
+ static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); }
+
+ Fps() = default;
+ explicit constexpr Fps(float fps)
+ : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {}
+
+ constexpr float getValue() const { return fps; }
+
+ constexpr nsecs_t getPeriodNsecs() const { return period; }
+
+ bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; }
+
+ // DO NOT use for std::sort. Instead use comparesLess().
+ bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; }
+
+ bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; }
+
+ bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); }
+
+ bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); }
+
+ bool isValid() const { return fps > 0.0f; }
+
+ int getIntValue() const { return static_cast<int>(std::round(fps)); }
+
+ // Use this comparator for sorting. Using a comparator with margins can
+ // cause std::sort to crash.
+ inline static bool comparesLess(const Fps& left, const Fps& right) {
+ return left.fps < right.fps;
+ }
+
+ // Compares two FPS with margin.
+ // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c.
+ // DO NOT use with hash maps. Instead use EqualsInBuckets.
+ struct EqualsWithMargin {
+ bool operator()(const Fps& left, const Fps& right) const {
+ return left.equalsWithMargin(right);
+ }
+ };
+
+ // Equals comparator which can be used with hash maps.
+ // It's guaranteed that if two elements are equal, then their hashes are equal.
+ struct EqualsInBuckets {
+ bool operator()(const Fps& left, const Fps& right) const {
+ return left.getBucket() == right.getBucket();
+ }
+ };
+
+ inline friend std::string to_string(const Fps& fps) {
+ return base::StringPrintf("%.2ffps", fps.fps);
+ }
+
+ inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) {
+ return os << to_string(fps);
+ }
+
+private:
+ friend std::hash<android::Fps>;
+
+ constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {}
+
+ float getBucket() const { return std::round(fps / kMargin); }
+
+ static constexpr float kMargin = 0.001f;
+ float fps = 0;
+ nsecs_t period = 0;
+};
+
+static_assert(std::is_trivially_copyable_v<Fps>);
+
+} // namespace android
+
+namespace std {
+template <>
+struct hash<android::Fps> {
+ std::size_t operator()(const android::Fps& fps) const {
+ return std::hash<float>()(fps.getBucket());
+ }
+};
+} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index e075d3e..1e6d21e 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -14,5 +14,8 @@
"libui",
"libutils",
],
+ static_libs: [
+ "libperfetto_client_experimental",
+ ],
export_include_dirs: ["."],
}
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index bd87482..c994434 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -26,9 +26,10 @@
#include <cinttypes>
#include <numeric>
-namespace android::frametimeline::impl {
+namespace android::frametimeline {
using base::StringAppendF;
+using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
@@ -59,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);
@@ -88,99 +90,167 @@
case PredictionState::Expired:
return "Expired";
case PredictionState::None:
- default:
return "None";
}
}
-std::string toString(TimeStats::JankType jankType) {
- switch (jankType) {
- case TimeStats::JankType::None:
- return "None";
- case TimeStats::JankType::Display:
- return "Composer/Display - outside SF and App";
- case TimeStats::JankType::SurfaceFlingerDeadlineMissed:
- return "SurfaceFlinger Deadline Missed";
- case TimeStats::JankType::AppDeadlineMissed:
- return "App Deadline Missed";
- case TimeStats::JankType::PredictionExpired:
- return "Prediction Expired";
- case TimeStats::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;
});
}
-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;
- }
- 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;
- }
+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";
}
}
-SurfaceFrame::SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName,
+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::DisplayHAL:
+ return FrameTimelineEvent::JANK_DISPLAY_HAL;
+ case JankType::SurfaceFlingerCpuDeadlineMissed:
+ case JankType::SurfaceFlingerGpuDeadlineMissed:
+ return FrameTimelineEvent::JANK_SF_DEADLINE_MISSED;
+ case JankType::AppDeadlineMissed:
+ 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;
+ }
+}
+
+// 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);
+ }
+
+ // 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;
+}
+
+int64_t TraceCookieCounter::getCookieForTracing() {
+ return ++mTraceCookie;
+}
+
+SurfaceFrame::SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
std::string debugName, PredictionState predictionState,
- frametimeline::TimelineItem&& predictions)
- : mOwnerPid(ownerPid),
+ frametimeline::TimelineItem&& predictions,
+ std::shared_ptr<TimeStats> timeStats,
+ JankClassificationThresholds thresholds,
+ TraceCookieCounter* traceCookieCounter)
+ : mToken(token),
+ mOwnerPid(ownerPid),
mOwnerUid(ownerUid),
mLayerName(std::move(layerName)),
mDebugName(std::move(debugName)),
@@ -188,29 +258,9 @@
mPredictionState(predictionState),
mPredictions(predictions),
mActuals({0, 0, 0}),
- mActualQueueTime(0),
- mJankType(TimeStats::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),
+ mTraceCookieCounter(*traceCookieCounter) {}
void SurfaceFrame::setActualStartTime(nsecs_t actualStartTime) {
std::lock_guard<std::mutex> lock(mMutex);
@@ -226,139 +276,582 @@
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(TimeStats::JankType jankType, int32_t jankMetadata) {
+std::optional<int32_t> SurfaceFrame::getJankType() const {
std::lock_guard<std::mutex> lock(mMutex);
- mJankType = jankType;
- mJankMetadata = jankMetadata;
-}
-
-TimeStats::JankType SurfaceFrame::getJankType() const {
- std::lock_guard<std::mutex> lock(mMutex);
+ if (mActuals.presentTime == 0) {
+ return std::nullopt;
+ }
return mJankType;
}
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());
- if (mJankType != TimeStats::JankType::None) {
+ if (mJankType != JankType::None) {
// Easily identify a janky Surface Frame in the dump
StringAppendF(&result, " [*] ");
}
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);
}
-FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
- : mCurrentDisplayFrame(std::make_shared<DisplayFrame>()),
- mMaxDisplayFrames(kDefaultMaxDisplayFrames),
- mTimeStats(std::move(timeStats)) {}
+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;
+ }
-FrameTimeline::DisplayFrame::DisplayFrame()
- : surfaceFlingerPredictions(TimelineItem()),
- surfaceFlingerActuals(TimelineItem()),
- predictionState(PredictionState::None),
- jankType(TimeStats::JankType::None),
- jankMetadata(0) {
- this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
+ 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);
}
-std::unique_ptr<android::frametimeline::SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
- pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
- std::optional<int64_t> token) {
+void SurfaceFrame::trace(int64_t displayFrameToken) {
+ using FrameTimelineDataSource = impl::FrameTimeline::FrameTimelineDataSource;
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace SurfaceFrame - %s with invalid token", mLayerName.c_str());
+ return;
+ } else if (displayFrameToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace SurfaceFrame - %s with invalid displayFrameToken",
+ mLayerName.c_str());
+ return;
+ }
+ }
+
+ int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+ // Expected timeline start
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* expectedSurfaceFrameStartEvent = event->set_expected_surface_frame_start();
+
+ expectedSurfaceFrameStartEvent->set_cookie(expectedTimelineCookie);
+
+ expectedSurfaceFrameStartEvent->set_token(mToken);
+ expectedSurfaceFrameStartEvent->set_display_frame_token(displayFrameToken);
+
+ expectedSurfaceFrameStartEvent->set_pid(mOwnerPid);
+ expectedSurfaceFrameStartEvent->set_layer_name(mDebugName);
+ });
+ // Expected timeline end
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
+
+ expectedSurfaceFrameEndEvent->set_cookie(expectedTimelineCookie);
+ });
+
+ int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+ // Actual timeline start
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ // Actual start time is not yet available, so use expected start instead
+ packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualSurfaceFrameStartEvent = event->set_actual_surface_frame_start();
+
+ actualSurfaceFrameStartEvent->set_cookie(actualTimelineCookie);
+
+ actualSurfaceFrameStartEvent->set_token(mToken);
+ actualSurfaceFrameStartEvent->set_display_frame_token(displayFrameToken);
+
+ actualSurfaceFrameStartEvent->set_pid(mOwnerPid);
+ actualSurfaceFrameStartEvent->set_layer_name(mDebugName);
+
+ if (mPresentState == PresentState::Dropped) {
+ actualSurfaceFrameStartEvent->set_present_type(FrameTimelineEvent::PRESENT_DROPPED);
+ } else if (mPresentState == PresentState::Unknown) {
+ actualSurfaceFrameStartEvent->set_present_type(FrameTimelineEvent::PRESENT_UNSPECIFIED);
+ } else {
+ actualSurfaceFrameStartEvent->set_present_type(toProto(mFramePresentMetadata));
+ }
+ actualSurfaceFrameStartEvent->set_on_time_finish(mFrameReadyMetadata ==
+ FrameReadyMetadata::OnTimeFinish);
+ actualSurfaceFrameStartEvent->set_gpu_composition(mGpuComposition);
+ actualSurfaceFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+ });
+ // Actual timeline end
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualSurfaceFrameEndEvent = event->set_frame_end();
+
+ actualSurfaceFrameEndEvent->set_cookie(actualTimelineCookie);
+ });
+}
+
+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)
+ : mMaxDisplayFrames(kDefaultMaxDisplayFrames),
+ mTimeStats(std::move(timeStats)),
+ mSurfaceFlingerPid(surfaceFlingerPid),
+ mJankClassificationThresholds(thresholds) {
+ mCurrentDisplayFrame =
+ std::make_shared<DisplayFrame>(mTimeStats, thresholds, &mTraceCookieCounter);
+}
+
+void FrameTimeline::onBootFinished() {
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+ registerDataSource();
+}
+
+void FrameTimeline::registerDataSource() {
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name(kFrameTimelineDataSource);
+ FrameTimelineDataSource::Register(dsd);
+}
+
+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_unique<impl::SurfaceFrame>(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, &mTraceCookieCounter);
}
std::optional<TimelineItem> predictions = mTokenManager.getPredictionsForToken(*token);
if (predictions) {
- return std::make_unique<impl::SurfaceFrame>(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, &mTraceCookieCounter);
}
- return std::make_unique<impl::SurfaceFrame>(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,
+ &mTraceCookieCounter);
}
-void FrameTimeline::addSurfaceFrame(
- std::unique_ptr<android::frametimeline::SurfaceFrame> surfaceFrame,
- SurfaceFrame::PresentState state) {
- ATRACE_CALL();
- surfaceFrame->setPresentState(state);
- std::unique_ptr<impl::SurfaceFrame> implSurfaceFrame(
- static_cast<impl::SurfaceFrame*>(surfaceFrame.release()));
- std::lock_guard<std::mutex> lock(mMutex);
- mCurrentDisplayFrame->surfaceFrames.push_back(std::move(implSurfaceFrame));
+FrameTimeline::DisplayFrame::DisplayFrame(std::shared_ptr<TimeStats> timeStats,
+ JankClassificationThresholds thresholds,
+ TraceCookieCounter* traceCookieCounter)
+ : mSurfaceFlingerPredictions(TimelineItem()),
+ mSurfaceFlingerActuals(TimelineItem()),
+ mTimeStats(timeStats),
+ mJankClassificationThresholds(thresholds),
+ mTraceCookieCounter(*traceCookieCounter) {
+ 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);
- 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 {
+ if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace DisplayFrame with invalid token");
+ return;
+ }
+
+ int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+ // Expected timeline start
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerPredictions.startTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* expectedDisplayFrameStartEvent = event->set_expected_display_frame_start();
+
+ expectedDisplayFrameStartEvent->set_cookie(expectedTimelineCookie);
+
+ expectedDisplayFrameStartEvent->set_token(mToken);
+ expectedDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
+ });
+ // Expected timeline end
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* expectedDisplayFrameEndEvent = event->set_frame_end();
+
+ expectedDisplayFrameEndEvent->set_cookie(expectedTimelineCookie);
+ });
+
+ int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+ // Expected timeline start
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.startTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
+
+ actualDisplayFrameStartEvent->set_cookie(actualTimelineCookie);
+
+ actualDisplayFrameStartEvent->set_token(mToken);
+ actualDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
+
+ actualDisplayFrameStartEvent->set_present_type(toProto(mFramePresentMetadata));
+ actualDisplayFrameStartEvent->set_on_time_finish(mFrameReadyMetadata ==
+ FrameReadyMetadata::OnTimeFinish);
+ actualDisplayFrameStartEvent->set_gpu_composition(mGpuComposition);
+ actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
+ });
+ // Expected timeline end
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.endTime));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualDisplayFrameEndEvent = event->set_frame_end();
+
+ actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+ });
+
+ for (auto& surfaceFrame : mSurfaceFrames) {
+ surfaceFrame->trace(mToken);
+ }
+}
+
void FrameTimeline::flushPendingPresentFences() {
for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
const auto& pendingPresentFence = mPendingPresentFences[i];
@@ -370,92 +863,9 @@
}
}
if (signalTime != Fence::SIGNAL_TIME_INVALID) {
- int32_t totalJankReasons = TimeStats::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 = TimeStats::JankType::SurfaceFlingerEarlyLatch;
- } else if ((displayFrame->jankMetadata & LateFinish) &&
- (displayFrame->jankMetadata & LatePresent)) {
- displayFrame->jankType = TimeStats::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 = TimeStats::JankType::Display;
- }
- }
-
- if (std::abs(sfActuals.startTime - sfPredictions.startTime) > kSFStartThreshold) {
- displayFrame->jankMetadata |=
- sfActuals.startTime > sfPredictions.startTime ? LateStart : EarlyStart;
- }
-
- totalJankReasons |= displayFrame->jankType;
-
- 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(TimeStats::JankType::PredictionExpired, 0);
- continue;
- } else if (predictionState == PredictionState::Valid) {
- const auto& actuals = surfaceFrame->getActuals();
- const auto& predictions = surfaceFrame->getPredictions();
- int32_t jankMetadata = 0;
- TimeStats::JankType jankType = TimeStats::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 = TimeStats::JankType::SurfaceFlingerEarlyLatch;
- } else if (jankMetadata & LatePresent) {
- if (jankMetadata & EarlyFinish) {
- // TODO(b/169890654): Classify this properly
- jankType = TimeStats::JankType::Display;
- } else {
- jankType = TimeStats::JankType::AppDeadlineMissed;
- }
- }
-
- totalJankReasons |= jankType;
- mTimeStats->incrementJankyFrames(surfaceFrame->getOwnerUid(),
- surfaceFrame->getName(),
- jankType | displayFrame->jankType);
- surfaceFrame->setJankInfo(jankType, jankMetadata);
- }
- }
- }
-
- mTimeStats->incrementJankyFrames(totalJankReasons);
+ displayFrame->onPresent(signalTime);
+ displayFrame->trace(mSurfaceFlingerPid);
}
mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
@@ -470,16 +880,14 @@
}
mDisplayFrames.push_back(mCurrentDisplayFrame);
mCurrentDisplayFrame.reset();
- mCurrentDisplayFrame = std::make_shared<DisplayFrame>();
+ mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds,
+ &mTraceCookieCounter);
}
-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);
@@ -488,60 +896,79 @@
return baseTime;
}
-void FrameTimeline::dumpDisplayFrame(std::string& result,
- const std::shared_ptr<DisplayFrame>& displayFrame,
- nsecs_t baseTime) {
- if (displayFrame->jankType != TimeStats::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 == TimeStats::JankType::None) {
- // Check if any Surface Frame has been janky
- bool isJanky = false;
- for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
- if (surfaceFrame->getJankType() != TimeStats::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;
@@ -569,4 +996,5 @@
setMaxDisplayFrames(kDefaultMaxDisplayFrames);
}
-} // namespace android::frametimeline::impl
+} // namespace impl
+} // namespace android::frametimeline
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index e61567e..ed38cc6 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -18,6 +18,9 @@
#include <../TimeStats/TimeStats.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/JankInfo.h>
+#include <perfetto/trace/android/frame_timeline_event.pbzero.h>
+#include <perfetto/tracing.h>
#include <ui/FenceTime.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
@@ -29,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.
@@ -68,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,
@@ -80,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 {
@@ -89,9 +128,22 @@
};
/*
- * Stores a set of predictions and the corresponding actual timestamps pertaining to a single frame
- * from the app
+ * Trace cookie is used to send start and end timestamps of <Surface/Display>Frames separately
+ * without needing to resend all the other information. We send all info to perfetto, along with a
+ * new cookie, in the start of a frame. For the corresponding end, we just send the same cookie.
+ * This helps in reducing the amount of data emitted by the producer.
*/
+class TraceCookieCounter {
+public:
+ int64_t getCookieForTracing();
+
+private:
+ // Friend class for testing
+ friend class android::frametimeline::FrameTimelineTest;
+
+ std::atomic<int64_t> mTraceCookie = 0;
+};
+
class SurfaceFrame {
public:
enum class PresentState {
@@ -100,23 +152,80 @@
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,
+ TraceCookieCounter* traceCookieCounter);
+ ~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);
+
+ // 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);
+
+ // 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;
+ // TraceCookieCounter is used to obtain the cookie for sendig trace packets to perfetto. Using a
+ // reference here because the counter is owned by FrameTimeline, which outlives SurfaceFrame.
+ TraceCookieCounter& mTraceCookieCounter;
};
/*
@@ -128,21 +237,24 @@
virtual ~FrameTimeline() = default;
virtual TokenManager* getTokenManager() = 0;
+ // Initializes the Perfetto DataSource that emits DisplayFrame and SurfaceFrame events. Test
+ // classes can avoid double registration by mocking this function.
+ 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::unique_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::unique_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
@@ -165,15 +277,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
@@ -181,107 +291,134 @@
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(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; };
- TimeStats::JankType getJankType() const;
- 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(TimeStats::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);
-
-private:
- 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;
- TimeStats::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:
- FrameTimeline(std::shared_ptr<TimeStats> timeStats);
- ~FrameTimeline() = default;
-
- frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
- std::unique_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::unique_ptr<frametimeline::SurfaceFrame> surfaceFrame,
- SurfaceFrame::PresentState state) override;
- void setSfWakeUp(int64_t token, nsecs_t wakeupTime) override;
- void setSfPresent(nsecs_t sfPresentTime,
- const std::shared_ptr<FenceTime>& presentFence) override;
- void parseArgs(const Vector<String16>& args, std::string& result) override;
- void setMaxDisplayFrames(uint32_t size) override;
- void reset() override;
-
-private:
- // Friend class for testing
- friend class android::frametimeline::FrameTimelineTest;
+ class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
+ void OnSetup(const SetupArgs&) override{};
+ void OnStart(const StartArgs&) override{};
+ void OnStop(const StopArgs&) override{};
+ };
/*
- * DisplayFrame should be used only internally within FrameTimeline.
+ * DisplayFrame should be used only internally within FrameTimeline. All members and methods are
+ * guarded by FrameTimeline's mMutex.
*/
- struct DisplayFrame {
- DisplayFrame();
+ class DisplayFrame {
+ public:
+ DisplayFrame(std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds,
+ TraceCookieCounter* traceCookieCounter);
+ 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 surfaceFlingerPredictions;
- TimelineItem surfaceFlingerActuals;
+ 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::unique_ptr<SurfaceFrame>> surfaceFrames;
+ std::vector<std::shared_ptr<SurfaceFrame>> mSurfaceFrames;
- PredictionState predictionState;
- TimeStats::JankType jankType = TimeStats::JankType::None; // Enum for the type of jank
- int32_t jankMetadata = 0x0; // Additional details about the jank
+ 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;
+ // TraceCookieCounter is used to obtain the cookie for sendig trace packets to perfetto.
+ // Using a reference here because the counter is owned by FrameTimeline, which outlives
+ // DisplayFrame.
+ TraceCookieCounter& mTraceCookieCounter;
};
+ FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
+ JankClassificationThresholds thresholds = {});
+ ~FrameTimeline() = default;
+
+ frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
+ 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;
+ void setMaxDisplayFrames(uint32_t size) override;
+ void reset() override;
+
+ // Sets up the perfetto tracing backend and data source.
+ void onBootFinished() override;
+ // Registers the data source with the perfetto backend. Called as part of onBootFinished()
+ // and should not be called manually outside of tests.
+ void registerDataSource();
+
+ static constexpr char kFrameTimelineDataSource[] = "android.surfaceflinger.frametimeline";
+
+private:
+ // Friend class for testing
+ friend class android::frametimeline::FrameTimelineTest;
+
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);
@@ -291,23 +428,18 @@
mPendingPresentFences GUARDED_BY(mMutex);
std::shared_ptr<DisplayFrame> mCurrentDisplayFrame GUARDED_BY(mMutex);
TokenManager mTokenManager;
- std::mutex mMutex;
+ TraceCookieCounter mTraceCookieCounter;
+ 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.
+ // 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 ab0d3df..a545d18 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -74,6 +74,9 @@
#define DEBUG_RESIZE 0
namespace android {
+namespace {
+constexpr int kDumpTableRowLength = 159;
+} // namespace
using base::StringAppendF;
using namespace android::flag_operators;
@@ -627,7 +630,7 @@
// ---------------------------------------------------------------------------
std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareClientComposition(
- compositionengine::LayerFE::ClientCompositionTargetSettings& /* targetSettings */) {
+ compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
if (!getCompositionState()) {
return {};
}
@@ -649,8 +652,12 @@
layerSettings.alpha = alpha;
layerSettings.sourceDataspace = getDataSpace();
- layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
- layerSettings.blurRegions = getBlurRegions();
+ if (!targetSettings.disableBlurs) {
+ layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
+ layerSettings.blurRegions = getBlurRegions();
+ }
+ // Record the name of the layer for debugging further down the stack.
+ layerSettings.name = getName();
return layerSettings;
}
@@ -681,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 {};
@@ -716,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(
@@ -814,7 +823,8 @@
// to be applied as per normal (no synchronization).
mCurrentState.barrierLayer_legacy = nullptr;
} else {
- auto syncPoint = std::make_shared<SyncPoint>(mCurrentState.barrierFrameNumber, this);
+ auto syncPoint = std::make_shared<SyncPoint>(mCurrentState.barrierFrameNumber, this,
+ barrierLayer);
if (barrierLayer->addSyncPoint(syncPoint)) {
std::stringstream ss;
ss << "Adding sync point " << mCurrentState.barrierFrameNumber;
@@ -839,7 +849,7 @@
ATRACE_CALL();
*stateToCommit = mPendingStates[0];
- mPendingStates.removeAt(0);
+ mPendingStates.pop_front();
ATRACE_INT(mTransactionName.c_str(), mPendingStates.size());
}
@@ -878,6 +888,7 @@
mRemoteSyncPoints.pop_front();
} else {
ATRACE_NAME("!frameIsAvailable");
+ mRemoteSyncPoints.front()->checkTimeoutAndLog();
break;
}
} else {
@@ -901,17 +912,16 @@
? std::nullopt
: std::make_optional(stateToCommit->frameTimelineVsyncId);
- auto surfaceFrame =
- mFlinger->mFrameTimeline->createSurfaceFrameForToken(getOwnerPid(), getOwnerUid(),
- mName, mTransactionName,
- vsyncId);
- surfaceFrame->setActualQueueTime(stateToCommit->postTime);
+ mSurfaceFrame =
+ 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
// BufferStateLayer::applyPendingStates
- surfaceFrame->setAcquireFenceTime(stateToCommit->postTime);
+ mSurfaceFrame->setAcquireFenceTime(stateToCommit->postTime);
- mSurfaceFrame = std::move(surfaceFrame);
+ onSurfaceFrameCreated(mSurfaceFrame);
}
mCurrentState.modified = false;
@@ -1057,7 +1067,8 @@
void Layer::commitTransaction(const State& stateToCommit) {
mDrawingState = stateToCommit;
- mFlinger->mFrameTimeline->addSurfaceFrame(std::move(mSurfaceFrame), PresentState::Presented);
+ mSurfaceFrame->setPresentState(PresentState::Presented);
+ mFlinger->mFrameTimeline->addSurfaceFrame(mSurfaceFrame);
}
uint32_t Layer::getTransactionFlags(uint32_t flags) {
@@ -1274,7 +1285,8 @@
t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
- ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER ignored");
+ ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
+ "ROTATE_SURFACE_FLINGER ignored");
return false;
}
mCurrentState.sequence++;
@@ -1423,7 +1435,8 @@
// First traverse the tree and count how many layers has votes
int layersWithVote = 0;
traverseTree([&layersWithVote](Layer* layer) {
- const auto layerVotedWithDefaultCompatibility = layer->mCurrentState.frameRate.rate > 0 &&
+ const auto layerVotedWithDefaultCompatibility =
+ layer->mCurrentState.frameRate.rate.isValid() &&
layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
const auto layerVotedWithNoVote =
layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote;
@@ -1484,14 +1497,14 @@
Layer::FrameRate Layer::getFrameRateForLayerTree() const {
const auto frameRate = getDrawingState().frameRate;
- if (frameRate.rate > 0 || frameRate.type == FrameRateCompatibility::NoVote) {
+ if (frameRate.rate.isValid() || frameRate.type == FrameRateCompatibility::NoVote) {
return frameRate;
}
// This layer doesn't have a frame rate. If one of its ancestors or successors
// have a vote, return a NoVote for ancestors/successors to set the vote
if (getDrawingState().treeHasFrameRateVote) {
- return {0, FrameRateCompatibility::NoVote};
+ return {Fps(0.0f), FrameRateCompatibility::NoVote};
}
return frameRate;
@@ -1619,11 +1632,8 @@
}
void Layer::miniDumpHeader(std::string& result) {
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------\n");
+ result.append(kDumpTableRowLength, '-');
+ result.append("\n");
result.append(" Layer name\n");
result.append(" Z | ");
result.append(" Window Type | ");
@@ -1631,12 +1641,9 @@
result.append(" Transform | ");
result.append(" Disp Frame (LTRB) | ");
result.append(" Source Crop (LTRB) | ");
- result.append(" Frame Rate (Explicit) [Focused]\n");
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------------------");
- result.append("-------------------\n");
+ result.append(" Frame Rate (Explicit) (Seamlessness) [Focused]\n");
+ result.append(kDumpTableRowLength, '-');
+ result.append("\n");
}
std::string Layer::frameRateCompatibilityString(Layer::FrameRateCompatibility compatibility) {
@@ -1685,23 +1692,20 @@
const FloatRect& crop = outputLayerState.sourceCrop;
StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
crop.bottom);
- if (layerState.frameRate.rate != 0 ||
+ if (layerState.frameRate.rate.isValid() ||
layerState.frameRate.type != FrameRateCompatibility::Default) {
- StringAppendF(&result, "% 6.2ffps %15s seamless=%s", layerState.frameRate.rate,
+ StringAppendF(&result, "%s %15s %17s", to_string(layerState.frameRate.rate).c_str(),
frameRateCompatibilityString(layerState.frameRate.type).c_str(),
toString(layerState.frameRate.seamlessness).c_str());
} else {
- StringAppendF(&result, " ");
+ result.append(41, ' ');
}
const auto focused = isLayerFocusedBasedOnPriority(getFrameRateSelectionPriority());
StringAppendF(&result, " [%s]\n", focused ? "*" : " ");
- result.append("- - - - - - - - - - - - - - - - ");
- result.append("- - - - - - - - - - - - - - - - ");
- result.append("- - - - - - - - - - - - - - - - ");
- result.append("- - - - - - - - - - - - - - - - ");
- result.append("- - - - - - - -\n");
+ result.append(kDumpTableRowLength, '-');
+ result.append("\n");
}
void Layer::dumpFrameStats(std::string& result) const {
@@ -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 {
@@ -2434,27 +2441,7 @@
return mRemovedFromCurrentState;
}
-InputWindowInfo Layer::fillInputInfo() {
- if (!hasInputInfo()) {
- mDrawingState.inputInfo.name = getName();
- mDrawingState.inputInfo.ownerUid = mCallingUid;
- mDrawingState.inputInfo.ownerPid = mCallingPid;
- mDrawingState.inputInfo.inputFeatures = InputWindowInfo::Feature::NO_INPUT_CHANNEL;
- mDrawingState.inputInfo.flags = InputWindowInfo::Flag::NOT_TOUCH_MODAL;
- mDrawingState.inputInfo.displayId = getLayerStack();
- }
-
- InputWindowInfo info = mDrawingState.inputInfo;
- info.id = sequence;
-
- if (info.displayId == ADISPLAY_ID_NONE) {
- info.displayId = getLayerStack();
- }
-
- ui::Transform t = getTransform();
- int32_t xSurfaceInset = info.surfaceInset;
- int32_t ySurfaceInset = info.surfaceInset;
-
+void Layer::fillInputFrameInfo(InputWindowInfo& info) {
// Transform layer size to screen space and inset it by surface insets.
// If this is a portal window, set the touchableRegion to the layerBounds.
Rect layerBounds = info.portalToDisplayId == ADISPLAY_ID_NONE
@@ -2464,6 +2451,20 @@
layerBounds = getCroppedBufferSize(getDrawingState());
}
+ if (!layerBounds.isValid()) {
+ // If the layer bounds is empty, set the frame to empty and clear the transform
+ info.frameLeft = 0;
+ info.frameTop = 0;
+ info.frameRight = 0;
+ info.frameBottom = 0;
+ info.transform.reset();
+ return;
+ }
+
+ ui::Transform t = getTransform();
+ int32_t xSurfaceInset = info.surfaceInset;
+ int32_t ySurfaceInset = info.surfaceInset;
+
const float xScale = t.getScaleX();
const float yScale = t.getScaleY();
if (xScale != 1.0f || yScale != 1.0f) {
@@ -2530,6 +2531,27 @@
// Position the touchable region relative to frame screen location and restrict it to frame
// bounds.
info.touchableRegion = inputTransform.transform(info.touchableRegion);
+}
+
+InputWindowInfo Layer::fillInputInfo() {
+ if (!hasInputInfo()) {
+ mDrawingState.inputInfo.name = getName();
+ mDrawingState.inputInfo.ownerUid = mOwnerUid;
+ mDrawingState.inputInfo.ownerPid = mOwnerPid;
+ mDrawingState.inputInfo.inputFeatures = InputWindowInfo::Feature::NO_INPUT_CHANNEL;
+ mDrawingState.inputInfo.flags = InputWindowInfo::Flag::NOT_TOUCH_MODAL;
+ mDrawingState.inputInfo.displayId = getLayerStack();
+ }
+
+ InputWindowInfo info = mDrawingState.inputInfo;
+ info.id = sequence;
+
+ if (info.displayId == ADISPLAY_ID_NONE) {
+ info.displayId = getLayerStack();
+ }
+
+ fillInputFrameInfo(info);
+
// For compatibility reasons we let layers which can receive input
// receive input before they have actually submitted a buffer. Because
// of this we use canReceiveInput instead of isVisible to check the
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 75d68a1..f78b5f3 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -36,6 +36,7 @@
#include <utils/RefBase.h>
#include <utils/Timers.h>
+#include <chrono>
#include <cstdint>
#include <list>
#include <optional>
@@ -45,6 +46,7 @@
#include "ClientCache.h"
#include "DisplayHardware/ComposerHal.h"
#include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
#include "FrameTracker.h"
#include "LayerVector.h"
#include "MonitoredProducer.h"
@@ -155,7 +157,7 @@
struct FrameRate {
using Seamlessness = scheduler::Seamlessness;
- float rate;
+ Fps rate;
FrameRateCompatibility type;
Seamlessness seamlessness;
@@ -163,11 +165,12 @@
: rate(0),
type(FrameRateCompatibility::Default),
seamlessness(Seamlessness::Default) {}
- FrameRate(float rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
+ FrameRate(Fps rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
: rate(rate), type(type), seamlessness(getSeamlessness(rate, shouldBeSeamless)) {}
bool operator==(const FrameRate& other) const {
- return rate == other.rate && type == other.type && seamlessness == other.seamlessness;
+ return rate.equalsWithMargin(other.rate) && type == other.type &&
+ seamlessness == other.seamlessness;
}
bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -177,8 +180,8 @@
static FrameRateCompatibility convertCompatibility(int8_t compatibility);
private:
- static Seamlessness getSeamlessness(float rate, bool shouldBeSeamless) {
- if (rate == 0.0f) {
+ static Seamlessness getSeamlessness(Fps rate, bool shouldBeSeamless) {
+ if (!rate.isValid()) {
// Refresh rate of 0 is a special value which should reset the vote to
// its default value.
return Seamlessness::Default;
@@ -272,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.
@@ -441,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 */, std::optional<nsecs_t> /* dequeueTime */) {
return false;
};
virtual bool setAcquireFence(const sp<Fence>& /*fence*/) { return false; };
@@ -478,6 +483,8 @@
virtual void useSurfaceDamage() {}
virtual void useEmptyDamage() {}
+ virtual void incrementPendingBufferCount() {}
+
/*
* isOpaque - true if this surface is opaque
*
@@ -742,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
@@ -876,7 +883,7 @@
*/
bool hasInputInfo() const;
- uid_t getOwnerUid() { return mOwnerUid; }
+ virtual uid_t getOwnerUid() const { return mOwnerUid; }
pid_t getOwnerPid() { return mOwnerPid; }
@@ -900,12 +907,13 @@
protected:
class SyncPoint {
public:
- explicit SyncPoint(uint64_t frameNumber, wp<Layer> requestedSyncLayer)
+ explicit SyncPoint(uint64_t frameNumber, wp<Layer> requestedSyncLayer,
+ wp<Layer> barrierLayer_legacy)
: mFrameNumber(frameNumber),
mFrameIsAvailable(false),
mTransactionIsApplied(false),
- mRequestedSyncLayer(requestedSyncLayer) {}
-
+ mRequestedSyncLayer(requestedSyncLayer),
+ mBarrierLayer_legacy(barrierLayer_legacy) {}
uint64_t getFrameNumber() const { return mFrameNumber; }
bool frameIsAvailable() const { return mFrameIsAvailable; }
@@ -918,11 +926,42 @@
sp<Layer> getRequestedSyncLayer() { return mRequestedSyncLayer.promote(); }
+ sp<Layer> getBarrierLayer() const { return mBarrierLayer_legacy.promote(); }
+
+ bool isTimeout() const {
+ using namespace std::chrono_literals;
+ static constexpr std::chrono::nanoseconds TIMEOUT_THRESHOLD = 1s;
+
+ return std::chrono::steady_clock::now() - mCreateTimeStamp > TIMEOUT_THRESHOLD;
+ }
+
+ void checkTimeoutAndLog() {
+ using namespace std::chrono_literals;
+ static constexpr std::chrono::nanoseconds LOG_PERIOD = 1s;
+
+ if (!frameIsAvailable() && isTimeout()) {
+ const auto now = std::chrono::steady_clock::now();
+ if (now - mLastLogTime > LOG_PERIOD) {
+ mLastLogTime = now;
+ sp<Layer> requestedSyncLayer = getRequestedSyncLayer();
+ sp<Layer> barrierLayer = getBarrierLayer();
+ ALOGW("[%s] sync point %" PRIu64 " wait timeout %lld for %s",
+ requestedSyncLayer ? requestedSyncLayer->getDebugName() : "Removed",
+ mFrameNumber, (now - mCreateTimeStamp).count(),
+ barrierLayer ? barrierLayer->getDebugName() : "Removed");
+ }
+ }
+ }
+
private:
const uint64_t mFrameNumber;
std::atomic<bool> mFrameIsAvailable;
std::atomic<bool> mTransactionIsApplied;
wp<Layer> mRequestedSyncLayer;
+ wp<Layer> mBarrierLayer_legacy;
+ const std::chrono::time_point<std::chrono::steady_clock> mCreateTimeStamp =
+ std::chrono::steady_clock::now();
+ std::chrono::time_point<std::chrono::steady_clock> mLastLogTime;
};
friend class impl::SurfaceInterceptor;
@@ -942,6 +981,7 @@
virtual void commitTransaction(const State& stateToCommit);
virtual bool applyPendingStates(State* stateToCommit);
virtual uint32_t doTransactionResize(uint32_t flags, Layer::State* stateToCommit);
+ virtual void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&) {}
// Returns mCurrentScaling mode (originating from the
// Client) or mOverrideScalingMode mode (originating from
@@ -1008,12 +1048,12 @@
State mDrawingState;
// Store a copy of the pending state so that the drawing thread can access the
// states without a lock.
- Vector<State> mPendingStatesSnapshot;
+ std::deque<State> mPendingStatesSnapshot;
// these are protected by an external lock (mStateLock)
State mCurrentState;
std::atomic<uint32_t> mTransactionFlags{0};
- Vector<State> mPendingStates;
+ std::deque<State> mPendingStates;
// Timestamp history for UIAutomation. Thread safe.
FrameTracker mFrameTracker;
@@ -1066,7 +1106,7 @@
const InputWindowInfo::Type mWindowType;
// Can only be accessed with the SF state lock held.
- std::unique_ptr<frametimeline::SurfaceFrame> mSurfaceFrame;
+ std::shared_ptr<frametimeline::SurfaceFrame> mSurfaceFrame;
// The owner of the layer. If created from a non system process, it will be the calling uid.
// If created from a system process, the value can be passed in.
@@ -1111,6 +1151,9 @@
// null.
sp<Layer> getRootLayer();
+ // Fills in the frame and transform info for the InputWindowInfo
+ void fillInputFrameInfo(InputWindowInfo& info);
+
// Cached properties computed from drawing state
// Effective transform taking into account parent transforms and any parent scaling, which is
// a transform from the current layer coordinate space to display(screen) coordinate space.
diff --git a/services/surfaceflinger/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/OWNERS b/services/surfaceflinger/OWNERS
index c5a4689..f273725 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -4,7 +4,5 @@
chaviw@google.com
lpy@google.com
racarr@google.com
-steventhomas@google.com
stoza@google.com
-vhau@google.com
vishnun@google.com
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 f676d5b..9230e72 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() {
@@ -190,7 +193,7 @@
Mutex::Autolock _l(mFlinger.mStateLock);
mLayer = mClient->getLayerUser(mIBinder);
- mLayer->setFrameRate(Layer::FrameRate(0, Layer::FrameRateCompatibility::NoVote));
+ mLayer->setFrameRate(Layer::FrameRate(Fps(0.0f), Layer::FrameRateCompatibility::NoVote));
// setting Layer's Z requires resorting layersSortedByZ
ssize_t idx = mFlinger.mCurrentState.layersSortedByZ.indexOf(mLayer);
@@ -202,26 +205,15 @@
return true;
}
-void RefreshRateOverlay::primeCache() {
- auto& allRefreshRates = mFlinger.mRefreshRateConfigs->getAllRefreshRates();
- if (allRefreshRates.size() == 1) {
- auto fps = allRefreshRates.begin()->second->getFps();
- 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());
- }
-
- 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) {
@@ -239,11 +233,12 @@
mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
}
-void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
- mCurrentFps = refreshRate.getFps();
- auto buffer = mBufferCache[*mCurrentFps][mFrame];
- mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {},
- mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */));
+void RefreshRateOverlay::changeRefreshRate(const Fps& fps) {
+ mCurrentFps = fps.getIntValue();
+ auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
+ mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
+ mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */),
+ std::nullopt /* dequeueTime */);
mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
}
@@ -251,16 +246,24 @@
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->getHeadFrameNumber(-1 /* expectedPresentTime */));
+ mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
+ mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */),
+ std::nullopt /* dequeueTime */);
mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
}
+void RefreshRateOverlay::reset() {
+ mBufferCache.clear();
+ const auto range = mFlinger.mRefreshRateConfigs->getSupportedRefreshRateRange();
+ mLowFps = range.min.getIntValue();
+ mHighFps = range.max.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..c16cfa0 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -23,7 +23,7 @@
#include <ui/Size.h>
#include <utils/StrongPointer.h>
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Fps.h"
namespace android {
@@ -34,15 +34,14 @@
class Layer;
class SurfaceFlinger;
-using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
-
class RefreshRateOverlay {
public:
RefreshRateOverlay(SurfaceFlinger&, bool showSpinner);
void setViewport(ui::Size);
- void changeRefreshRate(const RefreshRate&);
+ void changeRefreshRate(const Fps&);
void onInvalidate();
+ void reset();
private:
class SevenSegmentDrawer {
@@ -71,7 +70,7 @@
};
bool createLayer();
- void primeCache();
+ const std::vector<sp<GraphicBuffer>>& getOrCreateBuffers(uint32_t fps);
SurfaceFlinger& mFlinger;
const sp<Client> mClient;
@@ -87,6 +86,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 c0d00f3..170933d 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -41,7 +41,7 @@
bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
// Layers with an explicit vote are always kept active
- if (layer.getFrameRateForLayerTree().rate > 0) {
+ if (layer.getFrameRateForLayerTree().rate.isValid()) {
return true;
}
@@ -86,10 +86,8 @@
LayerHistory::~LayerHistory() = default;
-void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
- LayerVoteType type) {
- const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
- 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));
}
@@ -144,11 +142,11 @@
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, static_cast<int>(std::round(vote.fps)));
+ trace(layer, *info, vote.type, vote.fps.getIntValue());
}
}
@@ -177,7 +175,7 @@
}
}();
- if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+ if (frameRate.rate.isValid() || voteType == LayerVoteType::NoVote) {
const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
} else {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 507ccc6..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*, float lowRefreshRate, float 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 66ac98a..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,12 +37,10 @@
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, 0.0f}),
+ mLayerVote({defaultVote, Fps(0.0f)}),
mRefreshRateHistory(name) {}
void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
@@ -91,7 +93,8 @@
// Layer is considered frequent if the average frame rate is higher than the threshold
const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
- return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
+ return Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
+ .greaterThanOrEqualWithMargin(MIN_FPS_FOR_FREQUENT_LAYER);
}
bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -132,21 +135,21 @@
}
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)) {
missingPresentTime = true;
// If there are no presentation timestamps and we haven't calculated
// one in the past then we can't calculate the refresh rate
- if (mLastRefreshRate.reported == 0) {
+ if (!mLastRefreshRate.reported.isValid()) {
return std::nullopt;
}
continue;
}
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
@@ -163,7 +166,7 @@
return static_cast<nsecs_t>(averageFrameTime);
}
-std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
static constexpr float MARGIN = 1.0f; // 1Hz
if (!hasEnoughDataForHeuristic()) {
ALOGV("Not enough data");
@@ -172,7 +175,7 @@
const auto averageFrameTime = calculateAverageFrameTime();
if (averageFrameTime.has_value()) {
- const auto refreshRate = 1e9f / *averageFrameTime;
+ const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
if (refreshRateConsistent) {
const auto knownRefreshRate =
@@ -180,22 +183,23 @@
// To avoid oscillation, use the last calculated refresh rate if it is
// close enough
- if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
- mLastRefreshRate.reported != knownRefreshRate) {
+ if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
+ MARGIN &&
+ !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
mLastRefreshRate.calculated = refreshRate;
mLastRefreshRate.reported = knownRefreshRate;
}
- ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
+ ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
+ to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
} else {
- ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
+ ALOGV("%s Not stable (%s) returning last known frame rate %s", mName.c_str(),
+ to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
}
}
- return mLastRefreshRate.reported == 0 ? std::nullopt
- : std::make_optional(mLastRefreshRate.reported);
+ return mLastRefreshRate.reported.isValid() ? std::make_optional(mLastRefreshRate.reported)
+ : std::nullopt;
}
LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
@@ -207,13 +211,13 @@
if (isAnimating(now)) {
ALOGV("%s is animating", mName.c_str());
mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Max, 0};
+ return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
}
if (!isFrequent(now)) {
ALOGV("%s is infrequent", mName.c_str());
mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Min, 0};
+ return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
}
// If the layer was previously tagged as animating or infrequent, we clear
@@ -225,12 +229,12 @@
auto refreshRate = calculateRefreshRateIfPossible(now);
if (refreshRate.has_value()) {
- ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+ ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
}
ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
- return {LayerHistory::LayerVoteType::Max, 0};
+ return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
}
const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
@@ -256,7 +260,7 @@
mRefreshRates.clear();
}
-bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
mRefreshRates.push_back({refreshRate, now});
while (mRefreshRates.size() >= HISTORY_SIZE ||
now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
@@ -268,7 +272,7 @@
mHeuristicTraceTagData = makeHeuristicTraceTagData();
}
- ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
}
return isConsistent();
@@ -279,15 +283,16 @@
const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
- const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+ const auto consistent =
+ max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
if (CC_UNLIKELY(sTraceEnabled)) {
if (!mHeuristicTraceTagData.has_value()) {
mHeuristicTraceTagData = makeHeuristicTraceTagData();
}
- ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
- ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
+ ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
}
@@ -295,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 e434670..427cc9e 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -50,9 +50,9 @@
// is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
// favor of a low refresh rate.
static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+ static constexpr Fps MIN_FPS_FOR_FREQUENT_LAYER{10.0f};
static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
- std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
+ std::chrono::nanoseconds(MIN_FPS_FOR_FREQUENT_LAYER.getPeriodNsecs()) + 1ms;
friend class LayerHistoryTest;
@@ -60,7 +60,7 @@
// Holds information about the layer vote
struct LayerVote {
LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
- float fps = 0.0f;
+ Fps fps{0.0f};
Seamlessness seamlessness = Seamlessness::Default;
};
@@ -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;
@@ -92,7 +91,7 @@
void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
// Resets the layer vote to its default.
- void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+ void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; }
LayerVote getRefreshRateVote(nsecs_t now);
@@ -130,9 +129,9 @@
// Holds information about the calculated and reported refresh rate
struct RefreshRateHeuristicData {
// Rate calculated on the layer
- float calculated = 0.0f;
+ Fps calculated{0.0f};
// Last reported rate for LayerInfo::getRefreshRate()
- float reported = 0.0f;
+ Fps reported{0.0f};
// Whether the last reported rate for LayerInfo::getRefreshRate()
// was due to animation or infrequent updates
bool animatingOrInfrequent = false;
@@ -151,18 +150,20 @@
void clear();
// Adds a new refresh rate and returns true if it is consistent
- bool add(float refreshRate, nsecs_t now);
+ bool add(Fps refreshRate, nsecs_t now);
private:
friend class LayerHistoryTest;
// Holds the refresh rate when it was calculated
struct RefreshRateData {
- float refreshRate = 0.0f;
+ Fps refreshRate{0.0f};
nsecs_t timestamp = 0;
bool operator<(const RefreshRateData& other) const {
- return refreshRate < other.refreshRate;
+ // We don't need comparison with margins since we are using
+ // this to find the min and max refresh rates.
+ return refreshRate.getValue() < other.refreshRate.getValue();
}
};
@@ -180,20 +181,21 @@
const std::string mName;
mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
std::deque<RefreshRateData> mRefreshRates;
- static constexpr float MARGIN_FPS = 1.0;
+ static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
};
bool isFrequent(nsecs_t now) const;
bool isAnimating(nsecs_t now) const;
bool hasEnoughDataForHeuristic() const;
- std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+ std::optional<Fps> calculateRefreshRateIfPossible(nsecs_t now);
std::optional<nsecs_t> calculateAverageFrameTime() const;
bool isFrameTimeValid(const FrameTimeData&) const;
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 83fa20e..975754b 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -17,11 +17,16 @@
// #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>
#include <chrono>
#include <cmath>
+#include "../SurfaceFlingerProperties.h"
#undef LOG_TAG
#define LOG_TAG "RefreshRateConfigs"
@@ -29,11 +34,31 @@
namespace android::scheduler {
namespace {
std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
- return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %.2fHz",
- layer.name.c_str(),
+ return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %s", layer.name.c_str(),
RefreshRateConfigs::layerVoteTypeString(layer.vote).c_str(), weight,
- toString(layer.seamlessness).c_str(), layer.desiredRefreshRate);
+ toString(layer.seamlessness).c_str(),
+ to_string(layer.desiredRefreshRate).c_str());
}
+
+std::vector<Fps> constructKnownFrameRates(
+ const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
+ std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
+ knownFrameRates.reserve(knownFrameRates.size() + configs.size());
+
+ // Add all supported refresh rates to the set
+ for (const auto& config : configs) {
+ const auto refreshRate = Fps::fromPeriodNsecs(config->getVsyncPeriod());
+ knownFrameRates.emplace_back(refreshRate);
+ }
+
+ // Sort and remove duplicates
+ std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
+ knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
+ Fps::EqualsWithMargin()),
+ knownFrameRates.end());
+ return knownFrameRates;
+}
+
} // namespace
using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
@@ -41,7 +66,7 @@
std::string RefreshRate::toString() const {
return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
- getConfigId().value(), hwcConfig->getId(), getFps(),
+ getConfigId().value(), hwcConfig->getId(), getFps().getValue(),
hwcConfig->getWidth(), hwcConfig->getHeight(), getConfigGroup());
}
@@ -64,26 +89,94 @@
std::string RefreshRateConfigs::Policy::toString() const {
return base::StringPrintf("default config ID: %d, allowGroupSwitching = %d"
- ", primary range: [%.2f %.2f], app request range: [%.2f %.2f]",
- defaultConfig.value(), allowGroupSwitching, primaryRange.min,
- primaryRange.max, appRequestRange.min, appRequestRange.max);
+ ", primary range: %s, app request range: %s",
+ defaultConfig.value(), allowGroupSwitching,
+ primaryRange.toString().c_str(), appRequestRange.toString().c_str());
}
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};
}
-const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
- const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
- GlobalSignals* outSignalsConsidered) const {
+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;
+};
+
+RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+ const GlobalSignals& globalSignals,
+ GlobalSignals* outSignalsConsidered) const {
ATRACE_CALL();
ALOGV("getBestRefreshRate %zu layers", layers.size());
@@ -144,7 +237,8 @@
// move out the of range if layers explicitly request a different refresh
// rate.
const Policy* policy = getCurrentPolicyLocked();
- const bool primaryRangeIsSingleRate = policy->primaryRange.min == policy->primaryRange.max;
+ const bool primaryRangeIsSingleRate =
+ policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
if (!globalSignals.touch && globalSignals.idle &&
!(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
@@ -164,11 +258,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);
@@ -183,12 +277,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;
}
@@ -197,7 +292,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;
}
@@ -208,18 +304,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
@@ -227,80 +325,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 / scores.back().first->fps;
- // 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->name.c_str(), layerScore);
- scores[i].second += weight * layerScore;
- continue;
- }
-
- const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();
- const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
- 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->name.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->name.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;
}
}
@@ -315,7 +344,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();
@@ -331,7 +360,7 @@
const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&
- bestRefreshRate->fps < touchRefreshRate.fps) {
+ bestRefreshRate->fps.lessThanWithMargin(touchRefreshRate.fps)) {
setTouchConsidered();
ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
return touchRefreshRate;
@@ -340,16 +369,116 @@
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->name.c_str(), score);
+ ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
- ATRACE_INT(refreshRate->name.c_str(), round<int>(score * 100));
+ ATRACE_INT(refreshRate->getName().c_str(), round<int>(score * 100));
if (score > max * (1 + EPSILON)) {
max = score;
@@ -360,13 +489,20 @@
return bestRefreshRate;
}
-const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
- return mRefreshRates;
-}
-
-const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
+std::optional<Fps> RefreshRateConfigs::onKernelTimerChanged(
+ std::optional<HwcConfigIndexType> desiredActiveConfigId, bool timerExpired) const {
std::lock_guard lock(mLock);
- return getMinRefreshRateByPolicyLocked();
+
+ const auto& current = desiredActiveConfigId ? *mRefreshRates.at(*desiredActiveConfigId)
+ : *mCurrentRefreshRate;
+ const auto& min = *mMinSupportedRefreshRate;
+
+ if (current != min) {
+ const auto& refreshRate = timerExpired ? min : current;
+ return refreshRate.getFps();
+ }
+
+ return {};
}
const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
@@ -382,7 +518,7 @@
return *mPrimaryRefreshRates.front();
}
-const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+RefreshRate RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
std::lock_guard lock(mLock);
return getMaxRefreshRateByPolicyLocked();
}
@@ -401,12 +537,12 @@
return *mPrimaryRefreshRates.back();
}
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRate() const {
+RefreshRate RefreshRateConfigs::getCurrentRefreshRate() const {
std::lock_guard lock(mLock);
return *mCurrentRefreshRate;
}
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
+RefreshRate RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
std::lock_guard lock(mLock);
return getCurrentRefreshRateByPolicyLocked();
}
@@ -428,15 +564,23 @@
const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
HwcConfigIndexType currentConfigId)
: mKnownFrameRates(constructKnownFrameRates(configs)) {
+ updateDisplayConfigs(configs, currentConfigId);
+}
+
+void RefreshRateConfigs::updateDisplayConfigs(
+ const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
+ HwcConfigIndexType currentConfigId) {
+ std::lock_guard lock(mLock);
LOG_ALWAYS_FATAL_IF(configs.empty());
+ LOG_ALWAYS_FATAL_IF(currentConfigId.value() < 0);
LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
+ mRefreshRates.clear();
for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
const auto& config = configs.at(static_cast<size_t>(configId.value()));
- const float fps = 1e9f / config->getVsyncPeriod();
+ const auto fps = Fps::fromPeriodNsecs(config->getVsyncPeriod());
mRefreshRates.emplace(configId,
- std::make_unique<RefreshRate>(configId, config,
- base::StringPrintf("%.2ffps", fps), fps,
+ std::make_unique<RefreshRate>(configId, config, fps,
RefreshRate::ConstructorTag(0)));
if (configId == currentConfigId) {
mCurrentRefreshRate = mRefreshRates.at(configId).get();
@@ -444,14 +588,27 @@
}
std::vector<const RefreshRate*> sortedConfigs;
- getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
+ getSortedRefreshRateListLocked([](const RefreshRate&) { return true; }, &sortedConfigs);
mDisplayManagerPolicy.defaultConfig = currentConfigId;
mMinSupportedRefreshRate = sortedConfigs.front();
mMaxSupportedRefreshRate = sortedConfigs.back();
+
+ mSupportsFrameRateOverride = false;
+ if (android::sysprop::enable_frame_rate_override(true)) {
+ for (const auto& config1 : sortedConfigs) {
+ for (const auto& config2 : sortedConfigs) {
+ if (getFrameRateDivider(config1->getFps(), config2->getFps()) >= 2) {
+ mSupportsFrameRateOverride = true;
+ break;
+ }
+ }
+ }
+ }
+
constructAvailableRefreshRates();
}
-bool RefreshRateConfigs::isPolicyValid(const Policy& policy) {
+bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const {
// defaultConfig must be a valid config, and within the given refresh rate range.
auto iter = mRefreshRates.find(policy.defaultConfig);
if (iter == mRefreshRates.end()) {
@@ -463,13 +620,13 @@
ALOGE("Default config is not in the primary range.");
return false;
}
- return policy.appRequestRange.min <= policy.primaryRange.min &&
- policy.appRequestRange.max >= policy.primaryRange.max;
+ return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) &&
+ policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max);
}
status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
std::lock_guard lock(mLock);
- if (!isPolicyValid(policy)) {
+ if (!isPolicyValidLocked(policy)) {
ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str());
return BAD_VALUE;
}
@@ -484,7 +641,7 @@
status_t RefreshRateConfigs::setOverridePolicy(const std::optional<Policy>& policy) {
std::lock_guard lock(mLock);
- if (policy && !isPolicyValid(*policy)) {
+ if (policy && !isPolicyValidLocked(*policy)) {
return BAD_VALUE;
}
Policy previousPolicy = *getCurrentPolicyLocked();
@@ -520,14 +677,14 @@
return false;
}
-void RefreshRateConfigs::getSortedRefreshRateList(
+void RefreshRateConfigs::getSortedRefreshRateListLocked(
const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
std::vector<const RefreshRate*>* outRefreshRates) {
outRefreshRates->clear();
outRefreshRates->reserve(mRefreshRates.size());
for (const auto& [type, refreshRate] : mRefreshRates) {
if (shouldAddRefreshRate(*refreshRate)) {
- ALOGV("getSortedRefreshRateList: config %d added to list policy",
+ ALOGV("getSortedRefreshRateListLocked: config %d added to list policy",
refreshRate->configId.value());
outRefreshRates->push_back(refreshRate.get());
}
@@ -550,14 +707,12 @@
// Filter configs based on current policy and sort based on vsync period
const Policy* policy = getCurrentPolicyLocked();
const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig)->hwcConfig;
- ALOGV("constructAvailableRefreshRates: default %d group %d primaryRange=[%.2f %.2f]"
- " appRequestRange=[%.2f %.2f]",
- policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->primaryRange.min,
- policy->primaryRange.max, policy->appRequestRange.min, policy->appRequestRange.max);
+ ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
- auto filterRefreshRates = [&](float min, float max, const char* listName,
- std::vector<const RefreshRate*>* outRefreshRates) {
- getSortedRefreshRateList(
+ auto filterRefreshRates = [&](Fps min, Fps max, const char* listName,
+ std::vector<const RefreshRate*>*
+ outRefreshRates) REQUIRES(mLock) {
+ getSortedRefreshRateListLocked(
[&](const RefreshRate& refreshRate) REQUIRES(mLock) {
const auto& hwcConfig = refreshRate.hwcConfig;
@@ -572,12 +727,12 @@
outRefreshRates);
LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
- "No matching configs for %s range: min=%.0f max=%.0f", listName, min,
- max);
+ "No matching configs for %s range: min=%s max=%s", listName,
+ to_string(min).c_str(), to_string(max).c_str());
auto stringifyRefreshRates = [&]() -> std::string {
std::string str;
for (auto refreshRate : *outRefreshRates) {
- base::StringAppendF(&str, "%s ", refreshRate->name.c_str());
+ base::StringAppendF(&str, "%s ", refreshRate->getName().c_str());
}
return str;
};
@@ -590,45 +745,26 @@
&mAppRequestRefreshRates);
}
-std::vector<float> RefreshRateConfigs::constructKnownFrameRates(
- const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
- std::vector<float> knownFrameRates = {24.0f, 30.0f, 45.0f, 60.0f, 72.0f};
- knownFrameRates.reserve(knownFrameRates.size() + configs.size());
-
- // Add all supported refresh rates to the set
- for (const auto& config : configs) {
- const auto refreshRate = 1e9f / config->getVsyncPeriod();
- knownFrameRates.emplace_back(refreshRate);
- }
-
- // Sort and remove duplicates
- const auto frameRatesEqual = [](float a, float b) { return std::abs(a - b) <= 0.01f; };
- std::sort(knownFrameRates.begin(), knownFrameRates.end());
- knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
- frameRatesEqual),
- knownFrameRates.end());
- return knownFrameRates;
-}
-
-float RefreshRateConfigs::findClosestKnownFrameRate(float frameRate) const {
- if (frameRate <= *mKnownFrameRates.begin()) {
+Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
+ if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
return *mKnownFrameRates.begin();
}
- if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+ if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) {
return *std::prev(mKnownFrameRates.end());
}
- auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+ auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
+ Fps::comparesLess);
- const auto distance1 = std::abs(frameRate - *lowerBound);
- const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+ const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue()));
+ const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue()));
return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
}
RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const {
std::lock_guard lock(mLock);
- const auto& deviceMin = getMinRefreshRate();
+ const auto& deviceMin = *mMinSupportedRefreshRate;
const auto& minByPolicy = getMinRefreshRateByPolicyLocked();
const auto& maxByPolicy = getMaxRefreshRateByPolicyLocked();
@@ -650,50 +786,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] = 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() / refreshRateHz;
+ 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});
- }
-
- return overrides;
+ return getFrameRateDivider(mCurrentRefreshRate->getFps(), frameRate);
}
void RefreshRateConfigs::dump(std::string& result) const {
@@ -715,7 +823,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 6e0c0d3..4b99145 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -25,6 +25,7 @@
#include <type_traits>
#include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
#include "HwcStrongTypes.h"
#include "Scheduler/SchedulerUtils.h"
#include "Scheduler/Seamlessness.h"
@@ -64,29 +65,29 @@
public:
RefreshRate(HwcConfigIndexType configId,
- std::shared_ptr<const HWC2::Display::Config> config, std::string name,
- float fps, ConstructorTag)
- : configId(configId), hwcConfig(config), name(std::move(name)), fps(fps) {}
-
- RefreshRate(const RefreshRate&) = delete;
+ std::shared_ptr<const HWC2::Display::Config> config, Fps fps, ConstructorTag)
+ : configId(configId), hwcConfig(config), fps(std::move(fps)) {}
HwcConfigIndexType getConfigId() const { return configId; }
nsecs_t getVsyncPeriod() const { return hwcConfig->getVsyncPeriod(); }
int32_t getConfigGroup() const { return hwcConfig->getConfigGroup(); }
- const std::string& getName() const { return name; }
- float getFps() const { return fps; }
+ std::string getName() const { return to_string(fps); }
+ Fps getFps() const { return fps; }
// Checks whether the fps of this RefreshRate struct is within a given min and max refresh
- // rate passed in. FPS_EPSILON is applied to the boundaries for approximation.
- bool inPolicy(float minRefreshRate, float maxRefreshRate) const {
- return (fps >= (minRefreshRate - FPS_EPSILON) && fps <= (maxRefreshRate + FPS_EPSILON));
+ // rate passed in. Margin of error is applied to the boundaries for approximation.
+ bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
+ return minRefreshRate.lessThanOrEqualWithMargin(fps) &&
+ fps.lessThanOrEqualWithMargin(maxRefreshRate);
}
bool operator!=(const RefreshRate& other) const {
return configId != other.configId || hwcConfig != other.hwcConfig;
}
- bool operator<(const RefreshRate& other) const { return getFps() < other.getFps(); }
+ bool operator<(const RefreshRate& other) const {
+ return getFps().getValue() < other.getFps().getValue();
+ }
bool operator==(const RefreshRate& other) const { return !(*this != other); }
@@ -96,39 +97,38 @@
friend RefreshRateConfigs;
friend class RefreshRateConfigsTest;
- // The tolerance within which we consider FPS approximately equals.
- static constexpr float FPS_EPSILON = 0.001f;
-
// This config ID corresponds to the position of the config in the vector that is stored
// on the device.
const HwcConfigIndexType configId;
// The config itself
std::shared_ptr<const HWC2::Display::Config> hwcConfig;
- // Human readable name of the refresh rate.
- const std::string name;
// Refresh rate in frames per second
- const float fps = 0;
+ const Fps fps{0.0f};
};
using AllRefreshRatesMapType =
std::unordered_map<HwcConfigIndexType, std::unique_ptr<const RefreshRate>>;
+ struct FpsRange {
+ Fps min{0.0f};
+ Fps max{std::numeric_limits<float>::max()};
+
+ bool operator==(const FpsRange& other) const {
+ return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
+ }
+
+ bool operator!=(const FpsRange& other) const { return !(*this == other); }
+
+ std::string toString() const {
+ return base::StringPrintf("[%s %s]", to_string(min).c_str(), to_string(max).c_str());
+ }
+ };
+
struct Policy {
private:
static constexpr int kAllowGroupSwitchingDefault = false;
public:
- struct Range {
- float min = 0;
- float max = std::numeric_limits<float>::max();
-
- bool operator==(const Range& other) const {
- return min == other.min && max == other.max;
- }
-
- bool operator!=(const Range& other) const { return !(*this == other); }
- };
-
// The default config, used to ensure we only initiate display config switches within the
// same config group as defaultConfigId's group.
HwcConfigIndexType defaultConfig;
@@ -137,29 +137,29 @@
// The primary refresh rate range represents display manager's general guidance on the
// display configs we'll consider when switching refresh rates. Unless we get an explicit
// signal from an app, we should stay within this range.
- Range primaryRange;
+ FpsRange primaryRange;
// The app request refresh rate range allows us to consider more display configs when
// switching refresh rates. Although we should generally stay within the primary range,
// specific considerations, such as layer frame rate settings specified via the
// setFrameRate() api, may cause us to go outside the primary range. We never go outside the
// app request range. The app request range will be greater than or equal to the primary
// refresh rate range, never smaller.
- Range appRequestRange;
+ FpsRange appRequestRange;
Policy() = default;
- Policy(HwcConfigIndexType defaultConfig, const Range& range)
+ Policy(HwcConfigIndexType defaultConfig, const FpsRange& range)
: Policy(defaultConfig, kAllowGroupSwitchingDefault, range, range) {}
- Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching, const Range& range)
+ Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching, const FpsRange& range)
: Policy(defaultConfig, allowGroupSwitching, range, range) {}
- Policy(HwcConfigIndexType defaultConfig, const Range& primaryRange,
- const Range& appRequestRange)
+ Policy(HwcConfigIndexType defaultConfig, const FpsRange& primaryRange,
+ const FpsRange& appRequestRange)
: Policy(defaultConfig, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
Policy(HwcConfigIndexType defaultConfig, bool allowGroupSwitching,
- const Range& primaryRange, const Range& appRequestRange)
+ const FpsRange& primaryRange, const FpsRange& appRequestRange)
: defaultConfig(defaultConfig),
allowGroupSwitching(allowGroupSwitching),
primaryRange(primaryRange),
@@ -218,10 +218,12 @@
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.
- float desiredRefreshRate = 0.0f;
+ Fps desiredRefreshRate{0.0f};
// If a seamless mode switch is required.
Seamlessness seamlessness = Seamlessness::Default;
// Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
@@ -232,7 +234,7 @@
bool operator==(const LayerRequirement& other) const {
return name == other.name && vote == other.vote &&
- desiredRefreshRate == other.desiredRefreshRate &&
+ desiredRefreshRate.equalsWithMargin(other.desiredRefreshRate) &&
seamlessness == other.seamlessness && weight == other.weight &&
focused == other.focused;
}
@@ -253,38 +255,35 @@
// globalSignals - global state of touch and idle
// outSignalsConsidered - An output param that tells the caller whether the refresh rate was
// chosen based on touch boost and/or idle timer.
- const RefreshRate& getBestRefreshRate(const std::vector<LayerRequirement>& layers,
- const GlobalSignals& globalSignals,
- GlobalSignals* outSignalsConsidered = nullptr) const
+ RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+ const GlobalSignals& globalSignals,
+ 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);
+ FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
+ return {mMinSupportedRefreshRate->getFps(), mMaxSupportedRefreshRate->getFps()};
+ }
- // Returns the lowest refresh rate supported by the device. This won't change at runtime.
- const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
-
- // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
- // uses the primary range, not the app request range.
- const RefreshRate& getMinRefreshRateByPolicy() const EXCLUDES(mLock);
-
- // Returns the highest refresh rate supported by the device. This won't change at runtime.
- const RefreshRate& getMaxRefreshRate() const { return *mMaxSupportedRefreshRate; }
+ std::optional<Fps> onKernelTimerChanged(std::optional<HwcConfigIndexType> desiredActiveConfigId,
+ bool timerExpired) const EXCLUDES(mLock);
// Returns the highest refresh rate according to the current policy. May change at runtime. Only
// uses the primary range, not the app request range.
- const RefreshRate& getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
+ RefreshRate getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
// Returns the current refresh rate
- const RefreshRate& getCurrentRefreshRate() const EXCLUDES(mLock);
+ RefreshRate getCurrentRefreshRate() const EXCLUDES(mLock);
// Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
// the policy.
- const RefreshRate& getCurrentRefreshRateByPolicy() const;
+ RefreshRate getCurrentRefreshRateByPolicy() const;
- // Returns the refresh rate that corresponds to a HwcConfigIndexType. This won't change at
+ // Returns the refresh rate that corresponds to a HwcConfigIndexType. This may change at
// runtime.
- const RefreshRate& getRefreshRateFromConfigId(HwcConfigIndexType configId) const {
+ // TODO(b/159590486) An invalid config id may be given here if the dipslay configs have changed.
+ RefreshRate getRefreshRateFromConfigId(HwcConfigIndexType configId) const EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
return *mRefreshRates.at(configId);
};
@@ -295,15 +294,22 @@
static std::string layerVoteTypeString(LayerVoteType vote);
// Returns a known frame rate that is the closest to frameRate
- float findClosestKnownFrameRate(float frameRate) const;
+ Fps findClosestKnownFrameRate(Fps frameRate) const;
RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
HwcConfigIndexType currentConfigId);
+ void updateDisplayConfigs(
+ const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
+ HwcConfigIndexType currentConfig) EXCLUDES(mLock);
+
// Returns whether switching configs (refresh rate or resolution) is possible.
// TODO(b/158780872): Consider HAL support, and skip frame rate detection if the configs only
// differ in resolution.
- bool canSwitch() const { return mRefreshRates.size() > 1; }
+ bool canSwitch() const EXCLUDES(mLock) {
+ std::lock_guard lock(mLock);
+ return mRefreshRates.size() > 1;
+ }
// Class to enumerate options around toggling the kernel timer on and off. We have an option
// for no change to avoid extra calls to kernel.
@@ -316,28 +322,26 @@
// 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;
void constructAvailableRefreshRates() REQUIRES(mLock);
- static std::vector<float> constructKnownFrameRates(
- const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
- void getSortedRefreshRateList(
+ void getSortedRefreshRateListLocked(
const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
- std::vector<const RefreshRate*>* outRefreshRates);
+ std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
// Returns the refresh rate with the highest score in the collection specified from begin
// to end. If there are more than one with the same highest refresh rate, the first one is
@@ -362,11 +366,21 @@
const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
- bool isPolicyValid(const Policy& policy);
+ bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
- // The list of refresh rates, indexed by display config ID. This must not change after this
+ // 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 may change after this
// object is initialized.
- AllRefreshRatesMapType mRefreshRates;
+ AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
// The list of refresh rates in the primary range of the current policy, ordered by vsyncPeriod
// (the first element is the lowest refresh rate).
@@ -385,20 +399,18 @@
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, float> mPreferredRefreshRateForUid GUARDED_BY(mLock);
-
// The min and max refresh rates supported by the device.
- // This will not change at runtime.
- const RefreshRate* mMinSupportedRefreshRate;
- const RefreshRate* mMaxSupportedRefreshRate;
+ // This may change at runtime.
+ const RefreshRate* mMinSupportedRefreshRate GUARDED_BY(mLock);
+ const RefreshRate* mMaxSupportedRefreshRate GUARDED_BY(mLock);
mutable std::mutex mLock;
// A sorted list of known frame rates that a Heuristic layer will choose
// from based on the closest value.
- const std::vector<float> mKnownFrameRates;
+ const std::vector<Fps> mKnownFrameRates;
+
+ bool mSupportsFrameRateOverride;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index d9e7b37..80f4665 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -18,7 +18,7 @@
#include <numeric>
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Fps.h"
#include "Scheduler/SchedulerUtils.h"
#include "TimeStats/TimeStats.h"
@@ -40,12 +40,10 @@
static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR;
public:
- RefreshRateStats(const RefreshRateConfigs& refreshRateConfigs, TimeStats& timeStats,
- HwcConfigIndexType currentConfigId,
+ RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate,
android::hardware::graphics::composer::hal::PowerMode currentPowerMode)
- : mRefreshRateConfigs(refreshRateConfigs),
- mTimeStats(timeStats),
- mCurrentConfigMode(currentConfigId),
+ : mTimeStats(timeStats),
+ mCurrentRefreshRate(currentRefreshRate),
mCurrentPowerMode(currentPowerMode) {}
// Sets power mode.
@@ -59,12 +57,12 @@
// Sets config mode. If the mode has changed, it records how much time was spent in the previous
// mode.
- void setConfigMode(HwcConfigIndexType configId) {
- if (mCurrentConfigMode == configId) {
+ void setRefreshRate(Fps currRefreshRate) {
+ if (mCurrentRefreshRate.equalsWithMargin(currRefreshRate)) {
return;
}
flushTime();
- mCurrentConfigMode = configId;
+ mCurrentRefreshRate = currRefreshRate;
}
// Returns a map between human readable refresh rate and number of seconds the device spent in
@@ -79,10 +77,10 @@
// Multiple configs may map to the same name, e.g. "60fps". Add the
// times for such configs together.
for (const auto& [configId, time] : mConfigModesTotalTime) {
- totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] = 0;
+ totalTime[to_string(configId)] = 0;
}
for (const auto& [configId, time] : mConfigModesTotalTime) {
- totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getName()] += time;
+ totalTime[to_string(configId)] += time;
}
totalTime["ScreenOff"] = mScreenOffTime;
return totalTime;
@@ -111,12 +109,11 @@
uint32_t fps = 0;
if (mCurrentPowerMode == android::hardware::graphics::composer::hal::PowerMode::ON) {
// Normal power mode is counted under different config modes.
- if (mConfigModesTotalTime.find(mCurrentConfigMode) == mConfigModesTotalTime.end()) {
- mConfigModesTotalTime[mCurrentConfigMode] = 0;
+ if (mConfigModesTotalTime.find(mCurrentRefreshRate) == mConfigModesTotalTime.end()) {
+ mConfigModesTotalTime[mCurrentRefreshRate] = 0;
}
- mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
- fps = static_cast<uint32_t>(std::round(
- mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).getFps()));
+ mConfigModesTotalTime[mCurrentRefreshRate] += timeElapsedMs;
+ fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
} else {
mScreenOffTime += timeElapsedMs;
}
@@ -134,16 +131,13 @@
days, hours, mins, sec, secRemainderMs);
}
- // Keeps information about refresh rate configs that device has.
- const RefreshRateConfigs& mRefreshRateConfigs;
-
// Aggregate refresh rate statistics for telemetry.
TimeStats& mTimeStats;
- HwcConfigIndexType mCurrentConfigMode;
+ Fps mCurrentRefreshRate;
android::hardware::graphics::composer::hal::PowerMode mCurrentPowerMode;
- std::unordered_map<HwcConfigIndexType /* configId */, int64_t /* duration in ms */>
+ std::unordered_map<Fps, int64_t /* duration in ms */, std::hash<Fps>, Fps::EqualsInBuckets>
mConfigModesTotalTime;
int64_t mScreenOffTime = 0;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 52bf483..49e3903 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,26 +564,19 @@
void Scheduler::registerLayer(Layer* layer) {
if (!mLayerHistory) return;
- const auto minFps = mRefreshRateConfigs.getMinRefreshRate().getFps();
- const auto maxFps = mRefreshRateConfigs.getMaxRefreshRate().getFps();
-
if (layer->getWindowType() == InputWindowInfo::Type::STATUS_BAR) {
- mLayerHistory->registerLayer(layer, minFps, 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, minFps, 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, minFps, maxFps,
- scheduler::LayerHistory::LayerVoteType::Min);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::Min);
} else {
- mLayerHistory->registerLayer(layer, minFps, maxFps,
- scheduler::LayerHistory::LayerVoteType::Heuristic);
+ mLayerHistory->registerLayer(layer, scheduler::LayerHistory::LayerVoteType::Heuristic);
}
}
}
@@ -553,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) {
@@ -561,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;
- auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+ }
+ if (frameRateChanged) {
+ auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
mSchedulerCallback.changeRefreshRate(newRefreshRate,
consideredSignals.idle ? ConfigEvent::None
: ConfigEvent::Changed);
}
+ if (frameRateOverridesChanged) {
+ mSchedulerCallback.triggerOnFrameRateOverridesChanged();
+ }
}
void Scheduler::resetIdleTimer() {
@@ -618,14 +678,15 @@
// TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
// magic number
const auto& refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
- constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
- if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+ constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER{65.0f};
+ if (state == TimerState::Reset &&
+ refreshRate.getFps().greaterThanWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
// If we're not in performance mode then the kernel timer shouldn't do
// anything, as the refresh rate during DPU power collapse will be the
// same.
resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
} else if (state == TimerState::Expired &&
- refreshRate.getFps() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+ refreshRate.getFps().lessThanOrEqualWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
// Disable HW VSYNC if the timer expired, as we don't need it enabled if
// we're not pushing frames, and if we're in PERFORMANCE mode then we'll
// need to update the VsyncController model anyway.
@@ -668,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 {
@@ -679,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);
@@ -690,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;
}
@@ -777,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 aac2569..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,50 +36,26 @@
return std::nullopt;
}
-bool fpsEqualsWithMargin(float fpsA, float fpsB) {
- static constexpr float MARGIN = 0.01f;
- return std::abs(fpsA - fpsB) <= MARGIN;
-}
-
-std::vector<float> getRefreshRatesFromConfigs(
- const android::scheduler::RefreshRateConfigs& refreshRateConfigs) {
- const auto& allRefreshRates = refreshRateConfigs.getAllRefreshRates();
- std::vector<float> refreshRates;
- refreshRates.reserve(allRefreshRates.size());
-
- for (const auto& [ignored, refreshRate] : allRefreshRates) {
- refreshRates.emplace_back(refreshRate->getFps());
- }
-
- return refreshRates;
-}
-
} // namespace
namespace android::scheduler::impl {
-VsyncConfiguration::VsyncConfiguration(float currentFps) : mRefreshRateFps(currentFps) {}
+VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {}
-PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(float fps) const {
- const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(),
- [&fps](const std::pair<float, VsyncConfigSet>& candidateFps) {
- return fpsEqualsWithMargin(fps, candidateFps.first);
- });
+PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const {
+ 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 %.2f fps", fps);
- return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
-}
-
-void VsyncConfiguration::initializeOffsets(const std::vector<float>& refreshRates) {
- for (const auto fps : refreshRates) {
- mOffsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
- }
+ const auto offset = constructOffsets(fps.getPeriodNsecs());
+ mOffsetsCache[fps] = offset;
+ return offset;
}
void VsyncConfiguration::dump(std::string& result) const {
@@ -103,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"),
@@ -126,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<float>& refreshRates, float 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),
@@ -148,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()) {
@@ -366,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),
@@ -378,18 +355,15 @@
validateSysprops();
}
-WorkDuration::WorkDuration(const std::vector<float>& refreshRates, float currentFps,
- nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
- nsecs_t appEarlyDuration, nsecs_t sfEarlyGpuDuration,
- nsecs_t appEarlyGpuDuration)
- : VsyncConfiguration(currentFps),
+WorkDuration::WorkDuration(Fps currentRefreshRate, nsecs_t sfDuration, nsecs_t appDuration,
+ nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+ nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration)
+ : 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 c27a25d..d9d206d 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -16,9 +16,14 @@
#pragma once
+#include <mutex>
+#include <type_traits>
#include <unordered_map>
+#include <vector>
-#include "RefreshRateConfigs.h"
+#include <utils/Timers.h>
+
+#include "Fps.h"
#include "VsyncModulator.h"
namespace android::scheduler {
@@ -35,10 +40,10 @@
virtual ~VsyncConfiguration() = default;
virtual VsyncConfigSet getCurrentConfigs() const = 0;
- virtual VsyncConfigSet getConfigsForRefreshRate(float fps) const = 0;
+ virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0;
+ virtual void reset() = 0;
- virtual void setRefreshRateFps(float fps) = 0;
-
+ virtual void setRefreshRateFps(Fps fps) = 0;
virtual void dump(std::string& result) const = 0;
};
@@ -51,29 +56,42 @@
*/
class VsyncConfiguration : public scheduler::VsyncConfiguration {
public:
- explicit VsyncConfiguration(float currentFps);
+ explicit VsyncConfiguration(Fps currentFps);
// Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
- VsyncConfigSet getConfigsForRefreshRate(float 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(float 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<float>& refreshRates);
virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0;
- std::unordered_map<float, VsyncConfigSet> mOffsets;
- std::atomic<float> 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;
};
/*
@@ -82,12 +100,11 @@
*/
class PhaseOffsets : public VsyncConfiguration {
public:
- explicit PhaseOffsets(const scheduler::RefreshRateConfigs&);
+ explicit PhaseOffsets(Fps currentRefreshRate);
protected:
// Used for unit tests
- PhaseOffsets(const std::vector<float>& refreshRates, float currentFps,
- nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
+ 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,
@@ -126,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<float>& refreshRates, float 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/StartPropertySetThread.cpp b/services/surfaceflinger/StartPropertySetThread.cpp
index db82772..f42cd53 100644
--- a/services/surfaceflinger/StartPropertySetThread.cpp
+++ b/services/surfaceflinger/StartPropertySetThread.cpp
@@ -31,6 +31,7 @@
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
// Clear BootAnimation exit flag
property_set("service.bootanim.exit", "0");
+ property_set("service.bootanim.progress", "0");
// Start BootAnimation if not started
property_set("ctl.start", "bootanim");
// Exit immediately
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 576bd50..2e00ca8 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"
@@ -274,6 +274,7 @@
const String16 sHardwareTest("android.permission.HARDWARE_TEST");
const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER");
+const String16 sRotateSurfaceFlinger("android.permission.ROTATE_SURFACE_FLINGER");
const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
const String16 sDump("android.permission.DUMP");
const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
@@ -324,6 +325,14 @@
}
}
+bool callingThreadHasRotateSurfaceFlingerAccess() {
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
+ PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid);
+}
+
SurfaceFlingerBE::SurfaceFlingerBE() : mHwcServiceName(getHwcServiceName()) {}
SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
@@ -331,7 +340,7 @@
mInterceptor(mFactory.createSurfaceInterceptor()),
mTimeStats(std::make_shared<impl::TimeStats>()),
mFrameTracer(mFactory.createFrameTracer()),
- mFrameTimeline(std::make_unique<frametimeline::impl::FrameTimeline>(mTimeStats)),
+ mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, getpid())),
mEventQueue(mFactory.createMessageQueue()),
mCompositionEngine(mFactory.createCompositionEngine()),
mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
@@ -621,6 +630,7 @@
mFrameTracer->initialize();
mTimeStats->onBootFinished();
+ mFrameTimeline->onBootFinished();
// wait patiently for the window manager death
const String16 name("window");
@@ -701,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);
@@ -920,10 +931,10 @@
}
const nsecs_t period = hwConfig->getVsyncPeriod();
- config.refreshRate = 1e9f / period;
+ config.refreshRate = Fps::fromPeriodNsecs(period).getValue();
const auto vsyncConfigSet =
- mVsyncConfiguration->getConfigsForRefreshRate(config.refreshRate);
+ mVsyncConfiguration->getConfigsForRefreshRate(Fps(config.refreshRate));
config.appVsyncOffset = vsyncConfigSet.late.appOffset;
config.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
config.configGroup = hwConfig->getConfigGroup();
@@ -953,7 +964,7 @@
return BAD_VALUE;
}
- mScheduler->getDisplayStatInfo(stats, systemTime());
+ *stats = mScheduler->getDisplayStatInfo(systemTime());
return NO_ERROR;
}
@@ -984,7 +995,7 @@
void SurfaceFlinger::setDesiredActiveConfig(const ActiveConfigInfo& info) {
ATRACE_CALL();
- auto& refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
+ auto refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
ALOGV("setDesiredActiveConfig(%s)", refreshRate.getName().c_str());
std::lock_guard<std::mutex> lock(mActiveConfigLock);
@@ -1019,7 +1030,7 @@
}
if (mRefreshRateOverlay) {
- mRefreshRateOverlay->changeRefreshRate(refreshRate);
+ mRefreshRateOverlay->changeRefreshRate(refreshRate.getFps());
}
}
@@ -1041,7 +1052,7 @@
return INVALID_OPERATION;
} else {
const HwcConfigIndexType config(mode);
- const float fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
+ const auto fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
// Keep the old switching type.
const auto allowGroupSwitching =
mRefreshRateConfigs->getCurrentPolicy().allowGroupSwitching;
@@ -1065,21 +1076,22 @@
return;
}
- auto& oldRefreshRate =
+ auto oldRefreshRate =
mRefreshRateConfigs->getRefreshRateFromConfigId(display->getActiveConfig());
std::lock_guard<std::mutex> lock(mActiveConfigLock);
mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
- mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
display->setActiveConfig(mUpcomingActiveConfig.configId);
- auto& refreshRate =
+ auto refreshRate =
mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
+ mRefreshRateStats->setRefreshRate(refreshRate.getFps());
+
if (refreshRate.getVsyncPeriod() != oldRefreshRate.getVsyncPeriod()) {
mTimeStats->incrementRefreshRateSwitches();
}
updatePhaseConfiguration(refreshRate);
- ATRACE_INT("ActiveConfigFPS", refreshRate.getFps());
+ ATRACE_INT("ActiveConfigFPS", refreshRate.getFps().getValue());
if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
const nsecs_t vsyncPeriod =
@@ -1114,7 +1126,7 @@
return;
}
- auto& refreshRate =
+ auto refreshRate =
mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig->configId);
ALOGV("performSetActiveConfig changing active config to %d(%s)",
refreshRate.getConfigId().value(), refreshRate.getName().c_str());
@@ -1137,7 +1149,7 @@
mUpcomingActiveConfig = *desiredActiveConfig;
const auto displayId = display->getPhysicalId();
- ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps());
+ ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps().getValue());
// TODO(b/142753666) use constrains
hal::VsyncPeriodChangeConstraints constraints;
@@ -1420,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();
@@ -1430,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
@@ -1501,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; })
@@ -1616,7 +1628,7 @@
void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hal::HWDisplayId hwcDisplayId,
hal::Connection connection) {
- ALOGV("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId,
+ ALOGI("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId,
connection == hal::Connection::CONNECTED ? "connected" : "disconnected");
// Ignore events that do not have the right sequenceId.
@@ -1709,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;
@@ -1759,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",
@@ -1861,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();
@@ -1905,19 +1914,17 @@
bool SurfaceFlinger::handleMessageTransaction() {
ATRACE_CALL();
+
+ if (getTransactionFlags(eTransactionFlushNeeded)) {
+ flushPendingTransactionQueues();
+ flushTransactionQueue();
+ }
uint32_t transactionFlags = peekTransactionFlags();
-
- bool flushedATransaction = flushTransactionQueues();
-
bool runHandleTransaction =
- (transactionFlags && (transactionFlags != eTransactionFlushNeeded)) ||
- flushedATransaction ||
- mForceTraversal;
+ ((transactionFlags & (~eTransactionFlushNeeded)) != 0) || mForceTraversal;
if (runHandleTransaction) {
handleTransaction(eTransactionMask);
- } else {
- getTransactionFlags(eTransactionFlushNeeded);
}
if (transactionFlushNeeded()) {
@@ -1988,9 +1995,6 @@
postFrame();
postComposition();
- mFrameTimeline->setSfPresent(systemTime(),
- std::make_shared<FenceTime>(mPreviousPresentFences[0]));
-
const bool prevFrameHadClientComposition = mHadClientComposition;
mHadClientComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
@@ -2109,11 +2113,6 @@
ATRACE_CALL();
ALOGV("postComposition");
- nsecs_t dequeueReadyTime = systemTime();
- for (auto layer : mLayersWithQueuedFrames) {
- layer->releasePendingBuffer(dequeueReadyTime);
- }
-
const auto* display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked()).get();
getBE().mGlCompositionDoneTimeline.updateSignalTimes();
@@ -2135,8 +2134,18 @@
auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFences[0]);
getBE().mDisplayTimeline.push(presentFenceTime);
- DisplayStatInfo stats;
- mScheduler->getDisplayStatInfo(&stats, systemTime());
+ // Set presentation information before calling Layer::releasePendingBuffer, such that jank
+ // information from previous' frame classification is already available when sending jank info
+ // to clients, so they get jank classification as early as possible.
+ mFrameTimeline->setSfPresent(systemTime(),
+ std::make_shared<FenceTime>(mPreviousPresentFences[0]));
+
+ nsecs_t dequeueReadyTime = systemTime();
+ for (const auto& layer : mLayersWithQueuedFrames) {
+ layer->releasePendingBuffer(dequeueReadyTime);
+ }
+
+ 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,
@@ -2383,6 +2392,8 @@
}
void SurfaceFlinger::dispatchDisplayHotplugEvent(PhysicalDisplayId displayId, bool connected) {
+ ALOGI("Dispatching display hotplug event displayId=%s, connected=%d",
+ to_string(displayId).c_str(), connected);
mScheduler->onHotplugReceived(mAppConnectionHandle, displayId, connected);
mScheduler->onHotplugReceived(mSfConnectionHandle, displayId, connected);
}
@@ -2558,7 +2569,8 @@
}
void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
- if (const auto display = getDisplayDeviceLocked(displayToken)) {
+ auto display = getDisplayDeviceLocked(displayToken);
+ if (display) {
display->disconnect();
if (!display->isVirtual()) {
dispatchDisplayHotplugEvent(display->getPhysicalId(), false);
@@ -2566,6 +2578,22 @@
}
mDisplays.erase(displayToken);
+
+ if (display && display->isVirtual()) {
+ static_cast<void>(schedule([display = std::move(display)] {
+ // Destroy the display without holding the mStateLock.
+ // This is a temporary solution until we can manage transaction queues without
+ // holding the mStateLock.
+ // With blast, the IGBP that is passed to the VirtualDisplaySurface is owned by the
+ // client. When the IGBP is disconnected, its buffer cache in SF will be cleared
+ // via SurfaceComposerClient::doUncacheBufferTransaction. This call from the client
+ // ends up running on the main thread causing a deadlock since setTransactionstate
+ // will try to acquire the mStateLock. Instead we extend the lifetime of
+ // DisplayDevice and destroy it in the main thread without holding the mStateLock.
+ // The display will be disconnected and removed from the mDisplays list so it will
+ // not be accessible.
+ }));
+ }
}
void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken,
@@ -2587,6 +2615,20 @@
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);
+ const auto currentConfig =
+ HwcConfigIndexType(getHwComposer().getActiveConfigIndex(displayId));
+ mRefreshRateConfigs->updateDisplayConfigs(configs, currentConfig);
+ mVsyncConfiguration->reset();
+ updatePhaseConfiguration(mRefreshRateConfigs->getCurrentRefreshRate());
+ if (mRefreshRateOverlay) {
+ mRefreshRateOverlay->reset();
+ }
+ }
}
return;
}
@@ -2787,7 +2829,6 @@
});
}
- commitInputWindowCommands();
commitTransaction();
}
@@ -2828,11 +2869,6 @@
: nullptr);
}
-void SurfaceFlinger::commitInputWindowCommands() {
- mInputWindowCommands.merge(mPendingInputWindowCommands);
- mPendingInputWindowCommands.clear();
-}
-
void SurfaceFlinger::updateCursorAsync() {
compositionengine::CompositionRefreshArgs refreshArgs;
for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
@@ -2848,12 +2884,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
@@ -2867,19 +2912,18 @@
std::make_unique<scheduler::RefreshRateConfigs>(getHwComposer().getConfigs(
primaryDisplayId),
currentConfig);
+ const auto& currRefreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig);
mRefreshRateStats =
- std::make_unique<scheduler::RefreshRateStats>(*mRefreshRateConfigs, *mTimeStats,
- currentConfig, hal::PowerMode::OFF);
- mRefreshRateStats->setConfigMode(currentConfig);
+ 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
mScheduler = getFactory().createScheduler(*mRefreshRateConfigs, *this);
const auto configs = mVsyncConfiguration->getCurrentConfigs();
- const nsecs_t vsyncPeriod =
- mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig).getVsyncPeriod();
+ const nsecs_t vsyncPeriod = currRefreshRate.getVsyncPeriod();
mAppConnectionHandle =
mScheduler->createConnection("app", mFrameTimeline->getTokenManager(),
/*workDuration=*/configs.late.appWorkDuration,
@@ -3055,7 +3099,7 @@
// writes to Layer current state. See also b/119481871
Mutex::Autolock lock(mStateLock);
- for (auto& layer : mLayersWithQueuedFrames) {
+ for (const auto& layer : mLayersWithQueuedFrames) {
if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
mLayersPendingRefresh.push_back(layer);
}
@@ -3188,63 +3232,66 @@
mForceTraversal = true;
}
-bool SurfaceFlinger::flushTransactionQueues() {
+void SurfaceFlinger::flushPendingTransactionQueues() {
// to prevent onHandleDestroyed from being called while the lock is held,
// we must keep a copy of the transactions (specifically the composer
// states) around outside the scope of the lock
std::vector<const TransactionState> transactions;
- bool flushedATransaction = false;
{
- Mutex::Autolock _l(mStateLock);
+ Mutex::Autolock _l(mQueueLock);
- auto it = mTransactionQueues.begin();
- while (it != mTransactionQueues.end()) {
+ auto it = mPendingTransactionQueues.begin();
+ while (it != mPendingTransactionQueues.end()) {
auto& [applyToken, transactionQueue] = *it;
while (!transactionQueue.empty()) {
const auto& transaction = transactionQueue.front();
if (!transactionIsReadyToBeApplied(transaction.desiredPresentTime,
transaction.states)) {
- setTransactionFlags(eTransactionFlushNeeded);
break;
}
transactions.push_back(transaction);
- applyTransactionState(transaction.frameTimelineVsyncId, transaction.states,
- transaction.displays, transaction.flags,
- mPendingInputWindowCommands, transaction.desiredPresentTime,
- transaction.buffer, transaction.postTime,
- transaction.privileged, transaction.hasListenerCallbacks,
- transaction.listenerCallbacks, transaction.originPid,
- transaction.originUid, transaction.id, /*isMainThread*/ true);
transactionQueue.pop();
- flushedATransaction = true;
}
if (transactionQueue.empty()) {
- it = mTransactionQueues.erase(it);
- mTransactionCV.broadcast();
+ it = mPendingTransactionQueues.erase(it);
+ mTransactionQueueCV.broadcast();
} else {
it = std::next(it, 1);
}
}
}
- return flushedATransaction;
+
+ {
+ Mutex::Autolock _l(mStateLock);
+ for (const auto& transaction : transactions) {
+ applyTransactionState(transaction.frameTimelineVsyncId, transaction.states,
+ transaction.displays, transaction.flags, mInputWindowCommands,
+ transaction.desiredPresentTime, transaction.isAutoTimestamp,
+ transaction.buffer, transaction.postTime, transaction.privileged,
+ transaction.hasListenerCallbacks, transaction.listenerCallbacks,
+ transaction.originPid, transaction.originUid, transaction.id);
+ }
+ }
}
bool SurfaceFlinger::transactionFlushNeeded() {
- return !mTransactionQueues.empty();
+ Mutex::Autolock _l(mQueueLock);
+ return !mPendingTransactionQueues.empty();
}
-
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) {
@@ -3253,93 +3300,187 @@
continue;
}
if (s.acquireFence && s.acquireFence->getStatus() == Fence::Status::Unsignaled) {
- return false;
+ ready = false;
+ }
+
+ Mutex::Autolock _l(mStateLock);
+ 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();
const int64_t postTime = systemTime();
bool privileged = callingThreadHasUnscopedSurfaceFlingerAccess();
+ {
+ Mutex::Autolock _l(mQueueLock);
- Mutex::Autolock _l(mStateLock);
-
- // If its TransactionQueue already has a pending TransactionState or if it is pending
- auto itr = mTransactionQueues.find(applyToken);
- // if this is an animation frame, wait until prior animation frame has
- // been applied by SF
- if (flags & eAnimation) {
- while (itr != mTransactionQueues.end()) {
- status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
- if (CC_UNLIKELY(err != NO_ERROR)) {
- ALOGW_IF(err == TIMED_OUT,
- "setTransactionState timed out "
- "waiting for animation frame to apply");
- break;
+ // If its TransactionQueue already has a pending TransactionState or if it is pending
+ auto itr = mPendingTransactionQueues.find(applyToken);
+ // if this is an animation frame, wait until prior animation frame has
+ // been applied by SF
+ if (flags & eAnimation) {
+ while (itr != mPendingTransactionQueues.end() ||
+ (!mTransactionQueue.empty() && mAnimTransactionPending)) {
+ status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
+ if (CC_UNLIKELY(err != NO_ERROR)) {
+ ALOGW_IF(err == TIMED_OUT,
+ "setTransactionState timed out "
+ "waiting for animation frame to apply");
+ break;
+ }
+ itr = mPendingTransactionQueues.find(applyToken);
}
- itr = mTransactionQueues.find(applyToken);
}
+
+ const bool pendingTransactions = itr != mPendingTransactionQueues.end();
+ // Expected present time is computed and cached on invalidate, so it may be stale.
+ if (!pendingTransactions) {
+ 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;
+ }
+ }
+
+ // TODO(b/159125966): Remove eEarlyWakeup completly as no client should use this flag
+ if (flags & eEarlyWakeup) {
+ ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
+ }
+
+ if (!privileged && (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
+ ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
+ flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
+ }
+
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int originPid = ipc->getCallingPid();
+ const int originUid = ipc->getCallingUid();
+
+ // Call transactionIsReadyToBeApplied first in case we need to incrementPendingBufferCount
+ // if the transaction contains a buffer.
+ if (!transactionIsReadyToBeApplied(isAutoTimestamp ? 0 : desiredPresentTime, states,
+ true) ||
+ pendingTransactions) {
+ mPendingTransactionQueues[applyToken].emplace(frameTimelineVsyncId, states, displays,
+ flags, inputWindowCommands,
+ desiredPresentTime, isAutoTimestamp,
+ uncacheBuffer, postTime, privileged,
+ hasListenerCallbacks, listenerCallbacks,
+ originPid, originUid, transactionId);
+
+ setTransactionFlags(eTransactionFlushNeeded);
+ return NO_ERROR;
+ }
+
+ mTransactionQueue.emplace_back(frameTimelineVsyncId, states, displays, flags,
+ inputWindowCommands, desiredPresentTime, isAutoTimestamp,
+ uncacheBuffer, postTime, privileged, hasListenerCallbacks,
+ listenerCallbacks, originPid, originUid, transactionId);
}
- 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 schedule = [](uint32_t flags) {
+ if (flags & eEarlyWakeup) return TransactionSchedule::Early;
+ if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
+ if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
+ return TransactionSchedule::Late;
+ }(flags);
+ setTransactionFlags(eTransactionFlushNeeded, schedule);
- IPCThreadState* ipc = IPCThreadState::self();
- const int originPid = ipc->getCallingPid();
- const int originUid = ipc->getCallingUid();
-
- if (pendingTransactions || !transactionIsReadyToBeApplied(desiredPresentTime, states)) {
- mTransactionQueues[applyToken].emplace(frameTimelineVsyncId, states, displays, flags,
- desiredPresentTime, uncacheBuffer, postTime,
- privileged, hasListenerCallbacks, listenerCallbacks,
- originPid, originUid, transactionId);
- setTransactionFlags(eTransactionFlushNeeded);
+ // if this is a synchronous transaction, wait for it to take effect
+ // before returning.
+ const bool synchronous = flags & eSynchronous;
+ const bool syncInput = inputWindowCommands.syncInputWindows;
+ if (!synchronous && !syncInput) {
return NO_ERROR;
}
- applyTransactionState(frameTimelineVsyncId, states, displays, flags, inputWindowCommands,
- desiredPresentTime, uncacheBuffer, postTime, privileged,
- hasListenerCallbacks, listenerCallbacks, originPid, originUid,
- transactionId, /*isMainThread*/ false);
- return NO_ERROR;
-}
+ // Handle synchronous cases.
+ {
+ Mutex::Autolock _l(mStateLock);
+ if (synchronous) {
+ mTransactionPending = true;
+ }
+ if (syncInput) {
+ mPendingSyncInputWindows = true;
+ }
-void SurfaceFlinger::applyTransactionState(
- 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) {
- uint32_t transactionFlags = 0;
-
- if (flags & eAnimation) {
- // For window updates that are part of an animation we must wait for
- // previous animation "frames" to be handled.
- while (!isMainThread && mAnimTransactionPending) {
+ // applyTransactionState can be called by either the main SF thread or by
+ // another process through setTransactionState. While a given process may wish
+ // to wait on synchronous transactions, the main SF thread should never
+ // be blocked. Therefore, we only wait if isMainThread is false.
+ while (mTransactionPending || mPendingSyncInputWindows) {
status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
if (CC_UNLIKELY(err != NO_ERROR)) {
// just in case something goes wrong in SF, return to the
- // caller after a few seconds.
- ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out "
- "waiting for previous animation frame");
- mAnimTransactionPending = false;
+ // called after a few seconds.
+ ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
+ mTransactionPending = false;
+ mPendingSyncInputWindows = false;
break;
}
}
}
+ return NO_ERROR;
+}
+
+void SurfaceFlinger::flushTransactionQueue() {
+ std::vector<TransactionState> transactionQueue;
+ {
+ Mutex::Autolock _l(mQueueLock);
+ if (!mTransactionQueue.empty()) {
+ transactionQueue.swap(mTransactionQueue);
+ }
+ mTransactionQueueCV.broadcast();
+ }
+
+ Mutex::Autolock _l(mStateLock);
+ for (const auto& t : transactionQueue) {
+ applyTransactionState(t.frameTimelineVsyncId, t.states, t.displays, t.flags,
+ t.inputWindowCommands, t.desiredPresentTime, t.isAutoTimestamp,
+ t.buffer, t.postTime, t.privileged, t.hasListenerCallbacks,
+ t.listenerCallbacks, t.originPid, t.originUid, t.id);
+ }
+}
+
+void SurfaceFlinger::applyTransactionState(int64_t frameTimelineVsyncId,
+ const Vector<ComposerState>& states,
+ const Vector<DisplayState>& displays, uint32_t flags,
+ const InputWindowCommands& inputWindowCommands,
+ const int64_t desiredPresentTime, 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) {
+ uint32_t transactionFlags = 0;
for (const DisplayState& display : displays) {
transactionFlags |= setDisplayStateLocked(display);
@@ -3356,12 +3497,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);
}
}
@@ -3397,80 +3539,25 @@
transactionFlags = eTransactionNeeded;
}
- // If we are on the main thread, we are about to preform a traversal. Clear the traversal bit
- // so we don't have to wake up again next frame to preform an uneeded traversal.
- if (isMainThread && (transactionFlags & eTraversalNeeded)) {
- transactionFlags = transactionFlags & (~eTraversalNeeded);
- mForceTraversal = true;
- }
-
- const auto schedule = [](uint32_t flags) {
- if (flags & eEarlyWakeup) return TransactionSchedule::Early;
- if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
- if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
- return TransactionSchedule::Late;
- }(flags);
-
if (transactionFlags) {
if (mInterceptor->isEnabled()) {
mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags,
originPid, originUid, transactionId);
}
- // TODO(b/159125966): Remove eEarlyWakeup completly as no client should use this flag
- if (flags & eEarlyWakeup) {
- ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
+ // We are on the main thread, we are about to preform a traversal. Clear the traversal bit
+ // so we don't have to wake up again next frame to preform an unnecessary traversal.
+ if (transactionFlags & eTraversalNeeded) {
+ transactionFlags = transactionFlags & (~eTraversalNeeded);
+ mForceTraversal = true;
}
-
- if (!privileged && (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
- ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
- flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
+ if (transactionFlags) {
+ setTransactionFlags(transactionFlags);
}
- // this triggers the transaction
- setTransactionFlags(transactionFlags, schedule);
-
if (flags & eAnimation) {
mAnimTransactionPending = true;
}
-
- // if this is a synchronous transaction, wait for it to take effect
- // before returning.
- const bool synchronous = flags & eSynchronous;
- const bool syncInput = inputWindowCommands.syncInputWindows;
- if (!synchronous && !syncInput) {
- return;
- }
-
- if (synchronous) {
- mTransactionPending = true;
- }
- if (syncInput) {
- mPendingSyncInputWindows = true;
- }
-
-
- // applyTransactionState can be called by either the main SF thread or by
- // another process through setTransactionState. While a given process may wish
- // to wait on synchronous transactions, the main SF thread should never
- // be blocked. Therefore, we only wait if isMainThread is false.
- while (!isMainThread && (mTransactionPending || mPendingSyncInputWindows)) {
- status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
- if (CC_UNLIKELY(err != NO_ERROR)) {
- // just in case something goes wrong in SF, return to the
- // called after a few seconds.
- ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
- mTransactionPending = false;
- mPendingSyncInputWindows = false;
- break;
- }
- }
- } else {
- // Update VsyncModulator state machine even if transaction is not needed.
- if (schedule == TransactionSchedule::EarlyStart ||
- schedule == TransactionSchedule::EarlyEnd) {
- modulateVsync(&VsyncModulator::setTransactionSchedule, schedule);
- }
}
}
@@ -3536,7 +3623,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;
@@ -3651,15 +3738,20 @@
// must now be cropped to a non rectangular 8 sided region.
//
// Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
- // private API, and the WindowManager only uses rotation in one case, which is on a top
- // level layer in which cropping is not an issue.
+ // private API, and arbitrary rotation is used in limited use cases, for instance:
+ // - WindowManager only uses rotation in one case, which is on a top level layer in which
+ // cropping is not an issue.
+ // - Launcher, as a privileged app, uses this to transition an application to PiP
+ // (picture-in-picture) mode.
//
// However given that abuse of rotation matrices could lead to surfaces extending outside
- // of cropped areas, we need to prevent non-root clients without permission ACCESS_SURFACE_FLINGER
- // (a.k.a. everyone except WindowManager and tests) from setting non rectangle preserving
- // transformations.
- if (layer->setMatrix(s.matrix, privileged))
- flags |= eTraversalNeeded;
+ // of cropped areas, we need to prevent non-root clients without permission
+ // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
+ // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
+ // preserving transformations.
+ bool allowNonRectPreservingTransforms =
+ privileged || callingThreadHasRotateSurfaceFlingerAccess();
+ if (layer->setMatrix(s.matrix, allowNonRectPreservingTransforms)) flags |= eTraversalNeeded;
}
if (what & layer_state_t::eTransparentRegionChanged) {
if (layer->setTransparentRegionHint(s.transparentRegion))
@@ -3755,7 +3847,9 @@
ALOGE("Attempt to update InputWindowInfo without permission ACCESS_SURFACE_FLINGER");
}
}
+ std::optional<nsecs_t> dequeueBufferTimestamp;
if (what & layer_state_t::eMetadataChanged) {
+ dequeueBufferTimestamp = s.metadata.getInt64(METADATA_DEQUEUE_TIME);
if (layer->setMetadata(s.metadata)) flags |= eTraversalNeeded;
}
if (what & layer_state_t::eColorSpaceAgnosticChanged) {
@@ -3774,7 +3868,7 @@
if (what & layer_state_t::eFrameRateChanged) {
if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility,
"SurfaceFlinger::setClientStateLocked") &&
- layer->setFrameRate(Layer::FrameRate(s.frameRate,
+ layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate),
Layer::FrameRate::convertCompatibility(
s.frameRateCompatibility),
s.shouldBeSeamless))) {
@@ -3842,8 +3936,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, dequeueBufferTimestamp)) {
flags |= eTraversalNeeded;
}
}
@@ -3855,7 +3949,7 @@
}
uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& inputWindowCommands) {
- bool hasChanges = mPendingInputWindowCommands.merge(inputWindowCommands);
+ bool hasChanges = mInputWindowCommands.merge(inputWindowCommands);
return hasChanges ? eTraversalNeeded : 0;
}
@@ -4120,9 +4214,11 @@
d.width = 0;
d.height = 0;
displays.add(d);
- setTransactionState(ISurfaceComposer::INVALID_VSYNC_ID, state, displays, 0, nullptr,
- mPendingInputWindowCommands, -1, {}, false, {},
- 0 /* Undefined transactionId */);
+
+ // This called on the main thread, apply it directly.
+ applyTransactionState(ISurfaceComposer::INVALID_VSYNC_ID, state, displays, 0,
+ mInputWindowCommands, systemTime(), true, {}, systemTime(), true, false,
+ {}, getpid(), getuid(), 0 /* Undefined transactionId */);
setPowerModeInternal(display, hal::PowerMode::ON);
const nsecs_t vsyncPeriod = mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
@@ -4422,12 +4518,9 @@
void SurfaceFlinger::dumpFrameEventsLocked(std::string& result) {
result.append("Layer frame timestamps:\n");
-
- const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
- const size_t count = currentLayers.size();
- for (size_t i=0 ; i<count ; i++) {
- currentLayers[i]->dumpFrameEvents(result);
- }
+ // Traverse all layers to dump frame-events for each layer
+ mCurrentState.traverseInZOrder(
+ [&] (Layer* layer) { layer->dumpFrameEvents(result); });
}
void SurfaceFlinger::dumpBufferingStats(std::string& result) const {
@@ -4707,8 +4800,8 @@
const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
std::string fps, xDpi, yDpi;
if (activeConfig) {
- fps = base::StringPrintf("%.2f Hz",
- 1e9f / getHwComposer().getDisplayVsyncPeriod(*displayId));
+ const auto vsyncPeriod = getHwComposer().getDisplayVsyncPeriod(*displayId);
+ fps = base::StringPrintf("%s", to_string(Fps::fromPeriodNsecs(vsyncPeriod)).c_str());
xDpi = base::StringPrintf("%.2f", activeConfig->getDpiX());
yDpi = base::StringPrintf("%.2f", activeConfig->getDpiY());
} else {
@@ -4874,10 +4967,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
@@ -5226,12 +5320,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;
}
@@ -5285,13 +5384,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;
@@ -5299,7 +5395,7 @@
void SurfaceFlinger::repaintEverything() {
mRepaintEverything = true;
- signalTransaction();
+ setTransactionFlags(eTransactionNeeded);
}
void SurfaceFlinger::repaintEverythingForHWC() {
@@ -5318,16 +5414,16 @@
// mRefreshRateConfigs->getCurrentRefreshRate()
static_cast<void>(schedule([=] {
const auto desiredActiveConfig = getDesiredActiveConfig();
- const auto& current = desiredActiveConfig
- ? mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig->configId)
- : mRefreshRateConfigs->getCurrentRefreshRate();
- const auto& min = mRefreshRateConfigs->getMinRefreshRate();
+ const std::optional<HwcConfigIndexType> desiredConfigId = desiredActiveConfig
+ ? std::make_optional(desiredActiveConfig->configId)
+ : std::nullopt;
- if (current != min) {
- const bool timerExpired = mKernelIdleTimerEnabled && expired;
-
+ const bool timerExpired = mKernelIdleTimerEnabled && expired;
+ const auto newRefreshRate =
+ mRefreshRateConfigs->onKernelTimerChanged(desiredConfigId, timerExpired);
+ if (newRefreshRate) {
if (Mutex::Autolock lock(mStateLock); mRefreshRateOverlay) {
- mRefreshRateOverlay->changeRefreshRate(timerExpired ? min : current);
+ mRefreshRateOverlay->changeRefreshRate(*newRefreshRate);
}
mEventQueue->invalidate();
}
@@ -5483,7 +5579,7 @@
}
}
- RenderAreaFuture renderAreaFuture = promise::defer([=] {
+ RenderAreaFuture renderAreaFuture = ftl::defer([=] {
return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, dataspace,
args.useIdentityTransform, args.captureSecureLayers);
});
@@ -5517,7 +5613,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 */);
@@ -5621,7 +5717,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);
@@ -5795,7 +5891,10 @@
const auto display = renderArea.getDisplayDevice();
std::vector<Layer*> renderedLayers;
Region clearRegion = Region::INVALID_REGION;
+ bool disableBlurs = false;
traverseLayers([&](Layer* layer) {
+ disableBlurs |= layer->getCurrentState().sidebandStream != nullptr;
+
Region clip(renderArea.getBounds());
compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
clip,
@@ -5806,8 +5905,9 @@
clearRegion,
layerStackSpaceRect,
clientCompositionDisplay.outputDataspace,
- true, /* realContentIsVisible */
+ true, /* realContentIsVisible */
false, /* clearContent */
+ disableBlurs,
};
std::vector<compositionengine::LayerFE::LayerSettings> results =
layer->prepareClientCompositionList(targetSettings);
@@ -5822,11 +5922,13 @@
settings.backgroundBlurRadius = 0;
}
}
+
clientCompositionLayers.insert(clientCompositionLayers.end(),
std::make_move_iterator(results.begin()),
std::make_move_iterator(results.end()));
renderedLayers.push_back(layer);
}
+
});
std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers(
@@ -5958,11 +6060,7 @@
}
scheduler::RefreshRateConfigs::Policy currentPolicy = mRefreshRateConfigs->getCurrentPolicy();
- ALOGV("Setting desired display config specs: defaultConfig: %d primaryRange: [%.0f %.0f]"
- " expandedRange: [%.0f %.0f]",
- currentPolicy.defaultConfig.value(), currentPolicy.primaryRange.min,
- currentPolicy.primaryRange.max, currentPolicy.appRequestRange.min,
- currentPolicy.appRequestRange.max);
+ ALOGV("Setting desired display config specs: %s", currentPolicy.toString().c_str());
// TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
// be depending in this callback.
@@ -5975,7 +6073,7 @@
toggleKernelIdleTimer();
auto configId = mScheduler->getPreferredConfigId();
- auto& preferredRefreshRate = configId
+ auto preferredRefreshRate = configId
? mRefreshRateConfigs->getRefreshRateFromConfigId(*configId)
// NOTE: Choose the default config ID, if Scheduler doesn't have one in mind.
: mRefreshRateConfigs->getRefreshRateFromConfigId(currentPolicy.defaultConfig);
@@ -6018,8 +6116,8 @@
using Policy = scheduler::RefreshRateConfigs::Policy;
const Policy policy{HwcConfigIndexType(defaultConfig),
allowGroupSwitching,
- {primaryRefreshRateMin, primaryRefreshRateMax},
- {appRequestRefreshRateMin, appRequestRefreshRateMax}};
+ {Fps(primaryRefreshRateMin), Fps(primaryRefreshRateMax)},
+ {Fps(appRequestRefreshRateMin), Fps(appRequestRefreshRateMax)}};
constexpr bool kOverridePolicy = false;
return setDesiredDisplayConfigSpecsInternal(display, policy, kOverridePolicy);
@@ -6051,10 +6149,10 @@
mRefreshRateConfigs->getDisplayManagerPolicy();
*outDefaultConfig = policy.defaultConfig.value();
*outAllowGroupSwitching = policy.allowGroupSwitching;
- *outPrimaryRefreshRateMin = policy.primaryRange.min;
- *outPrimaryRefreshRateMax = policy.primaryRange.max;
- *outAppRequestRefreshRateMin = policy.appRequestRange.min;
- *outAppRequestRefreshRateMax = policy.appRequestRange.max;
+ *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue();
+ *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue();
+ *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue();
+ *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue();
return NO_ERROR;
} else if (display->isVirtual()) {
return INVALID_OPERATION;
@@ -6063,10 +6161,10 @@
*outDefaultConfig = getHwComposer().getActiveConfigIndex(displayId);
*outAllowGroupSwitching = false;
auto vsyncPeriod = getHwComposer().getActiveConfig(displayId)->getVsyncPeriod();
- *outPrimaryRefreshRateMin = 1e9f / vsyncPeriod;
- *outPrimaryRefreshRateMax = 1e9f / vsyncPeriod;
- *outAppRequestRefreshRateMin = 1e9f / vsyncPeriod;
- *outAppRequestRefreshRateMax = 1e9f / vsyncPeriod;
+ *outPrimaryRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+ *outPrimaryRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+ *outAppRequestRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+ *outAppRequestRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
return NO_ERROR;
}
}
@@ -6162,7 +6260,7 @@
return BAD_VALUE;
}
if (layer->setFrameRate(
- Layer::FrameRate(frameRate,
+ Layer::FrameRate(Fps{frameRate},
Layer::FrameRate::convertCompatibility(compatibility),
shouldBeSeamless))) {
setTransactionFlags(eTraversalNeeded);
@@ -6281,7 +6379,8 @@
mRefreshRateOverlay->setViewport(display->getSize());
}
- mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate());
+ mRefreshRateOverlay->changeRefreshRate(
+ mRefreshRateConfigs->getCurrentRefreshRate().getFps());
}
}));
}
@@ -6297,6 +6396,10 @@
return NO_ERROR;
}
+int SurfaceFlinger::getGPUContextPriority() {
+ return getRenderEngine().getContextPriority();
+}
+
} // namespace android
#if defined(__gl_h_)
@@ -6308,4 +6411,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 a909ad8..bae15dc 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -99,6 +99,8 @@
class TimeStats;
class FrameTracer;
+using gui::ScreenCaptureResults;
+
namespace frametimeline {
class FrameTimeline;
}
@@ -341,7 +343,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,7 +436,8 @@
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,
+ const InputWindowCommands& inputWindowCommands, 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)
@@ -442,7 +445,9 @@
states(composerStates),
displays(displayStates),
flags(transactionFlags),
+ inputWindowCommands(inputWindowCommands),
desiredPresentTime(desiredPresentTime),
+ isAutoTimestamp(isAutoTimestamp),
buffer(uncacheBuffer),
postTime(postTime),
privileged(privileged),
@@ -456,7 +461,9 @@
Vector<ComposerState> states;
Vector<DisplayState> displays;
uint32_t flags;
+ InputWindowCommands inputWindowCommands;
const int64_t desiredPresentTime;
+ const bool isAutoTimestamp;
client_cache_t buffer;
const int64_t postTime;
bool privileged;
@@ -520,8 +527,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 +615,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 +648,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
@@ -716,17 +727,20 @@
/*
* Transactions
*/
+ void flushTransactionQueue();
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,
- int originPid, int originUid, uint64_t transactionId,
- bool isMainThread = false) REQUIRES(mStateLock);
+ int originPid, int originUid, uint64_t transactionId)
+ REQUIRES(mStateLock);
// Returns true if at least one transaction was flushed
bool flushTransactionQueues();
+ // flush pending transaction that was presented after desiredPresentTime.
+ void flushPendingTransactionQueues();
// Returns true if there is at least one transaction that needs to be flushed
bool transactionFlushNeeded();
uint32_t getTransactionFlags(uint32_t flags);
@@ -743,7 +757,8 @@
void commitTransaction() REQUIRES(mStateLock);
void commitOffscreenLayers();
bool transactionIsReadyToBeApplied(int64_t desiredPresentTime,
- const Vector<ComposerState>& states);
+ const Vector<ComposerState>& states,
+ bool updateTransactionCounters = false);
uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
REQUIRES(mStateLock);
@@ -857,26 +872,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 +943,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
@@ -1091,9 +1092,8 @@
bool mAnimCompositionPending = false;
// Tracks layers that have pending frames which are candidates for being
- // latched. Because this contains a set of raw layer pointers, can only be
- // mutated on the main thread.
- std::unordered_set<Layer*> mLayersWithQueuedFrames;
+ // latched.
+ std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> mLayersWithQueuedFrames;
// Tracks layers that need to update a display's dirty region.
std::vector<sp<Layer>> mLayersPendingRefresh;
std::array<sp<Fence>, 2> mPreviousPresentFences = {Fence::NO_FENCE, Fence::NO_FENCE};
@@ -1175,8 +1175,11 @@
uint32_t mTexturePoolSize = 0;
std::vector<uint32_t> mTexturePool;
- std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash> mTransactionQueues;
-
+ mutable Mutex mQueueLock;
+ Condition mTransactionQueueCV;
+ std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
+ mPendingTransactionQueues GUARDED_BY(mQueueLock);
+ std::vector<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
/*
* Feature prototyping
*/
@@ -1253,7 +1256,6 @@
const float mEmulatedDisplayDensity;
sp<os::IInputFlinger> mInputFlinger;
- InputWindowCommands mPendingInputWindowCommands GUARDED_BY(mStateLock);
// Should only be accessed by the main thread.
InputWindowCommands mInputWindowCommands;
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index a93f5f6..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);
}
}
@@ -134,6 +134,12 @@
std::unique_ptr<FrameTracer> DefaultFactory::createFrameTracer() {
return std::make_unique<FrameTracer>();
}
+
+std::unique_ptr<frametimeline::FrameTimeline> DefaultFactory::createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) {
+ return std::make_unique<frametimeline::impl::FrameTimeline>(timeStats, surfaceFlingerPid);
+}
+
} // namespace android::surfaceflinger
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 90032d4..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;
@@ -55,6 +55,8 @@
sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) override;
sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) override;
std::unique_ptr<FrameTracer> createFrameTracer() override;
+ std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+ 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 69e9c3c..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>
@@ -45,6 +47,7 @@
class StartPropertySetThread;
class SurfaceFlinger;
class SurfaceInterceptor;
+class TimeStats;
struct DisplayDeviceCreationArgs;
struct ISchedulerCallback;
@@ -60,6 +63,10 @@
class RefreshRateConfigs;
} // namespace scheduler
+namespace frametimeline {
+class FrameTimeline;
+} // namespace frametimeline
+
namespace surfaceflinger {
class NativeWindowSurface;
@@ -71,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;
@@ -102,6 +109,8 @@
virtual sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) = 0;
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, pid_t surfaceFlingerPid) = 0;
protected:
~Factory() = default;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 97725ec..c043866 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -376,5 +376,9 @@
defaultValue);
}
+bool enable_frame_rate_override(bool defaultValue) {
+ return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue);
+}
+
} // namespace sysprop
} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 37a6b40..816cb09 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -98,6 +98,8 @@
bool update_device_product_info_on_hotplug_reconnect(bool defaultValue);
+bool enable_frame_rate_override(bool defaultValue);
+
} // namespace sysprop
} // namespace android
#endif // SURFACEFLINGERPROPERTIES_H_
diff --git a/services/surfaceflinger/SurfaceTracing.h b/services/surfaceflinger/SurfaceTracing.h
index 576bba7..497ebd3 100644
--- a/services/surfaceflinger/SurfaceTracing.h
+++ b/services/surfaceflinger/SurfaceTracing.h
@@ -85,7 +85,7 @@
std::unique_ptr<Runner> runner;
struct Config {
- uint32_t flags = TRACE_CRITICAL | TRACE_INPUT;
+ uint32_t flags = TRACE_CRITICAL | TRACE_INPUT | TRACE_SYNC;
size_t bufferSize = DEFAULT_BUFFER_SIZE;
} mConfig;
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 28a3a81..f4a0319 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -680,22 +680,21 @@
static void updateJankPayload(T& t, int32_t reasons) {
t.jankPayload.totalFrames++;
- static const constexpr int32_t kValidJankyReason =
- TimeStats::JankType::SurfaceFlingerDeadlineMissed |
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed |
- TimeStats::JankType::AppDeadlineMissed | TimeStats::JankType::Display;
+ static const constexpr int32_t kValidJankyReason = JankType::SurfaceFlingerCpuDeadlineMissed |
+ JankType::SurfaceFlingerGpuDeadlineMissed | JankType::AppDeadlineMissed |
+ JankType::DisplayHAL;
if (reasons & kValidJankyReason) {
t.jankPayload.totalJankyFrames++;
- if ((reasons & TimeStats::JankType::SurfaceFlingerDeadlineMissed) != 0) {
+ if ((reasons & JankType::SurfaceFlingerCpuDeadlineMissed) != 0) {
t.jankPayload.totalSFLongCpu++;
}
- if ((reasons & TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed) != 0) {
+ if ((reasons & JankType::SurfaceFlingerGpuDeadlineMissed) != 0) {
t.jankPayload.totalSFLongGpu++;
}
- if ((reasons & TimeStats::JankType::Display) != 0) {
+ if ((reasons & JankType::DisplayHAL) != 0) {
t.jankPayload.totalSFUnattributed++;
}
- if ((reasons & TimeStats::JankType::AppDeadlineMissed) != 0) {
+ if ((reasons & JankType::AppDeadlineMissed) != 0) {
t.jankPayload.totalAppUnattributed++;
}
}
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 4fa0a02..8fac8e9 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -20,17 +20,19 @@
#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>
#include <statslog.h>
#include <timestatsproto/TimeStatsHelper.h>
#include <timestatsproto/TimeStatsProtoHeader.h>
+#include <gui/JankInfo.h>
#include <ui/FenceTime.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -110,26 +112,6 @@
virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
const std::shared_ptr<FenceTime>& presentFence) = 0;
- // Subset of jank metadata tracked by FrameTimeline for the purpose of funneling to telemetry.
- enum JankType {
- // No Jank
- None = 0x0,
- // Jank not related to SurfaceFlinger or the App
- Display = 0x1,
- // SF took too long on the CPU
- SurfaceFlingerDeadlineMissed = 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,
- };
-
// Increments janky frames, tracked globally. Because FrameTimeline is the infrastructure
// responsible for computing jank in the system, this is expected to be called from
// FrameTimeline, rather than directly from SurfaceFlinger or individual layers. If there are no
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/TransactionCompletedThread.cpp b/services/surfaceflinger/TransactionCompletedThread.cpp
index ca24493..1797af4 100644
--- a/services/surfaceflinger/TransactionCompletedThread.cpp
+++ b/services/surfaceflinger/TransactionCompletedThread.cpp
@@ -153,7 +153,7 @@
}
status_t TransactionCompletedThread::finalizePendingCallbackHandles(
- const std::deque<sp<CallbackHandle>>& handles) {
+ const std::deque<sp<CallbackHandle>>& handles, const std::vector<JankData>& jankData) {
if (handles.empty()) {
return NO_ERROR;
}
@@ -186,7 +186,7 @@
ALOGW("cannot find listener in mPendingTransactions");
}
- status_t err = addCallbackHandle(handle);
+ status_t err = addCallbackHandle(handle, jankData);
if (err != NO_ERROR) {
ALOGE("could not add callback handle");
return err;
@@ -204,7 +204,7 @@
return BAD_VALUE;
}
- return addCallbackHandle(handle);
+ return addCallbackHandle(handle, std::vector<JankData>());
}
status_t TransactionCompletedThread::findTransactionStats(
@@ -225,7 +225,8 @@
return BAD_VALUE;
}
-status_t TransactionCompletedThread::addCallbackHandle(const sp<CallbackHandle>& handle) {
+status_t TransactionCompletedThread::addCallbackHandle(const sp<CallbackHandle>& handle,
+ const std::vector<JankData>& jankData) {
// If we can't find the transaction stats something has gone wrong. The client should call
// startRegistration before trying to add a callback handle.
TransactionStats* transactionStats;
@@ -246,7 +247,7 @@
handle->dequeueReadyTime);
transactionStats->surfaceStats.emplace_back(surfaceControl, handle->acquireTime,
handle->previousReleaseFence,
- handle->transformHint, eventStats);
+ handle->transformHint, eventStats, jankData);
}
return NO_ERROR;
}
diff --git a/services/surfaceflinger/TransactionCompletedThread.h b/services/surfaceflinger/TransactionCompletedThread.h
index f50147a..c4ba7e4 100644
--- a/services/surfaceflinger/TransactionCompletedThread.h
+++ b/services/surfaceflinger/TransactionCompletedThread.h
@@ -73,7 +73,8 @@
// presented.
status_t registerPendingCallbackHandle(const sp<CallbackHandle>& handle);
// Notifies the TransactionCompletedThread that a pending CallbackHandle has been presented.
- status_t finalizePendingCallbackHandles(const std::deque<sp<CallbackHandle>>& handles);
+ status_t finalizePendingCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
+ const std::vector<JankData>& jankData);
// Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
// presented this frame.
@@ -93,7 +94,8 @@
const std::vector<CallbackId>& callbackIds,
TransactionStats** outTransactionStats) REQUIRES(mMutex);
- status_t addCallbackHandle(const sp<CallbackHandle>& handle) REQUIRES(mMutex);
+ status_t addCallbackHandle(const sp<CallbackHandle>& handle,
+ const std::vector<JankData>& jankData) REQUIRES(mMutex);
class ThreadDeathRecipient : public IBinder::DeathRecipient {
public:
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 421484f..4d25a7a 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -445,3 +445,12 @@
access: Readonly
prop_name: "ro.surface_flinger.update_device_product_info_on_hotplug_reconnect"
}
+
+# Enables the frame rate override feature
+prop {
+ api_name: "enable_frame_rate_override"
+ type: Boolean
+ scope: Public
+ access: Readonly
+ prop_name: "ro.surface_flinger.enable_frame_rate_override"
+}
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index da66ece..0e0be09 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -41,6 +41,10 @@
prop_name: "ro.surface_flinger.display_update_imminent_timeout_ms"
}
prop {
+ api_name: "enable_frame_rate_override"
+ prop_name: "ro.surface_flinger.enable_frame_rate_override"
+ }
+ prop {
api_name: "enable_protected_contents"
prop_name: "ro.surface_flinger.protected_contents"
}
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/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp
index e66df4a..ecfed13 100644
--- a/services/surfaceflinger/tests/LayerState_test.cpp
+++ b/services/surfaceflinger/tests/LayerState_test.cpp
@@ -22,6 +22,8 @@
#include <gui/LayerState.h>
namespace android {
+using gui::ScreenCaptureResults;
+
namespace test {
TEST(LayerStateTest, ParcellingDisplayCaptureArgs) {
@@ -86,11 +88,11 @@
results.result = BAD_VALUE;
Parcel p;
- results.write(p);
+ results.writeToParcel(&p);
p.setDataPosition(0);
ScreenCaptureResults results2;
- results2.read(p);
+ results2.readFromParcel(&p);
// GraphicBuffer object is reallocated so compare the data in the graphic buffer
// rather than the object itself
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/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index a361b1e..3ef63d5 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -68,6 +68,9 @@
Rect(displayState.layerStackSpaceRect), Rect(resolution));
t.apply();
SurfaceComposerClient::Transaction().apply(true);
+ // wait for 3 vsyncs to ensure the buffer is latched.
+ usleep(static_cast<int32_t>(1e6 / displayConfig.refreshRate) * 3);
+
BufferItem item;
itemConsumer->acquireBuffer(&item, 0, true);
auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer);
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index 9fd2227..18e0806 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -52,6 +52,8 @@
virtualDisplay.clear();
// Sync here to ensure the display was completely destroyed in SF
t.apply(true);
+ // add another sync since we are deferring the display destruction
+ t.apply(true);
sp<Surface> surface = new Surface(mProducer);
sp<ANativeWindow> window(surface);
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 31a5126..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>
@@ -69,6 +71,7 @@
using Transaction = SurfaceComposerClient::Transaction;
using Attribute = V2_4::IComposerClient::Attribute;
+using Display = V2_1::Display;
///////////////////////////////////////////////
@@ -869,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();
}
@@ -1996,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 7427e27..13c7c8b 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -44,13 +44,13 @@
"DisplayDevice_GetBestColorModeTest.cpp",
"DisplayDevice_SetProjectionTest.cpp",
"EventThreadTest.cpp",
+ "FpsTest.cpp",
"FrameTimelineTest.cpp",
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
"LayerMetadataTest.cpp",
"MessageQueueTest.cpp",
- "PromiseTest.cpp",
"SurfaceFlinger_CreateDisplayTest.cpp",
"SurfaceFlinger_DestroyDisplayTest.cpp",
"SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
@@ -83,6 +83,7 @@
"mock/DisplayHardware/MockDisplay.cpp",
"mock/DisplayHardware/MockPowerAdvisor.cpp",
"mock/MockEventThread.cpp",
+ "mock/MockFrameTimeline.cpp",
"mock/MockFrameTracer.cpp",
"mock/MockMessageQueue.cpp",
"mock/MockNativeWindowSurface.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 4cd1e0a..e890a62 100644
--- a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
+++ b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
@@ -26,7 +26,7 @@
static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
- VsyncConfigSet getConfigsForRefreshRate(float) const override { return getCurrentConfigs(); }
+ VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
VsyncConfigSet getCurrentConfigs() const override {
return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
@@ -37,7 +37,8 @@
FAKE_DURATION_OFFSET_NS}};
}
- void setRefreshRateFps(float) override {}
+ void reset() override {}
+ void setRefreshRateFps(Fps) override {}
void dump(std::string&) const override {}
};
diff --git a/services/surfaceflinger/tests/unittests/FpsTest.cpp b/services/surfaceflinger/tests/unittests/FpsTest.cpp
new file mode 100644
index 0000000..db732cf
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FpsTest.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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 "Fps.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+TEST(FpsTest, construct) {
+ Fps fpsDefault;
+ EXPECT_FALSE(fpsDefault.isValid());
+
+ Fps fps1(60.0f);
+ EXPECT_TRUE(fps1.isValid());
+ Fps fps2 = Fps::fromPeriodNsecs(static_cast<nsecs_t>(1e9f / 60.0f));
+ EXPECT_TRUE(fps2.isValid());
+ EXPECT_TRUE(fps1.equalsWithMargin(fps2));
+}
+
+TEST(FpsTest, compare) {
+ constexpr float kEpsilon = 1e-4f;
+ const Fps::EqualsInBuckets equalsInBuckets;
+ const Fps::EqualsWithMargin equalsWithMargin;
+
+ EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f)));
+ EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f - kEpsilon)));
+ EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f + kEpsilon)));
+
+ EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f)));
+ EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f - kEpsilon)));
+ EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+ EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f)));
+ EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f - kEpsilon)));
+ EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+ EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+ EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f)));
+ EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+ EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+ EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f)));
+ EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+ // Fps with difference of 1 should be different
+ EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(61.f)));
+ EXPECT_TRUE(Fps(60.0f).lessThanWithMargin(Fps(61.f)));
+ EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.f)));
+
+ // These are common refresh rates which should be different.
+ EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(59.94f)));
+ EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.94f)));
+ EXPECT_FALSE(equalsInBuckets(Fps(60.0f), Fps(59.94f)));
+ EXPECT_FALSE(equalsWithMargin(Fps(60.0f), Fps(59.94f)));
+ EXPECT_NE(std::hash<Fps>()(Fps(60.0f)), std::hash<Fps>()(Fps(59.94f)));
+
+ EXPECT_FALSE(Fps(30.0f).equalsWithMargin(Fps(29.97f)));
+ EXPECT_TRUE(Fps(30.0f).greaterThanWithMargin(Fps(29.97f)));
+ EXPECT_FALSE(equalsInBuckets(Fps(30.0f), Fps(29.97f)));
+ EXPECT_FALSE(equalsWithMargin(Fps(30.0f), Fps(29.97f)));
+ EXPECT_NE(std::hash<Fps>()(Fps(30.0f)), std::hash<Fps>()(Fps(29.97f)));
+}
+
+TEST(FpsTest, getIntValue) {
+ EXPECT_EQ(30, Fps(30.1f).getIntValue());
+ EXPECT_EQ(31, Fps(30.9f).getIntValue());
+ EXPECT_EQ(31, Fps(30.5f).getIntValue());
+}
+
+TEST(FpsTest, equalsInBucketsImpliesEqualHashes) {
+ constexpr float kStep = 1e-4f;
+ const Fps::EqualsInBuckets equals;
+ for (float fps = 30.0f; fps < 31.0f; fps += kStep) {
+ const Fps left(fps);
+ const Fps right(fps + kStep);
+ if (equals(left, right)) {
+ ASSERT_EQ(std::hash<Fps>()(left), std::hash<Fps>()(right))
+ << "left= " << left << " right=" << right;
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 03c6f70..e2584e2 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
@@ -22,10 +26,22 @@
#include <FrameTimeline/FrameTimeline.h>
#include <gtest/gtest.h>
#include <log/log.h>
+#include <perfetto/trace/trace.pb.h>
#include <cinttypes>
using namespace std::chrono_literals;
+using testing::AtLeast;
using testing::Contains;
+using FrameTimelineEvent = perfetto::protos::FrameTimelineEvent;
+using ProtoExpectedDisplayFrameStart =
+ perfetto::protos::FrameTimelineEvent_ExpectedDisplayFrameStart;
+using ProtoExpectedSurfaceFrameStart =
+ perfetto::protos::FrameTimelineEvent_ExpectedSurfaceFrameStart;
+using ProtoActualDisplayFrameStart = perfetto::protos::FrameTimelineEvent_ActualDisplayFrameStart;
+using ProtoActualSurfaceFrameStart = perfetto::protos::FrameTimelineEvent_ActualSurfaceFrameStart;
+using ProtoFrameEnd = perfetto::protos::FrameTimelineEvent_FrameEnd;
+using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
+using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
MATCHER_P(HasBit, bit, "") {
return (arg & bit) != 0;
@@ -47,14 +63,58 @@
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
+ static void SetUpTestSuite() {
+ // Need to initialize tracing in process for testing, and only once per test suite.
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kInProcessBackend;
+ perfetto::Tracing::Initialize(args);
+ }
+
void SetUp() override {
mTimeStats = std::make_shared<mock::TimeStats>();
- mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats);
+ mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
+ kTestThresholds);
+ mFrameTimeline->registerDataSource();
mTokenManager = &mFrameTimeline->mTokenManager;
+ mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
maxTokenRetentionTime = mTokenManager->kMaxRetentionTime;
}
+ // Each tracing session can be used for a single block of Start -> Stop.
+ static std::unique_ptr<perfetto::TracingSession> 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(impl::FrameTimeline::kFrameTimelineDataSource);
+
+ auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+ tracingSession->Setup(cfg);
+ return tracingSession;
+ }
+
+ std::vector<perfetto::protos::TracePacket> readFrameTimelinePacketsBlocking(
+ perfetto::TracingSession* tracingSession) {
+ std::vector<char> raw_trace = tracingSession->ReadTraceBlocking();
+ perfetto::protos::Trace trace;
+ EXPECT_TRUE(trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())));
+
+ std::vector<perfetto::protos::TracePacket> packets;
+ for (const auto& packet : trace.packet()) {
+ if (!packet.has_frame_timeline_event()) {
+ continue;
+ }
+ packets.emplace_back(packet);
+ }
+ return packets;
+ }
+
+ void addEmptyDisplayFrame() {
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ mFrameTimeline->setSfPresent(2500, presentFence1);
+ }
+
void flushTokens(nsecs_t flushTime) {
std::lock_guard<std::mutex> lock(mTokenManager->mMutex);
mTokenManager->flushTokens(flushTime);
@@ -62,7 +122,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) {
@@ -75,21 +136,40 @@
a.presentTime == b.presentTime;
}
- const std::unordered_map<int64_t, TimelineItem>& getPredictions() {
+ const std::map<int64_t, TokenManagerPrediction>& getPredictions() const {
return mTokenManager->mPredictions;
}
- uint32_t getNumberOfDisplayFrames() {
+ uint32_t getNumberOfDisplayFrames() const {
std::lock_guard<std::mutex> lock(mFrameTimeline->mMutex);
return static_cast<uint32_t>(mFrameTimeline->mDisplayFrames.size());
}
+ int64_t snoopCurrentTraceCookie() const { return mTraceCookieCounter->mTraceCookie; }
+
+ void flushTrace() {
+ using FrameTimelineDataSource = impl::FrameTimeline::FrameTimelineDataSource;
+ FrameTimelineDataSource::Trace(
+ [&](FrameTimelineDataSource::TraceContext ctx) { ctx.Flush(); });
+ }
+
std::shared_ptr<mock::TimeStats> mTimeStats;
std::unique_ptr<impl::FrameTimeline> mFrameTimeline;
impl::TokenManager* mTokenManager;
+ TraceCookieCounter* mTraceCookieCounter;
FenceToFenceTimeMap fenceFactory;
uint32_t* maxDisplayFrames;
nsecs_t maxTokenRetentionTime;
+ static constexpr pid_t kSurfaceFlingerPid = 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";
@@ -114,55 +194,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(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 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);
@@ -171,52 +254,65 @@
}
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, surfaceFrameToken1);
- mFrameTimeline->setSfWakeUp(sfToken1, 22);
- mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
- SurfaceFrame::PresentState::Presented);
- mFrameTimeline->addSurfaceFrame(std::move(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);
// 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(std::move(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);
+ EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
}
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(
@@ -224,11 +320,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(std::move(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;
@@ -236,8 +332,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);
@@ -246,51 +341,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(std::move(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(std::move(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);
@@ -298,15 +396,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(std::move(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);
@@ -314,26 +415,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(std::move(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(TimeStats::JankType::SurfaceFlingerDeadlineMissed)));
+ HasBit(JankType::SurfaceFlingerCpuDeadlineMissed)));
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(HasBit(TimeStats::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(),
@@ -344,14 +449,15 @@
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(std::move(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>(90ms).count());
+ std::chrono::duration_cast<std::chrono::nanoseconds>(70ms).count());
mFrameTimeline->setSfPresent(std::chrono::duration_cast<std::chrono::nanoseconds>(59ms).count(),
presentFence1);
@@ -359,8 +465,9 @@
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(sUidOne, sLayerNameOne, HasBit(TimeStats::JankType::Display)));
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(TimeStats::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(),
@@ -371,46 +478,984 @@
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(std::move(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_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(sUidOne, sLayerNameOne,
- HasBit(TimeStats::JankType::AppDeadlineMissed)));
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(TimeStats::JankType::AppDeadlineMissed)));
+ HasBit(JankType::AppDeadlineMissed)));
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(JankType::AppDeadlineMissed)));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
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(std::move(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_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
}
+/*
+ * Tracing Tests
+ *
+ * Trace packets are flushed all the way only when the next packet is traced.
+ * For example: trace<Display/Surface>Frame() will create a TracePacket but not flush it. Only when
+ * another TracePacket is created, the previous one is guaranteed to be flushed. The following tests
+ * will have additional empty frames created for this reason.
+ */
+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(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+
+ // Set up the display frame
+ 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, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, tracing_sanityTest) {
+ auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ // 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(token1, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+
+ // Set up the display frame
+ 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, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has 8 packets - 4 from DisplayFrame and 4 from SurfaceFrame.
+ EXPECT_EQ(packets.size(), 8);
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_invalidTokenDoesNotEmitTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(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});
+
+ // Set up the display frame
+ 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, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(60);
+
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_invalidTokenDoesNotEmitTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(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(std::nullopt, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+
+ // Set up the display frame
+ 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, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(60);
+
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has 4 packets (SurfaceFrame shouldn't be traced since it has an invalid
+ // token).
+ EXPECT_EQ(packets.size(), 4);
+}
+
+void validateTraceEvent(const ProtoExpectedDisplayFrameStart& received,
+ const ProtoExpectedDisplayFrameStart& source) {
+ ASSERT_TRUE(received.has_cookie());
+ EXPECT_EQ(received.cookie(), source.cookie());
+
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_pid());
+ EXPECT_EQ(received.pid(), source.pid());
+}
+
+void validateTraceEvent(const ProtoActualDisplayFrameStart& received,
+ const ProtoActualDisplayFrameStart& source) {
+ ASSERT_TRUE(received.has_cookie());
+ EXPECT_EQ(received.cookie(), source.cookie());
+
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_pid());
+ EXPECT_EQ(received.pid(), source.pid());
+
+ ASSERT_TRUE(received.has_present_type());
+ EXPECT_EQ(received.present_type(), source.present_type());
+ ASSERT_TRUE(received.has_on_time_finish());
+ EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+ ASSERT_TRUE(received.has_gpu_composition());
+ EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
+ ASSERT_TRUE(received.has_jank_type());
+ EXPECT_EQ(received.jank_type(), source.jank_type());
+}
+
+void validateTraceEvent(const ProtoExpectedSurfaceFrameStart& received,
+ const ProtoExpectedSurfaceFrameStart& source) {
+ ASSERT_TRUE(received.has_cookie());
+ EXPECT_EQ(received.cookie(), source.cookie());
+
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_display_frame_token());
+ EXPECT_EQ(received.display_frame_token(), source.display_frame_token());
+
+ ASSERT_TRUE(received.has_pid());
+ EXPECT_EQ(received.pid(), source.pid());
+
+ ASSERT_TRUE(received.has_layer_name());
+ EXPECT_EQ(received.layer_name(), source.layer_name());
+}
+
+void validateTraceEvent(const ProtoActualSurfaceFrameStart& received,
+ const ProtoActualSurfaceFrameStart& source) {
+ ASSERT_TRUE(received.has_cookie());
+ EXPECT_EQ(received.cookie(), source.cookie());
+
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_display_frame_token());
+ EXPECT_EQ(received.display_frame_token(), source.display_frame_token());
+
+ ASSERT_TRUE(received.has_pid());
+ EXPECT_EQ(received.pid(), source.pid());
+
+ ASSERT_TRUE(received.has_layer_name());
+ EXPECT_EQ(received.layer_name(), source.layer_name());
+
+ ASSERT_TRUE(received.has_present_type());
+ EXPECT_EQ(received.present_type(), source.present_type());
+ ASSERT_TRUE(received.has_on_time_finish());
+ EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+ ASSERT_TRUE(received.has_gpu_composition());
+ EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
+ ASSERT_TRUE(received.has_jank_type());
+ EXPECT_EQ(received.jank_type(), source.jank_type());
+}
+
+void validateTraceEvent(const ProtoFrameEnd& received, const ProtoFrameEnd& source) {
+ ASSERT_TRUE(received.has_cookie());
+ EXPECT_EQ(received.cookie(), source.cookie());
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
+ int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, 11);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ presentFence1->signalForTest(31);
+
+ int64_t traceCookie = snoopCurrentTraceCookie();
+ ProtoExpectedDisplayFrameStart protoExpectedDisplayFrameStart;
+ protoExpectedDisplayFrameStart.set_cookie(traceCookie + 1);
+ protoExpectedDisplayFrameStart.set_token(displayFrameToken1);
+ protoExpectedDisplayFrameStart.set_pid(kSurfaceFlingerPid);
+
+ ProtoFrameEnd protoExpectedDisplayFrameEnd;
+ protoExpectedDisplayFrameEnd.set_cookie(traceCookie + 1);
+
+ ProtoActualDisplayFrameStart protoActualDisplayFrameStart;
+ protoActualDisplayFrameStart.set_cookie(traceCookie + 2);
+ protoActualDisplayFrameStart.set_token(displayFrameToken1);
+ protoActualDisplayFrameStart.set_pid(kSurfaceFlingerPid);
+ protoActualDisplayFrameStart.set_present_type(
+ ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+ protoActualDisplayFrameStart.set_on_time_finish(true);
+ protoActualDisplayFrameStart.set_gpu_composition(false);
+ protoActualDisplayFrameStart.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+
+ ProtoFrameEnd protoActualDisplayFrameEnd;
+ protoActualDisplayFrameEnd.set_cookie(traceCookie + 2);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 4);
+
+ // Packet - 0 : ExpectedDisplayFrameStart
+ const auto& packet0 = packets[0];
+ ASSERT_TRUE(packet0.has_timestamp());
+ EXPECT_EQ(packet0.timestamp(), 10);
+ ASSERT_TRUE(packet0.has_frame_timeline_event());
+
+ const auto& event0 = packet0.frame_timeline_event();
+ ASSERT_TRUE(event0.has_expected_display_frame_start());
+ const auto& expectedDisplayFrameStart = event0.expected_display_frame_start();
+ validateTraceEvent(expectedDisplayFrameStart, protoExpectedDisplayFrameStart);
+
+ // Packet - 1 : FrameEnd (ExpectedDisplayFrame)
+ const auto& packet1 = packets[1];
+ ASSERT_TRUE(packet1.has_timestamp());
+ EXPECT_EQ(packet1.timestamp(), 25);
+ ASSERT_TRUE(packet1.has_frame_timeline_event());
+
+ const auto& event1 = packet1.frame_timeline_event();
+ ASSERT_TRUE(event1.has_frame_end());
+ const auto& expectedDisplayFrameEnd = event1.frame_end();
+ validateTraceEvent(expectedDisplayFrameEnd, protoExpectedDisplayFrameEnd);
+
+ // Packet - 2 : ActualDisplayFrameStart
+ const auto& packet2 = packets[2];
+ ASSERT_TRUE(packet2.has_timestamp());
+ EXPECT_EQ(packet2.timestamp(), 20);
+ ASSERT_TRUE(packet2.has_frame_timeline_event());
+
+ const auto& event2 = packet2.frame_timeline_event();
+ ASSERT_TRUE(event2.has_actual_display_frame_start());
+ const auto& actualDisplayFrameStart = event2.actual_display_frame_start();
+ validateTraceEvent(actualDisplayFrameStart, protoActualDisplayFrameStart);
+
+ // Packet - 3 : FrameEnd (ActualDisplayFrame)
+ const auto& packet3 = packets[3];
+ ASSERT_TRUE(packet3.has_timestamp());
+ EXPECT_EQ(packet3.timestamp(), 26);
+ ASSERT_TRUE(packet3.has_frame_timeline_event());
+
+ const auto& event3 = packet3.frame_timeline_event();
+ ASSERT_TRUE(event3.has_frame_end());
+ const auto& actualDisplayFrameEnd = event3.frame_end();
+ validateTraceEvent(actualDisplayFrameEnd, protoActualDisplayFrameEnd);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ // Global increment
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(testing::_));
+ // 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 surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 25, 40});
+ int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({30, 35, 40});
+ int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(surfaceFrameToken, sPidOne, sUidOne,
+ sLayerNameOne, sLayerNameOne);
+ surfaceFrame1->setActualStartTime(0);
+ surfaceFrame1->setActualQueueTime(15);
+ surfaceFrame1->setAcquireFenceTime(20);
+
+ // First 2 cookies will be used by the DisplayFrame
+ int64_t traceCookie = snoopCurrentTraceCookie() + 2;
+
+ ProtoExpectedSurfaceFrameStart protoExpectedSurfaceFrameStart;
+ protoExpectedSurfaceFrameStart.set_cookie(traceCookie + 1);
+ protoExpectedSurfaceFrameStart.set_token(surfaceFrameToken);
+ protoExpectedSurfaceFrameStart.set_display_frame_token(displayFrameToken1);
+ protoExpectedSurfaceFrameStart.set_pid(sPidOne);
+ protoExpectedSurfaceFrameStart.set_layer_name(sLayerNameOne);
+
+ ProtoFrameEnd protoExpectedSurfaceFrameEnd;
+ protoExpectedSurfaceFrameEnd.set_cookie(traceCookie + 1);
+
+ ProtoActualSurfaceFrameStart protoActualSurfaceFrameStart;
+ protoActualSurfaceFrameStart.set_cookie(traceCookie + 2);
+ protoActualSurfaceFrameStart.set_token(surfaceFrameToken);
+ protoActualSurfaceFrameStart.set_display_frame_token(displayFrameToken1);
+ protoActualSurfaceFrameStart.set_pid(sPidOne);
+ protoActualSurfaceFrameStart.set_layer_name(sLayerNameOne);
+ protoActualSurfaceFrameStart.set_present_type(
+ ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+ protoActualSurfaceFrameStart.set_on_time_finish(true);
+ protoActualSurfaceFrameStart.set_gpu_composition(false);
+ protoActualSurfaceFrameStart.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+
+ ProtoFrameEnd protoActualSurfaceFrameEnd;
+ protoActualSurfaceFrameEnd.set_cookie(traceCookie + 2);
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, 11);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ presentFence1->signalForTest(40);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50, 11);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 8);
+
+ // Packet - 4 : ExpectedSurfaceFrameStart
+ const auto& packet4 = packets[4];
+ ASSERT_TRUE(packet4.has_timestamp());
+ EXPECT_EQ(packet4.timestamp(), 10);
+ ASSERT_TRUE(packet4.has_frame_timeline_event());
+
+ const auto& event4 = packet4.frame_timeline_event();
+ ASSERT_TRUE(event4.has_expected_surface_frame_start());
+ const auto& expectedSurfaceFrameStart = event4.expected_surface_frame_start();
+ validateTraceEvent(expectedSurfaceFrameStart, protoExpectedSurfaceFrameStart);
+
+ // Packet - 5 : FrameEnd (ExpectedSurfaceFrame)
+ const auto& packet5 = packets[5];
+ ASSERT_TRUE(packet5.has_timestamp());
+ EXPECT_EQ(packet5.timestamp(), 25);
+ ASSERT_TRUE(packet5.has_frame_timeline_event());
+
+ const auto& event5 = packet5.frame_timeline_event();
+ ASSERT_TRUE(event5.has_frame_end());
+ const auto& expectedSurfaceFrameEnd = event5.frame_end();
+ validateTraceEvent(expectedSurfaceFrameEnd, protoExpectedSurfaceFrameEnd);
+
+ // Packet - 6 : ActualSurfaceFrameStart
+ const auto& packet6 = packets[6];
+ ASSERT_TRUE(packet6.has_timestamp());
+ EXPECT_EQ(packet6.timestamp(), 10);
+ ASSERT_TRUE(packet6.has_frame_timeline_event());
+
+ const auto& event6 = packet6.frame_timeline_event();
+ ASSERT_TRUE(event6.has_actual_surface_frame_start());
+ const auto& actualSurfaceFrameStart = event6.actual_surface_frame_start();
+ validateTraceEvent(actualSurfaceFrameStart, protoActualSurfaceFrameStart);
+
+ // Packet - 7 : FrameEnd (ActualSurfaceFrame)
+ const auto& packet7 = packets[7];
+ ASSERT_TRUE(packet7.has_timestamp());
+ EXPECT_EQ(packet7.timestamp(), 20);
+ ASSERT_TRUE(packet7.has_frame_timeline_event());
+
+ const auto& event7 = packet7.frame_timeline_event();
+ ASSERT_TRUE(event7.has_frame_end());
+ const auto& actualSurfaceFrameEnd = event7.frame_end();
+ validateTraceEvent(actualSurfaceFrameEnd, protoActualSurfaceFrameEnd);
+}
+
+// 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 fbb4637..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"
@@ -32,7 +36,9 @@
using testing::_;
using testing::Return;
-namespace android::scheduler {
+namespace android {
+
+namespace scheduler {
class LayerHistoryTest : public testing::Test {
protected:
@@ -43,11 +49,11 @@
static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
LayerInfo::RefreshRateHistory::HISTORY_DURATION;
- static constexpr float LO_FPS = 30.f;
- static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
+ static constexpr Fps LO_FPS{30.f};
+ static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
- static constexpr float HI_FPS = 90.f;
- static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
+ static constexpr Fps HI_FPS{90.f};
+ static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
@@ -88,20 +94,19 @@
return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
}
- void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
- float desiredRefreshRate, int numFrames) {
- const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+ void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, Fps frameRate,
+ Fps desiredRefreshRate, int numFrames) {
LayerHistory::Summary summary;
for (int i = 0; i < numFrames; i++) {
history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += framePeriod;
+ time += frameRate.getPeriodNsecs();
summary = history().summarize(time);
}
ASSERT_EQ(1, summary.size());
ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+ ASSERT_TRUE(desiredRefreshRate.equalsWithMargin(summary[0].desiredRefreshRate))
<< "Frame rate is " << frameRate;
}
@@ -196,7 +201,7 @@
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
}
@@ -289,7 +294,7 @@
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree())
.WillRepeatedly(
- Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+ Return(Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::Default)));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
@@ -302,7 +307,7 @@
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -311,7 +316,7 @@
time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
}
@@ -321,7 +326,7 @@
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree())
.WillRepeatedly(Return(
- Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+ Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::ExactOrMultiple)));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
@@ -335,7 +340,7 @@
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -345,7 +350,7 @@
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
}
@@ -397,7 +402,8 @@
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ EXPECT_TRUE(HI_FPS.equalsWithMargin(history().summarize(time)[1].desiredRefreshRate));
+
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -411,7 +417,7 @@
ASSERT_EQ(1, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -430,7 +436,7 @@
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -440,9 +446,9 @@
summary = history().summarize(time);
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+ EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -452,9 +458,9 @@
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+ EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
EXPECT_EQ(2, layerCount());
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -469,7 +475,7 @@
ASSERT_EQ(1, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -490,7 +496,7 @@
ASSERT_EQ(1, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
+ EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -567,12 +573,12 @@
EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
.WillRepeatedly(Return(
- Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+ Layer::FrameRate(Fps(60.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
.WillRepeatedly(Return(
- Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+ Layer::FrameRate(Fps(90.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
nsecs_t time = systemTime();
@@ -585,7 +591,7 @@
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_TRUE(Fps(60.0f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
}
@@ -643,7 +649,7 @@
nsecs_t time = systemTime();
for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
- recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(fps), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
}
}
@@ -653,13 +659,13 @@
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
nsecs_t time = systemTime();
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(30.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(30.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(60.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
}
TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
@@ -669,11 +675,11 @@
nsecs_t time = systemTime();
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(26.90f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(26.00f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(26.90f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
}
class LayerHistoryTestParameterized : public LayerHistoryTest,
@@ -724,7 +730,7 @@
bool max = false;
bool min = false;
- float heuristic = 0;
+ Fps heuristic{0.0};
for (const auto& layer : history().summarize(time)) {
if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
heuristic = layer.desiredRefreshRate;
@@ -736,7 +742,7 @@
}
if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
- EXPECT_FLOAT_EQ(24.0f, heuristic);
+ EXPECT_TRUE(Fps(24.0f).equalsWithMargin(heuristic));
EXPECT_FALSE(max);
if (history().summarize(time).size() == 2) {
EXPECT_TRUE(min);
@@ -750,4 +756,8 @@
::testing::Values(1s, 2s, 3s, 4s, 5s));
} // namespace
-} // namespace android::scheduler
+} // 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 f2b7191..0813968 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"
@@ -30,6 +34,7 @@
using testing::_;
namespace android {
+
namespace scheduler {
namespace hal = android::hardware::graphics::composer::hal;
@@ -43,14 +48,29 @@
RefreshRateConfigsTest();
~RefreshRateConfigsTest();
- float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+ Fps findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, Fps frameRate) {
return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
}
- std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+ std::vector<Fps> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
return refreshRateConfigs.mKnownFrameRates;
}
+ RefreshRate getMinRefreshRateByPolicy(const RefreshRateConfigs& refreshRateConfigs) {
+ std::lock_guard lock(refreshRateConfigs.mLock);
+ return refreshRateConfigs.getMinRefreshRateByPolicyLocked();
+ }
+
+ RefreshRate getMinSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
+ std::lock_guard lock(refreshRateConfigs.mLock);
+ return *refreshRateConfigs.mMinSupportedRefreshRate;
+ }
+
+ RefreshRate getMaxSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
+ std::lock_guard lock(refreshRateConfigs.mLock);
+ return *refreshRateConfigs.mMaxSupportedRefreshRate;
+ }
+
// Test config IDs
static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
@@ -62,29 +82,29 @@
// Test configs
std::shared_ptr<const HWC2::Display::Config> mConfig60 =
- createConfig(HWC_CONFIG_ID_60, 0, static_cast<int64_t>(1e9f / 60));
+ createConfig(HWC_CONFIG_ID_60, 0, Fps(60.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig90 =
- createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90));
+ createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentGroup =
- createConfig(HWC_CONFIG_ID_90, 1, static_cast<int64_t>(1e9f / 90));
+ createConfig(HWC_CONFIG_ID_90, 1, Fps(90.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentResolution =
- createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90), 111, 222);
+ createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs(), 111, 222);
std::shared_ptr<const HWC2::Display::Config> mConfig72 =
- createConfig(HWC_CONFIG_ID_72, 0, static_cast<int64_t>(1e9f / 72));
+ createConfig(HWC_CONFIG_ID_72, 0, Fps(72.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig72DifferentGroup =
- createConfig(HWC_CONFIG_ID_72, 1, static_cast<int64_t>(1e9f / 72));
+ createConfig(HWC_CONFIG_ID_72, 1, Fps(72.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig120 =
- createConfig(HWC_CONFIG_ID_120, 0, static_cast<int64_t>(1e9f / 120));
+ createConfig(HWC_CONFIG_ID_120, 0, Fps(120.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig120DifferentGroup =
- createConfig(HWC_CONFIG_ID_120, 1, static_cast<int64_t>(1e9f / 120));
+ createConfig(HWC_CONFIG_ID_120, 1, Fps(120.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig30 =
- createConfig(HWC_CONFIG_ID_30, 0, static_cast<int64_t>(1e9f / 30));
+ createConfig(HWC_CONFIG_ID_30, 0, Fps(30.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig30DifferentGroup =
- createConfig(HWC_CONFIG_ID_30, 1, static_cast<int64_t>(1e9f / 30));
+ createConfig(HWC_CONFIG_ID_30, 1, Fps(30.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig25DifferentGroup =
- createConfig(HWC_CONFIG_ID_25, 1, static_cast<int64_t>(1e9f / 25));
+ createConfig(HWC_CONFIG_ID_25, 1, Fps(25.0f).getPeriodNsecs());
std::shared_ptr<const HWC2::Display::Config> mConfig50 =
- createConfig(HWC_CONFIG_ID_50, 0, static_cast<int64_t>(1e9f / 50));
+ createConfig(HWC_CONFIG_ID_50, 0, Fps(50.0f).getPeriodNsecs());
// Test device configurations
// The positions of the configs in the arrays below MUST match their IDs. For example,
@@ -124,23 +144,23 @@
mConfig50};
// Expected RefreshRate objects
- RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, "60fps", 60,
+ RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, Fps(60),
RefreshRate::ConstructorTag(0)};
RefreshRate mExpectedAlmost60Config = {HWC_CONFIG_ID_60,
- createConfig(HWC_CONFIG_ID_60, 0, 16666665), "60fps", 60,
+ createConfig(HWC_CONFIG_ID_60, 0, 16666665), Fps(60),
RefreshRate::ConstructorTag(0)};
- RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, "90fps", 90,
+ RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, Fps(90),
RefreshRate::ConstructorTag(0)};
RefreshRate mExpected90DifferentGroupConfig = {HWC_CONFIG_ID_90, mConfig90DifferentGroup,
- "90fps", 90, RefreshRate::ConstructorTag(0)};
+ Fps(90), RefreshRate::ConstructorTag(0)};
RefreshRate mExpected90DifferentResolutionConfig = {HWC_CONFIG_ID_90,
- mConfig90DifferentResolution, "90fps", 90,
+ mConfig90DifferentResolution, Fps(90),
RefreshRate::ConstructorTag(0)};
- RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, "72fps", 72,
+ RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, Fps(72.0f),
RefreshRate::ConstructorTag(0)};
- RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, "30fps", 30,
+ RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, Fps(30),
RefreshRate::ConstructorTag(0)};
- RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, "120fps", 120,
+ RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, Fps(120),
RefreshRate::ConstructorTag(0)};
Hwc2::mock::Display mDisplay;
@@ -192,8 +212,11 @@
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HwcConfigIndexType(10), {60, 60}}), 0);
- ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {20, 40}}), 0);
+ ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy(
+ {HwcConfigIndexType(10), {Fps(60), Fps(60)}}),
+ 0);
+ ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(20), Fps(40)}}),
+ 0);
}
TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
@@ -201,13 +224,13 @@
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- const auto& minRate = refreshRateConfigs->getMinRefreshRate();
- const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
+ const auto& minRate = getMinSupportedRefreshRate(*refreshRateConfigs);
+ const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
ASSERT_EQ(mExpected60Config, minRate);
ASSERT_EQ(mExpected90Config, performanceRate);
- const auto& minRateByPolicy = refreshRateConfigs->getMinRefreshRateByPolicy();
+ const auto& minRateByPolicy = getMinRefreshRateByPolicy(*refreshRateConfigs);
const auto& performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(minRateByPolicy, minRate);
ASSERT_EQ(performanceRateByPolicy, performanceRate);
@@ -218,19 +241,20 @@
std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- const auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
- const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
- const auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+ const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+ const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+ const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected60Config, minRate);
ASSERT_EQ(mExpected60Config, minRate60);
ASSERT_EQ(mExpected60Config, performanceRate60);
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+ 0);
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+ const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate);
@@ -243,19 +267,20 @@
std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentResolutions,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- const auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
- const auto& performanceRate = refreshRateConfigs->getMaxRefreshRate();
- const auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+ const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+ const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+ const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected60Config, minRate);
ASSERT_EQ(mExpected60Config, minRate60);
ASSERT_EQ(mExpected60Config, performanceRate60);
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+ 0);
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+ const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate);
@@ -268,16 +293,17 @@
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- auto& minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
- auto& performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+ auto minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
+ auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected60Config, minRate);
ASSERT_EQ(mExpected90Config, performanceRate);
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+ 0);
- auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
- auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+ auto minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
+ auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
ASSERT_EQ(mExpected60Config, minRate60);
ASSERT_EQ(mExpected60Config, performanceRate60);
}
@@ -287,19 +313,20 @@
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
{
- auto& current = refreshRateConfigs->getCurrentRefreshRate();
+ auto current = refreshRateConfigs->getCurrentRefreshRate();
EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_60);
}
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
{
- auto& current = refreshRateConfigs->getCurrentRefreshRate();
+ auto current = refreshRateConfigs->getCurrentRefreshRate();
EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
}
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+ 0);
{
- auto& current = refreshRateConfigs->getCurrentRefreshRate();
+ auto current = refreshRateConfigs->getCurrentRefreshRate();
EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
}
}
@@ -315,7 +342,8 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+ 0);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
@@ -338,34 +366,35 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz Heuristic";
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
lr.name = "45Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
lr.name = "30Hz Heuristic";
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
lr.name = "24Hz Heuristic";
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.name = "";
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+ 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected60Config,
@@ -375,28 +404,30 @@
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+ {HWC_CONFIG_ID_90, {Fps(90.0f), Fps(90.0f)}}),
+ 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected90Config,
@@ -406,28 +437,30 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+ {HWC_CONFIG_ID_60, {Fps(0.0f), Fps(120.0f)}}),
+ 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -436,24 +469,24 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
@@ -474,24 +507,24 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
@@ -506,23 +539,23 @@
auto& lr1 = layers[0];
auto& lr2 = layers[1];
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected120Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 48.0f;
+ lr2.desiredRefreshRate = Fps(48.0f);
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 48.0f;
+ lr2.desiredRefreshRate = Fps(48.0f);
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -538,82 +571,82 @@
auto& lr1 = layers[0];
auto& lr2 = layers[1];
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
EXPECT_EQ(mExpected120Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
EXPECT_EQ(mExpected120Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "60Hz ExplicitDefault";
EXPECT_EQ(mExpected120Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::Heuristic;
lr1.name = "24Hz Heuristic";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr1.desiredRefreshRate = 24.0f;
+ lr1.desiredRefreshRate = Fps(24.0f);
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.name = "90Hz ExplicitExactOrMultiple";
EXPECT_EQ(mExpected90Config,
@@ -636,24 +669,24 @@
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
EXPECT_EQ(mExpected30Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
@@ -676,41 +709,41 @@
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz Heuristic";
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
- lr.desiredRefreshRate = 45.0f;
+ lr.desiredRefreshRate = Fps(45.0f);
lr.name = "45Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
- lr.desiredRefreshRate = 30.0f;
+ lr.desiredRefreshRate = Fps(30.0f);
lr.name = "30Hz Heuristic";
EXPECT_EQ(mExpected30Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
lr.name = "24Hz Heuristic";
EXPECT_EQ(mExpected72Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
- lr.desiredRefreshRate = 24.0f;
+ lr.desiredRefreshRate = Fps(24.0f);
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
lr.name = "24Hz ExplicitExactOrMultiple";
EXPECT_EQ(mExpected72Config,
@@ -736,39 +769,39 @@
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 24.0f;
+ lr2.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 24.0f;
+ lr2.desiredRefreshRate = Fps(24.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 15.0f;
+ lr1.desiredRefreshRate = Fps(15.0f);
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 45.0f;
+ lr2.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 30.0f;
+ lr1.desiredRefreshRate = Fps(30.0f);
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 45.0f;
+ lr2.desiredRefreshRate = Fps(45.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
@@ -783,7 +816,7 @@
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
- lr.desiredRefreshRate = fps;
+ lr.desiredRefreshRate = Fps(fps);
const auto& refreshRate =
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -801,33 +834,33 @@
auto& lr2 = layers[1];
lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 90.0f;
+ lr1.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 90.0f;
+ lr1.desiredRefreshRate = Fps(90.0f);
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, testInPolicy) {
- ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(60.000004f, 60.000004f));
- ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(59.0f, 60.1f));
- ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(75.0f, 90.0f));
- ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(60.0011f, 90.0f));
- ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(50.0f, 59.998f));
+ ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(60.000004f), Fps(60.000004f)));
+ ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(59.0f), Fps(60.1f)));
+ ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(75.0f), Fps(90.0f)));
+ ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(60.0011f), Fps(90.0f)));
+ ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(50.0f), Fps(59.998f)));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
@@ -840,7 +873,7 @@
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
- lr.desiredRefreshRate = fps;
+ lr.desiredRefreshRate = Fps(fps);
const auto& refreshRate =
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -858,25 +891,25 @@
auto& lr2 = layers[1];
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected60Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
@@ -884,16 +917,16 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 30.0f;
+ lr1.desiredRefreshRate = Fps(30.0f);
lr1.name = "30Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 30.0f;
+ lr1.desiredRefreshRate = Fps(30.0f);
lr1.name = "30Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
@@ -912,7 +945,7 @@
auto& lr2 = layers[1];
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
@@ -920,7 +953,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
@@ -928,7 +961,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
@@ -936,7 +969,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
@@ -945,10 +978,10 @@
// The other layer starts to provide buffers
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90.0f;
+ lr2.desiredRefreshRate = Fps(90.0f);
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -972,40 +1005,40 @@
auto& lr2 = layers[1];
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.name = "60Hz Heuristic";
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
&consideredSignals);
EXPECT_EQ(true, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.name = "60Hz Heuristic";
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
&consideredSignals);
EXPECT_EQ(false, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.name = "60Hz Heuristic";
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
&consideredSignals);
EXPECT_EQ(true, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 60.0f;
+ lr1.desiredRefreshRate = Fps(60.0f);
lr1.name = "60Hz ExplicitExactOrMultiple";
lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60.0f;
+ lr2.desiredRefreshRate = Fps(60.0f);
lr2.name = "60Hz Heuristic";
refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
&consideredSignals);
@@ -1041,7 +1074,7 @@
for (const auto& test : testCases) {
lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = test.first;
+ lr.desiredRefreshRate = Fps(test.first);
std::stringstream ss;
ss << "ExplicitDefault " << test.first << " fps";
@@ -1049,7 +1082,7 @@
const auto& refreshRate =
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
- EXPECT_FLOAT_EQ(refreshRate.getFps(), test.second)
+ EXPECT_TRUE(refreshRate.getFps().equalsWithMargin(Fps(test.second)))
<< "Expecting " << test.first << "fps => " << test.second << "Hz";
}
}
@@ -1061,7 +1094,7 @@
/*currentConfigId=*/HWC_CONFIG_ID_90);
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+ {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
0);
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1069,7 +1102,7 @@
RefreshRateConfigs::GlobalSignals consideredSignals;
lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz ExplicitDefault";
lr.focused = true;
EXPECT_EQ(mExpected60Config,
@@ -1085,14 +1118,14 @@
/*currentConfigId=*/HWC_CONFIG_ID_60);
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 90.f}}),
+ {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(90.f)}}),
0);
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
auto& lr = layers[0];
lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 90.0f;
+ lr.desiredRefreshRate = Fps(90.0f);
lr.name = "90Hz ExplicitDefault";
lr.focused = true;
EXPECT_EQ(mExpected90Config,
@@ -1106,7 +1139,7 @@
/*currentConfigId=*/HWC_CONFIG_ID_90);
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+ {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
0);
RefreshRateConfigs::GlobalSignals consideredSignals;
@@ -1119,7 +1152,7 @@
auto& lr = layers[0];
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz ExplicitExactOrMultiple";
lr.focused = false;
EXPECT_EQ(mExpected90Config,
@@ -1130,7 +1163,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz ExplicitDefault";
lr.focused = false;
EXPECT_EQ(mExpected90Config,
@@ -1141,7 +1174,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Heuristic;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz Heuristic";
lr.focused = false;
EXPECT_EQ(mExpected90Config,
@@ -1152,7 +1185,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz Max";
lr.focused = false;
EXPECT_EQ(mExpected90Config,
@@ -1163,7 +1196,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Min;
- lr.desiredRefreshRate = 60.0f;
+ lr.desiredRefreshRate = Fps(60.0f);
lr.name = "60Hz Min";
lr.focused = false;
EXPECT_EQ(mExpected90Config,
@@ -1182,7 +1215,7 @@
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
auto& layer = layers[0];
layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 90.0f;
+ layer.desiredRefreshRate = Fps(90.0f);
layer.seamlessness = Seamlessness::SeamedAndSeamless;
layer.name = "90Hz ExplicitDefault";
layer.focused = true;
@@ -1207,7 +1240,7 @@
// Verify that we won't do a seamless switch if we request the same mode as the default
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- layer.desiredRefreshRate = 60.0f;
+ layer.desiredRefreshRate = Fps(60.0f);
layer.name = "60Hz ExplicitDefault";
layer.seamlessness = Seamlessness::OnlySeamless;
ASSERT_EQ(HWC_CONFIG_ID_90,
@@ -1216,7 +1249,7 @@
// Verify that if the current config is in another group and there are no layers with
// seamlessness=SeamedAndSeamless we'll go back to the default group.
- layer.desiredRefreshRate = 60.0f;
+ layer.desiredRefreshRate = Fps(60.0f);
layer.name = "60Hz ExplicitDefault";
layer.seamlessness = Seamlessness::Default;
ASSERT_EQ(HWC_CONFIG_ID_60,
@@ -1231,7 +1264,7 @@
layers.push_back(LayerRequirement{.weight = 0.5f});
auto& layer2 = layers[layers.size() - 1];
layer2.vote = LayerVoteType::ExplicitDefault;
- layer2.desiredRefreshRate = 90.0f;
+ layer2.desiredRefreshRate = Fps(90.0f);
layer2.name = "90Hz ExplicitDefault";
layer2.seamlessness = Seamlessness::SeamedAndSeamless;
layer2.focused = false;
@@ -1262,7 +1295,7 @@
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
auto& layer = layers[0];
layer.vote = LayerVoteType::ExplicitExactOrMultiple;
- layer.desiredRefreshRate = 60.0f;
+ layer.desiredRefreshRate = Fps(60.0f);
layer.seamlessness = Seamlessness::SeamedAndSeamless;
layer.name = "60Hz ExplicitExactOrMultiple";
layer.focused = true;
@@ -1291,13 +1324,13 @@
auto layers = std::vector<
LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
.vote = LayerVoteType::ExplicitDefault,
- .desiredRefreshRate = 60.0f,
+ .desiredRefreshRate = Fps(60.0f),
.seamlessness = Seamlessness::SeamedAndSeamless,
.weight = 0.5f,
.focused = false},
LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
.vote = LayerVoteType::ExplicitExactOrMultiple,
- .desiredRefreshRate = 25.0f,
+ .desiredRefreshRate = Fps(25.0f),
.seamlessness = Seamlessness::OnlySeamless,
.weight = 1.0f,
.focused = true}};
@@ -1307,7 +1340,7 @@
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
- seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = 30.0f;
+ seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = Fps(30.0f);
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_30);
ASSERT_EQ(HWC_CONFIG_ID_25,
@@ -1325,7 +1358,7 @@
// Return the config ID from calling getBestRefreshRate() for a single layer with the
// given voteType and fps.
- auto getFrameRate = [&](LayerVoteType voteType, float fps, bool touchActive = false,
+ auto getFrameRate = [&](LayerVoteType voteType, Fps fps, bool touchActive = false,
bool focused = true) -> HwcConfigIndexType {
layers[0].vote = voteType;
layers[0].desiredRefreshRate = fps;
@@ -1335,43 +1368,44 @@
};
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_60, {30.f, 60.f}, {30.f, 90.f}}),
+ {HWC_CONFIG_ID_60, {Fps(30.f), Fps(60.f)}, {Fps(30.f), Fps(90.f)}}),
0);
EXPECT_EQ(HWC_CONFIG_ID_60,
refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false})
.getConfigId());
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
// Layers not focused are not allowed to override primary config
EXPECT_EQ(HWC_CONFIG_ID_60,
- getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/false,
+ getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/false,
/*focused=*/false));
EXPECT_EQ(HWC_CONFIG_ID_60,
- getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/false,
+ getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/false,
/*focused=*/false));
// Touch boost should be restricted to the primary range.
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f, /*touch=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f), /*touch=*/true));
// When we're higher than the primary range max due to a layer frame rate setting, touch boost
// shouldn't drag us back down to the primary range max.
- EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90,
+ getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/true));
EXPECT_EQ(HWC_CONFIG_ID_60,
- getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/true));
+ getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/true));
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 60.f}}),
+ {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(60.f)}}),
0);
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
- EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
}
TEST_F(RefreshRateConfigsTest, idle) {
@@ -1385,7 +1419,7 @@
const auto getIdleFrameRate = [&](LayerVoteType voteType,
bool touchActive) -> HwcConfigIndexType {
layers[0].vote = voteType;
- layers[0].desiredRefreshRate = 90.f;
+ layers[0].desiredRefreshRate = Fps(90.f);
RefreshRateConfigs::GlobalSignals consideredSignals;
const auto configId =
refreshRateConfigs
@@ -1398,7 +1432,7 @@
};
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_60, {60.f, 90.f}, {60.f, 90.f}}),
+ {HWC_CONFIG_ID_60, {Fps(60.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
0);
// Idle should be lower priority than touch boost.
@@ -1439,22 +1473,22 @@
/*currentConfigId=*/HWC_CONFIG_ID_60);
for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
- const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
- float expectedFrameRate;
+ const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, Fps(fps));
+ Fps expectedFrameRate;
if (fps < 26.91f) {
- expectedFrameRate = 24.0f;
+ expectedFrameRate = Fps(24.0f);
} else if (fps < 37.51f) {
- expectedFrameRate = 30.0f;
+ expectedFrameRate = Fps(30.0f);
} else if (fps < 52.51f) {
- expectedFrameRate = 45.0f;
+ expectedFrameRate = Fps(45.0f);
} else if (fps < 66.01f) {
- expectedFrameRate = 60.0f;
+ expectedFrameRate = Fps(60.0f);
} else if (fps < 81.01f) {
- expectedFrameRate = 72.0f;
+ expectedFrameRate = Fps(72.0f);
} else {
- expectedFrameRate = 90.0f;
+ expectedFrameRate = Fps(90.0f);
}
- EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+ EXPECT_TRUE(expectedFrameRate.equalsWithMargin(knownFrameRate))
<< "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
}
}
@@ -1465,26 +1499,27 @@
/*currentConfigId=*/HWC_CONFIG_ID_60);
struct ExpectedRate {
- float rate;
+ Fps rate;
const RefreshRate& expected;
};
/* clang-format off */
std::vector<ExpectedRate> knownFrameRatesExpectations = {
- {24.0f, mExpected60Config},
- {30.0f, mExpected60Config},
- {45.0f, mExpected90Config},
- {60.0f, mExpected60Config},
- {72.0f, mExpected90Config},
- {90.0f, mExpected90Config},
+ {Fps(24.0f), mExpected60Config},
+ {Fps(30.0f), mExpected60Config},
+ {Fps(45.0f), mExpected90Config},
+ {Fps(60.0f), mExpected60Config},
+ {Fps(72.0f), mExpected90Config},
+ {Fps(90.0f), mExpected90Config},
};
/* clang-format on */
// Make sure the test tests all the known frame rate
const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
- const auto equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
- knownFrameRatesExpectations.begin(),
- [](float a, const ExpectedRate& b) { return a == b.rate; });
+ const auto equal =
+ std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+ knownFrameRatesExpectations.begin(),
+ [](Fps a, const ExpectedRate& b) { return a.equalsWithMargin(b.rate); });
EXPECT_TRUE(equal);
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1514,52 +1549,132 @@
EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
// SetPolicy(60, 90), current 60Hz => TurnOn.
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(90)}}),
+ 0);
EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
// SetPolicy(60, 60), current 60Hz => NoChange, avoid extra calls.
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+ 0);
EXPECT_EQ(KernelIdleTimerAction::NoChange, refreshRateConfigs->getIdleTimerAction());
// SetPolicy(90, 90), current 90Hz => TurnOff.
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+ 0);
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.5});
- 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 d0bb9e2..2188402 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -16,7 +16,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 "SchedulerUnittests"
@@ -25,6 +25,8 @@
#include <log/log.h>
#include <thread>
+#include "Scheduler/HwcStrongTypes.h"
+#include "Scheduler/RefreshRateConfigs.h"
#include "Scheduler/RefreshRateStats.h"
#include "mock/DisplayHardware/MockDisplay.h"
#include "mock/MockTimeStats.h"
@@ -51,8 +53,9 @@
void init(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
mRefreshRateConfigs =
std::make_unique<RefreshRateConfigs>(configs, /*currentConfig=*/CONFIG_ID_0);
- mRefreshRateStats = std::make_unique<RefreshRateStats>(*mRefreshRateConfigs, mTimeStats,
- /*currentConfigId=*/CONFIG_ID_0,
+
+ const auto currFps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+ mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, currFps,
/*currentPowerMode=*/PowerMode::OFF);
}
@@ -80,8 +83,8 @@
std::shared_ptr<const HWC2::Display::Config> RefreshRateStatsTest::createConfig(
HwcConfigIndexType configId, int32_t configGroup, int64_t vsyncPeriod) {
- return HWC2::Display::Config::Builder(mDisplay, configId.value())
- .setVsyncPeriod(int32_t(vsyncPeriod))
+ return HWC2::Display::Config::Builder(mDisplay, static_cast<hal::HWConfigId>(configId.value()))
+ .setVsyncPeriod(static_cast<int32_t>(vsyncPeriod))
.setConfigGroup(configGroup)
.build();
}
@@ -102,7 +105,7 @@
// Setting up tests on mobile harness can be flaky with time passing, so testing for
// exact time changes can result in flaxy numbers. To avoid that remember old
// numbers to make sure the correct values are increasing in the next test.
- int screenOff = times["ScreenOff"];
+ auto screenOff = times["ScreenOff"];
// Screen is off by default.
std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -110,7 +113,8 @@
EXPECT_LT(screenOff, times["ScreenOff"]);
EXPECT_EQ(0u, times.count("90.00fps"));
- mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+ const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+ mRefreshRateStats->setRefreshRate(config0Fps);
mRefreshRateStats->setPowerMode(PowerMode::ON);
screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -120,13 +124,13 @@
EXPECT_LT(0, times["90.00fps"]);
mRefreshRateStats->setPowerMode(PowerMode::DOZE);
- int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+ auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
EXPECT_LT(screenOff, times["ScreenOff"]);
EXPECT_EQ(ninety, times["90.00fps"]);
- mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+ mRefreshRateStats->setRefreshRate(config0Fps);
screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
@@ -150,14 +154,16 @@
// Setting up tests on mobile harness can be flaky with time passing, so testing for
// exact time changes can result in flaxy numbers. To avoid that remember old
// numbers to make sure the correct values are increasing in the next test.
- int screenOff = times["ScreenOff"];
+ auto screenOff = times["ScreenOff"];
// Screen is off by default.
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
EXPECT_LT(screenOff, times["ScreenOff"]);
- mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+ const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_0).getFps();
+ const auto config1Fps = mRefreshRateConfigs->getRefreshRateFromConfigId(CONFIG_ID_1).getFps();
+ mRefreshRateStats->setRefreshRate(config0Fps);
mRefreshRateStats->setPowerMode(PowerMode::ON);
screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -167,8 +173,8 @@
EXPECT_LT(0, times["90.00fps"]);
// When power mode is normal, time for configs updates.
- mRefreshRateStats->setConfigMode(CONFIG_ID_1);
- int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+ mRefreshRateStats->setRefreshRate(config1Fps);
+ auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
EXPECT_EQ(screenOff, times["ScreenOff"]);
@@ -176,15 +182,15 @@
ASSERT_EQ(1u, times.count("60.00fps"));
EXPECT_LT(0, times["60.00fps"]);
- mRefreshRateStats->setConfigMode(CONFIG_ID_0);
- int sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
+ mRefreshRateStats->setRefreshRate(config0Fps);
+ auto sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
EXPECT_EQ(screenOff, times["ScreenOff"]);
EXPECT_LT(ninety, times["90.00fps"]);
EXPECT_EQ(sixty, times["60.00fps"]);
- mRefreshRateStats->setConfigMode(CONFIG_ID_1);
+ mRefreshRateStats->setRefreshRate(config1Fps);
ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
@@ -195,7 +201,7 @@
// Because the power mode is not PowerMode::ON, switching the config
// does not update refresh rates that come from the config.
mRefreshRateStats->setPowerMode(PowerMode::DOZE);
- mRefreshRateStats->setConfigMode(CONFIG_ID_0);
+ mRefreshRateStats->setRefreshRate(config0Fps);
sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
@@ -203,7 +209,7 @@
EXPECT_EQ(ninety, times["90.00fps"]);
EXPECT_EQ(sixty, times["60.00fps"]);
- mRefreshRateStats->setConfigMode(CONFIG_ID_1);
+ mRefreshRateStats->setRefreshRate(config1Fps);
screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
std::this_thread::sleep_for(std::chrono::milliseconds(2));
times = mRefreshRateStats->getTotalTimes();
@@ -216,4 +222,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 "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 5278641..1bbe8e2 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -108,11 +108,12 @@
*/
class SetFrameRateTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
protected:
- const FrameRate FRAME_RATE_VOTE1 = FrameRate(67.f, FrameRateCompatibility::Default);
- const FrameRate FRAME_RATE_VOTE2 = FrameRate(14.f, FrameRateCompatibility::ExactOrMultiple);
- const FrameRate FRAME_RATE_VOTE3 = FrameRate(99.f, FrameRateCompatibility::NoVote);
- const FrameRate FRAME_RATE_TREE = FrameRate(0, FrameRateCompatibility::NoVote);
- const FrameRate FRAME_RATE_NO_VOTE = FrameRate(0, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE1 = FrameRate(Fps(67.f), FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE2 =
+ FrameRate(Fps(14.f), FrameRateCompatibility::ExactOrMultiple);
+ const FrameRate FRAME_RATE_VOTE3 = FrameRate(Fps(99.f), FrameRateCompatibility::NoVote);
+ const FrameRate FRAME_RATE_TREE = FrameRate(Fps(0.f), FrameRateCompatibility::NoVote);
+ const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(0.f), FrameRateCompatibility::Default);
SetFrameRateTest();
@@ -431,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..8552e15 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);
@@ -612,11 +604,11 @@
EXPECT_EQ(newLayerStackRect, display.mutableDisplayDevice()->getLayerStackSpaceRect());
}
-TEST_F(HandleTransactionLockedTest, processesDisplayFrameChanges) {
+TEST_F(HandleTransactionLockedTest, processesDisplayRectChanges) {
using Case = NonHwcVirtualDisplayCase;
- const Rect oldFrame(0, 0, 0, 0);
- const Rect newFrame(0, 0, 123, 456);
+ const Rect oldDisplayRect(0, 0);
+ const Rect newDisplayRect(123, 456);
// --------------------------------------------------------------------
// Preconditions
@@ -626,8 +618,8 @@
display.inject();
// There is a change to the layerStackSpaceRect state
- display.mutableDrawingDisplayState().orientedDisplaySpaceRect = oldFrame;
- display.mutableCurrentDisplayState().orientedDisplaySpaceRect = newFrame;
+ display.mutableDrawingDisplayState().orientedDisplaySpaceRect = oldDisplayRect;
+ display.mutableCurrentDisplayState().orientedDisplaySpaceRect = newDisplayRect;
// --------------------------------------------------------------------
// Invocation
@@ -637,7 +629,7 @@
// --------------------------------------------------------------------
// Postconditions
- EXPECT_EQ(newFrame, display.mutableDisplayDevice()->getOrientedDisplaySpaceRect());
+ EXPECT_EQ(newDisplayRect, display.mutableDisplayDevice()->getOrientedDisplaySpaceRect());
}
TEST_F(HandleTransactionLockedTest, processesDisplayWidthChanges) {
@@ -730,5 +722,61 @@
mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
}
+TEST_F(HandleTransactionLockedTest, processesDisplaySizeDisplayRectAndLayerStackRectChanges) {
+ using Case = NonHwcVirtualDisplayCase;
+
+ constexpr uint32_t kOldWidth = 567;
+ constexpr uint32_t kOldHeight = 456;
+ const auto kOldSize = Rect(kOldWidth, kOldHeight);
+
+ constexpr uint32_t kNewWidth = 234;
+ constexpr uint32_t kNewHeight = 123;
+ const auto kNewSize = Rect(kNewWidth, kNewHeight);
+
+ // --------------------------------------------------------------------
+ // Preconditions
+
+ // A display is set up
+ auto nativeWindow = new mock::NativeWindow();
+ auto displaySurface = new compositionengine::mock::DisplaySurface();
+ sp<GraphicBuffer> buf = new GraphicBuffer();
+ auto display = Case::Display::makeFakeExistingDisplayInjector(this);
+ display.setNativeWindow(nativeWindow);
+ display.setDisplaySurface(displaySurface);
+ // Setup injection expectations
+ EXPECT_CALL(*nativeWindow, query(NATIVE_WINDOW_WIDTH, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kOldWidth), Return(0)));
+ EXPECT_CALL(*nativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kOldHeight), Return(0)));
+ display.inject();
+
+ // There is a change to the layerStackSpaceRect state
+ display.mutableDrawingDisplayState().width = kOldWidth;
+ display.mutableDrawingDisplayState().height = kOldHeight;
+ display.mutableDrawingDisplayState().layerStackSpaceRect = kOldSize;
+ display.mutableDrawingDisplayState().orientedDisplaySpaceRect = kOldSize;
+
+ display.mutableCurrentDisplayState().width = kNewWidth;
+ display.mutableCurrentDisplayState().height = kNewHeight;
+ display.mutableCurrentDisplayState().layerStackSpaceRect = kNewSize;
+ display.mutableCurrentDisplayState().orientedDisplaySpaceRect = kNewSize;
+
+ // --------------------------------------------------------------------
+ // Call Expectations
+
+ EXPECT_CALL(*displaySurface, resizeBuffers(kNewWidth, kNewHeight)).Times(1);
+
+ // --------------------------------------------------------------------
+ // Invocation
+
+ mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+
+ EXPECT_EQ(display.mutableDisplayDevice()->getBounds(), kNewSize);
+ EXPECT_EQ(display.mutableDisplayDevice()->getWidth(), kNewWidth);
+ EXPECT_EQ(display.mutableDisplayDevice()->getHeight(), kNewHeight);
+ EXPECT_EQ(display.mutableDisplayDevice()->getOrientedDisplaySpaceRect(), kNewSize);
+ EXPECT_EQ(display.mutableDisplayDevice()->getLayerStackSpaceRect(), kNewSize);
+}
+
} // namespace
} // namespace android
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 8a0276a..739a9b2 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -23,6 +23,7 @@
#include <compositionengine/impl/Display.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
#include <compositionengine/mock/DisplaySurface.h>
+#include <gui/ScreenCaptureResults.h>
#include "BufferQueueLayer.h"
#include "BufferStateLayer.h"
@@ -42,6 +43,7 @@
#include "TestableScheduler.h"
#include "mock/DisplayHardware/MockDisplay.h"
#include "mock/MockDisplayIdGenerator.h"
+#include "mock/MockFrameTimeline.h"
#include "mock/MockFrameTracer.h"
namespace android {
@@ -77,7 +79,7 @@
}
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
- const scheduler::RefreshRateConfigs& /*refreshRateConfigs*/) override {
+ Fps /*currentRefreshRate*/) override {
return std::make_unique<scheduler::FakePhaseOffsets>();
}
@@ -154,6 +156,11 @@
return std::make_unique<mock::FrameTracer>();
}
+ std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+ return std::make_unique<mock::FrameTimeline>(timeStats, surfaceFlingerPid);
+ }
+
using CreateBufferQueueFunction =
std::function<void(sp<IGraphicBufferProducer>* /* outProducer */,
sp<IGraphicBufferConsumer>* /* outConsumer */,
@@ -217,14 +224,15 @@
.build());
}
- mFlinger->mRefreshRateConfigs = std::make_unique<
- scheduler::RefreshRateConfigs>(configs, /*currentConfig=*/HwcConfigIndexType(0));
- mFlinger->mRefreshRateStats = std::make_unique<
- scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs, *mFlinger->mTimeStats,
- /*currentConfig=*/HwcConfigIndexType(0),
- /*powerMode=*/hal::PowerMode::OFF);
- mFlinger->mVsyncConfiguration =
- mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
+ const auto currConfig = HwcConfigIndexType(0);
+ mFlinger->mRefreshRateConfigs =
+ std::make_unique<scheduler::RefreshRateConfigs>(configs, currConfig);
+ const auto currFps =
+ mFlinger->mRefreshRateConfigs->getRefreshRateFromConfigId(currConfig).getFps();
+ mFlinger->mRefreshRateStats =
+ std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
+ /*powerMode=*/hal::PowerMode::OFF);
+ mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
@@ -358,28 +366,30 @@
return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
}
- auto& getTransactionQueue() { return mFlinger->mTransactionQueues; }
+ auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
auto 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,
+ 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(); };
+ auto flushPendingTransactionQueues() { return mFlinger->flushPendingTransactionQueues(); };
auto onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
return mFlinger->onTransact(code, data, reply, flags);
}
+ auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
/* ------------------------------------------------------------------------
* Read-only access to private data to assert post-conditions.
*/
@@ -552,8 +562,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;
+ }
}
}
@@ -682,6 +699,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 a90f424..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,11 +357,11 @@
EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::Display);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::None);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerCpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::DisplayHAL);
+ mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::None);
const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
std::string expectedResult = "totalTimelineFrames = " + std::to_string(5);
@@ -383,13 +384,13 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
std::string expectedResult = "totalTimelineFrames = " + std::to_string(5);
@@ -848,13 +849,13 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count()));
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
EXPECT_TRUE(inputCommand(InputCommand::CLEAR, FMT_STRING).empty());
@@ -987,11 +988,11 @@
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::Display);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::None);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerCpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::DisplayHAL);
+ mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::None);
EXPECT_THAT(mDelegate->mAtomTags,
UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
@@ -1062,13 +1063,13 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerCpuDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::DisplayHAL);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
EXPECT_THAT(mDelegate->mAtomTags,
UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
@@ -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..a4a4408 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,28 +116,32 @@
}
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;
}
void NotPlacedOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0, mFlinger.getTransactionQueue().size());
+ ASSERT_EQ(0, mFlinger.getPendingTransactionQueue().size());
// called in SurfaceFlinger::signalTransaction
EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
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
@@ -148,12 +154,12 @@
} else {
EXPECT_LE(returnedTime, applicationTime + s2ns(5));
}
- auto transactionQueue = mFlinger.getTransactionQueue();
+ auto transactionQueue = mFlinger.getPendingTransactionQueue();
EXPECT_EQ(0, transactionQueue.size());
}
void PlaceOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0, mFlinger.getTransactionQueue().size());
+ ASSERT_EQ(0, mFlinger.getPendingTransactionQueue().size());
// called in SurfaceFlinger::signalTransaction
EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
@@ -164,23 +170,25 @@
.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));
// This transaction should have been placed on the transaction queue
- auto transactionQueue = mFlinger.getTransactionQueue();
+ auto transactionQueue = mFlinger.getPendingTransactionQueue();
EXPECT_EQ(1, transactionQueue.size());
}
void BlockedByPriorTransaction(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0, mFlinger.getTransactionQueue().size());
+ ASSERT_EQ(0, mFlinger.getPendingTransactionQueue().size());
// called in SurfaceFlinger::signalTransaction
nsecs_t time = systemTime();
EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
@@ -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
@@ -227,7 +239,7 @@
}
// check that there is one binder on the pending queue.
- auto transactionQueue = mFlinger.getTransactionQueue();
+ auto transactionQueue = mFlinger.getPendingTransactionQueue();
EXPECT_EQ(1, transactionQueue.size());
auto& [applyToken, transactionStates] = *(transactionQueue.begin());
@@ -246,7 +258,7 @@
};
TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
- ASSERT_EQ(0, mFlinger.getTransactionQueue().size());
+ ASSERT_EQ(0, mFlinger.getPendingTransactionQueue().size());
// called in SurfaceFlinger::signalTransaction
EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
@@ -256,14 +268,14 @@
.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();
+ auto& transactionQueue = mFlinger.getPendingTransactionQueue();
ASSERT_EQ(1, transactionQueue.size());
auto& [applyToken, transactionStates] = *(transactionQueue.begin());
@@ -279,12 +291,12 @@
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
+ // flush pending transaction queue should flush as desiredPresentTime has
// passed
- mFlinger.flushTransactionQueues();
+ mFlinger.flushPendingTransactionQueues();
EXPECT_EQ(0, transactionQueue.size());
}
@@ -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 72ee6db..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,19 +28,21 @@
namespace android::scheduler {
+using namespace std::chrono_literals;
+
class TestableWorkDuration : public impl::WorkDuration {
public:
- TestableWorkDuration(float currentFps, nsecs_t sfDuration, nsecs_t appDuration,
+ TestableWorkDuration(Fps currentFps, nsecs_t sfDuration, nsecs_t appDuration,
nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
- : impl::WorkDuration({60.0f, 90.0f}, currentFps, sfDuration, appDuration, sfEarlyDuration,
+ : impl::WorkDuration(currentFps, sfDuration, appDuration, sfEarlyDuration,
appEarlyDuration, sfEarlyGlDuration, appEarlyGlDuration) {}
};
class WorkDurationTest : public testing::Test {
protected:
WorkDurationTest()
- : mWorkDuration(60.0f, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
+ : mWorkDuration(Fps(60.0f), 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
21'000'000) {}
~WorkDurationTest() = default;
@@ -51,9 +54,9 @@
* Test cases
*/
TEST_F(WorkDurationTest, getConfigsForRefreshRate_60Hz) {
- mWorkDuration.setRefreshRateFps(60.0f);
+ mWorkDuration.setRefreshRateFps(Fps(60.0f));
auto currentOffsets = mWorkDuration.getCurrentConfigs();
- auto offsets = mWorkDuration.getConfigsForRefreshRate(60.0f);
+ auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(60.0f));
EXPECT_EQ(currentOffsets, offsets);
EXPECT_EQ(offsets.late.sfOffset, 6'166'667);
@@ -76,9 +79,9 @@
}
TEST_F(WorkDurationTest, getConfigsForRefreshRate_90Hz) {
- mWorkDuration.setRefreshRateFps(90.0f);
+ mWorkDuration.setRefreshRateFps(Fps(90.0f));
auto currentOffsets = mWorkDuration.getCurrentConfigs();
- auto offsets = mWorkDuration.getConfigsForRefreshRate(90.0f);
+ auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(90.0f));
EXPECT_EQ(currentOffsets, offsets);
EXPECT_EQ(offsets.late.sfOffset, 611'111);
@@ -101,7 +104,7 @@
}
TEST_F(WorkDurationTest, getConfigsForRefreshRate_DefaultOffsets) {
- TestableWorkDuration phaseOffsetsWithDefaultValues(60.0f, -1, -1, -1, -1, -1, -1);
+ TestableWorkDuration phaseOffsetsWithDefaultValues(Fps(60.0f), -1, -1, -1, -1, -1, -1);
auto validateOffsets = [](const auto& offsets, std::chrono::nanoseconds vsyncPeriod) {
EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
@@ -123,21 +126,20 @@
EXPECT_EQ(offsets.earlyGpu.appWorkDuration, vsyncPeriod);
};
- const auto testForRefreshRate = [&](float refreshRate) {
+ const auto testForRefreshRate = [&](Fps refreshRate) {
phaseOffsetsWithDefaultValues.setRefreshRateFps(refreshRate);
auto currentOffsets = phaseOffsetsWithDefaultValues.getCurrentConfigs();
auto offsets = phaseOffsetsWithDefaultValues.getConfigsForRefreshRate(refreshRate);
EXPECT_EQ(currentOffsets, offsets);
- validateOffsets(offsets,
- std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / refreshRate)));
+ validateOffsets(offsets, std::chrono::nanoseconds(refreshRate.getPeriodNsecs()));
};
- testForRefreshRate(90.0f);
- testForRefreshRate(60.0f);
+ testForRefreshRate(Fps(90.0f));
+ testForRefreshRate(Fps(60.0f));
}
TEST_F(WorkDurationTest, getConfigsForRefreshRate_unknownRefreshRate) {
- auto offsets = mWorkDuration.getConfigsForRefreshRate(14.7f);
+ auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(14.7f));
EXPECT_EQ(offsets.late.sfOffset, 57'527'208);
EXPECT_EQ(offsets.late.appOffset, 37'027'208);
@@ -171,7 +173,7 @@
std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
nsecs_t thresholdForNextVsync)
- : impl::PhaseOffsets({60.0f, 90.0f}, 60.0f, vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
+ : impl::PhaseOffsets(Fps(60.0f), vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
@@ -190,7 +192,7 @@
};
TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_unknownRefreshRate) {
- auto offsets = mPhaseOffsets.getConfigsForRefreshRate(14.7f);
+ auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(14.7f));
EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -212,7 +214,7 @@
}
TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_60Hz) {
- auto offsets = mPhaseOffsets.getConfigsForRefreshRate(60.0f);
+ auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -234,7 +236,7 @@
}
TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_90Hz) {
- auto offsets = mPhaseOffsets.getConfigsForRefreshRate(90.0f);
+ auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -258,7 +260,7 @@
TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_60Hz) {
TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {}, 2'000'000,
1'000'000, {}, {}, {}, {}, 10'000'000};
- auto offsets = phaseOffsets.getConfigsForRefreshRate(60.0f);
+ auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
EXPECT_EQ(offsets.late.appOffset, 1'000'000);
@@ -282,7 +284,7 @@
TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_90Hz) {
TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {}, 2'000'000,
1'000'000, {}, {}, {}, {}, 10'000'000};
- auto offsets = phaseOffsets.getConfigsForRefreshRate(90.0f);
+ auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
EXPECT_EQ(offsets.late.appOffset, 2'000'000);
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
new file mode 100644
index 0000000..ff005a0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
@@ -0,0 +1,26 @@
+/*
+ * 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 "mock/MockFrameTimeline.h"
+
+namespace android::mock {
+
+// Explicit default instantiation is recommended.
+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
new file mode 100644
index 0000000..0a6a9f4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -0,0 +1,38 @@
+/*
+ * 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 <gmock/gmock.h>
+
+#include "FrameTimeline/FrameTimeline.h"
+
+namespace android::mock {
+
+class FrameTimeline : public android::frametimeline::impl::FrameTimeline {
+ // 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, pid_t surfaceFlingerPid);
+ ~FrameTimeline();
+
+ MOCK_METHOD0(onBootFinished, void());
+ 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>&));
+};
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 078d8e07..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
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 75228d6..c18bf18 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -19,6 +19,7 @@
"VibratorCallbackScheduler.cpp",
"VibratorHalController.cpp",
"VibratorHalWrapper.cpp",
+ "VibratorManagerHalController.cpp",
"VibratorManagerHalWrapper.cpp",
],
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index b24e5c4..6bf6581 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -26,13 +26,13 @@
namespace vibrator {
-std::shared_ptr<ManagerHalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
+std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler) {
static bool gHalExists = true;
if (gHalExists) {
sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
if (hal) {
ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
- return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), aidlHal);
+ return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), hal);
}
}
@@ -80,12 +80,11 @@
// -------------------------------------------------------------------------------------------------
-bool ManagerHalController::init() {
+void ManagerHalController::init() {
std::lock_guard<std::mutex> lock(mConnectedHalMutex);
if (mConnectedHal == nullptr) {
mConnectedHal = mConnector(mCallbackScheduler);
}
- return mConnectedHal != nullptr;
}
HalResult<void> ManagerHalController::ping() {
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
index cf82562..9168565 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -19,6 +19,7 @@
#include <android/hardware/vibrator/IVibratorManager.h>
#include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorManagerHalWrapper.h>
#include <unordered_map>
namespace android {
@@ -36,7 +37,7 @@
ManagerHalController()
: ManagerHalController(std::make_shared<CallbackScheduler>(), &connectManagerHal) {}
ManagerHalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
- : mConnector(connector), mConnectedHal(nullptr) {}
+ : mConnector(connector), mCallbackScheduler(callbackScheduler), mConnectedHal(nullptr) {}
virtual ~ManagerHalController() = default;
/* Connects to the HAL service, possibly waiting for the registered service to
@@ -64,9 +65,10 @@
private:
Connector mConnector;
+ std::shared_ptr<CallbackScheduler> mCallbackScheduler;
std::mutex mConnectedHalMutex;
// Shared pointer to allow local copies to be used by different threads.
- std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
+ std::shared_ptr<ManagerHalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
template <typename T>
HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 6801f76..9af1b7b 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -23,6 +23,7 @@
"VibratorHalWrapperHidlV1_1Test.cpp",
"VibratorHalWrapperHidlV1_2Test.cpp",
"VibratorHalWrapperHidlV1_3Test.cpp",
+ "VibratorManagerHalControllerTest.cpp",
"VibratorManagerHalWrapperAidlTest.cpp",
"VibratorManagerHalWrapperLegacyTest.cpp",
],
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
index 3b036ee..e5fbbae 100644
--- a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -22,22 +22,28 @@
#include <utils/Log.h>
-#include <vibratorservice/VibratorManagerHalWrapper.h>
+#include <vibratorservice/VibratorManagerHalController.h>
#include "test_utils.h"
+using android::vibrator::HalController;
+
using namespace android;
using namespace testing;
static constexpr int MAX_ATTEMPTS = 2;
+static const std::vector<int32_t> VIBRATOR_IDS = {1, 2};
+static constexpr int VIBRATOR_ID = 1;
class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
public:
+ MOCK_METHOD(void, tryReconnect, (), (override));
MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
- MOCK_METHOD(vibrator::HalResult<int32_t>, getCapabilities, (), (override));
+ MOCK_METHOD(vibrator::HalResult<vibrator::ManagerCapabilities>, getCapabilities, (),
+ (override));
MOCK_METHOD(vibrator::HalResult<std::vector<int32_t>>, getVibratorIds, (), (override));
- MOCK_METHOD(vibrator::HalResult<std::shared_ptr<vibrator::HalController>>, getVibrator,
- (int32_t id), (override));
+ MOCK_METHOD(vibrator::HalResult<std::shared_ptr<HalController>>, getVibrator, (int32_t id),
+ (override));
MOCK_METHOD(vibrator::HalResult<void>, prepareSynced, (const std::vector<int32_t>& ids),
(override));
MOCK_METHOD(vibrator::HalResult<void>, triggerSynced,
@@ -50,13 +56,13 @@
void SetUp() override {
mConnectCounter = 0;
auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
- mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
- mController = std::make_unique<
- vibrator::HalController>(std::move(callbackScheduler),
- [&](std::shared_ptr<vibrator::CallbackScheduler>) {
- android_atomic_inc(&(this->mConnectCounter));
- return this->mMockHal;
- });
+ mMockHal = std::make_shared<StrictMock<MockManagerHalWrapper>>();
+ auto connector = [this](std::shared_ptr<vibrator::CallbackScheduler>) {
+ android_atomic_inc(&mConnectCounter);
+ return mMockHal;
+ };
+ mController = std::make_unique<vibrator::ManagerHalController>(std::move(callbackScheduler),
+ connector);
ASSERT_NE(mController, nullptr);
}
@@ -65,8 +71,7 @@
std::shared_ptr<MockManagerHalWrapper> mMockHal;
std::unique_ptr<vibrator::ManagerHalController> mController;
- void setHalExpectations(int32_t cardinality, std::vector<int32_t> ids,
- vibrator::HalResult<void> voidResult,
+ void setHalExpectations(int32_t cardinality, vibrator::HalResult<void> voidResult,
vibrator::HalResult<vibrator::ManagerCapabilities> capabilitiesResult,
vibrator::HalResult<std::vector<int32_t>> idsResult,
vibrator::HalResult<std::shared_ptr<HalController>> vibratorResult) {
@@ -100,24 +105,20 @@
};
TEST_F(VibratorManagerHalControllerTest, TestInit) {
- ASSERT_TRUE(mController->init());
+ mController->init();
ASSERT_EQ(1, mConnectCounter);
// Noop when wrapper was already initialized.
- ASSERT_TRUE(mController->init());
+ mController->init();
ASSERT_EQ(1, mConnectCounter);
}
TEST_F(VibratorManagerHalControllerTest, TestApiCallsAreForwardedToHal) {
- std::vector<int32_t> ids;
- ids.push_back(1);
- ids.push_back(2);
-
- setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::ok(),
+ setHalExpectations(/* cardinality= */ 1, vibrator::HalResult<void>::ok(),
vibrator::HalResult<vibrator::ManagerCapabilities>::ok(
vibrator::ManagerCapabilities::SYNC),
- vibrator::HalResult<std::vector<int32_t>>::ok(ids),
- vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::ok(nullptr));
+ vibrator::HalResult<std::vector<int32_t>>::ok(VIBRATOR_IDS),
+ vibrator::HalResult<std::shared_ptr<HalController>>::ok(nullptr));
ASSERT_TRUE(mController->ping().isOk());
@@ -127,13 +128,13 @@
auto getVibratorIdsResult = mController->getVibratorIds();
ASSERT_TRUE(getVibratorIdsResult.isOk());
- ASSERT_EQ(ids, getVibratorIdsResult.value());
+ ASSERT_EQ(VIBRATOR_IDS, getVibratorIdsResult.value());
- auto getVibratorResult = mController->getVibrator(1);
+ auto getVibratorResult = mController->getVibrator(VIBRATOR_ID);
ASSERT_TRUE(getVibratorResult.isOk());
ASSERT_EQ(nullptr, getVibratorResult.value());
- ASSERT_TRUE(mController->prepareSynced(ids).isOk());
+ ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isOk());
ASSERT_TRUE(mController->triggerSynced([]() {}).isOk());
ASSERT_TRUE(mController->cancelSynced().isOk());
@@ -141,20 +142,18 @@
}
TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
- std::vector<int32_t> ids;
- setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::unsupported(),
+ setHalExpectations(/* cardinality= */ 1, vibrator::HalResult<void>::unsupported(),
vibrator::HalResult<vibrator::ManagerCapabilities>::unsupported(),
vibrator::HalResult<std::vector<int32_t>>::unsupported(),
- vibrator::HalResult<
- std::shared_ptr<vibrator::HalController>>::unsupported());
+ vibrator::HalResult<std::shared_ptr<HalController>>::unsupported());
ASSERT_EQ(0, mConnectCounter);
ASSERT_TRUE(mController->ping().isUnsupported());
ASSERT_TRUE(mController->getCapabilities().isUnsupported());
ASSERT_TRUE(mController->getVibratorIds().isUnsupported());
- ASSERT_TRUE(mController->getVibrator(1).isUnsupported());
- ASSERT_TRUE(mController->prepareSynced(ids).isUnsupported());
+ ASSERT_TRUE(mController->getVibrator(VIBRATOR_ID).isUnsupported());
+ ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isUnsupported());
ASSERT_TRUE(mController->triggerSynced([]() {}).isUnsupported());
ASSERT_TRUE(mController->cancelSynced().isUnsupported());
@@ -162,20 +161,18 @@
}
TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultResetsHalConnection) {
- std::vector<int32_t> ids;
- setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::failed("message"),
+ setHalExpectations(MAX_ATTEMPTS, vibrator::HalResult<void>::failed("message"),
vibrator::HalResult<vibrator::ManagerCapabilities>::failed("message"),
vibrator::HalResult<std::vector<int32_t>>::failed("message"),
- vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::failed(
- "message"));
+ vibrator::HalResult<std::shared_ptr<HalController>>::failed("message"));
ASSERT_EQ(0, mConnectCounter);
ASSERT_TRUE(mController->ping().isFailed());
ASSERT_TRUE(mController->getCapabilities().isFailed());
ASSERT_TRUE(mController->getVibratorIds().isFailed());
- ASSERT_TRUE(mController->getVibrator(1).isFailed());
- ASSERT_TRUE(mController->prepareSynced(ids).isFailed());
+ ASSERT_TRUE(mController->getVibrator(VIBRATOR_ID).isFailed());
+ ASSERT_TRUE(mController->prepareSynced(VIBRATOR_IDS).isFailed());
ASSERT_TRUE(mController->triggerSynced([]() {}).isFailed());
ASSERT_TRUE(mController->cancelSynced().isFailed());