Merge "Some fixes to power manager lib code"
diff --git a/.gitignore b/.gitignore
index 0d20b64..685e379 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
+*.iml
*.pyc
+.idea/
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 436c809..a686dfb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -21,6 +21,7 @@
services/inputflinger/
services/powermanager/
services/surfaceflinger/
+ services/vibratorservice/
services/vr/
vulkan/
diff --git a/cmds/bugreport/bugreport.cpp b/cmds/bugreport/bugreport.cpp
index 840ae47..e5c52d8 100644
--- a/cmds/bugreport/bugreport.cpp
+++ b/cmds/bugreport/bugreport.cpp
@@ -27,12 +27,20 @@
// 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 main(int argc, char* /*argv*/[]) {
fprintf(stderr, "=============================================================================\n");
- fprintf(stderr, "WARNING: flat bugreports are deprecated, use adb bugreport <zip_file> instead\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");
diff --git a/cmds/bugreportz/main.cpp b/cmds/bugreportz/main.cpp
index 40346be..1d48e08 100644
--- a/cmds/bugreportz/main.cpp
+++ b/cmds/bugreportz/main.cpp
@@ -30,7 +30,7 @@
static void show_usage() {
fprintf(stderr,
- "usage: bugreportz [-h | -v]\n"
+ "usage: bugreportz [-hpv]\n"
" -h: to display this help message\n"
" -p: display progress\n"
" -v: to display the version\n"
@@ -64,6 +64,12 @@
}
}
+ // We don't support any non-option arguments.
+ if (optind != argc) {
+ show_usage();
+ return EXIT_FAILURE;
+ }
+
// TODO: code below was copy-and-pasted from bugreport.cpp (except by the
// timeout value);
// should be reused instead.
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index acca11a..ead491e 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -110,29 +110,20 @@
],
required: [
"atrace",
- "df",
- "getprop",
"ip",
"iptables",
- "ip6tables",
- "kill",
"librank",
"logcat",
"lpdump",
"lpdumpd",
- "lsmod",
- "lsof",
- "netstat",
- "printenv",
"procrank",
"screencap",
"showmap",
"ss",
"storaged",
- "top",
- "uptime",
+ "toolbox",
+ "toybox",
"vdc",
- "vril-dump",
],
init_rc: ["dumpstate.rc"],
}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 9ba4819..581d3de 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -209,6 +209,10 @@
return fd;
}
+static int OpenForWrite(std::string path) {
+ return Open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+}
static int OpenForRead(std::string path) {
return Open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
@@ -274,6 +278,27 @@
return version_code;
}
+static bool PathExists(const std::string& path) {
+ struct stat sb;
+ return stat(path.c_str(), &sb) == 0;
+}
+
+static bool CopyFileToFile(const std::string& input_file, const std::string& output_file) {
+ if (input_file == output_file) {
+ MYLOGD("Skipping copying bugreport file since the destination is the same (%s)\n",
+ output_file.c_str());
+ return false;
+ }
+ else if (PathExists(output_file)) {
+ MYLOGD("Cannot overwrite an existing file (%s)\n", output_file.c_str());
+ return false;
+ }
+
+ MYLOGD("Going to copy bugreport file (%s) to %s\n", input_file.c_str(), output_file.c_str());
+ android::base::unique_fd out_fd(OpenForWrite(output_file));
+ return CopyFileToFd(input_file, out_fd.get());
+}
+
} // namespace
} // namespace os
} // namespace android
@@ -305,8 +330,9 @@
/*
* Returns a vector of dump fds under |dir_path| with a given |file_prefix|.
- * The returned vector is sorted by the mtimes of the dumps. If |limit_by_mtime|
- * is set, the vector only contains files that were written in the last 30 minutes.
+ * The returned vector is sorted by the mtimes of the dumps with descending
+ * order. If |limit_by_mtime| is set, the vector only contains files that
+ * were written in the last 30 minutes.
*/
static std::vector<DumpData> GetDumpFds(const std::string& dir_path,
const std::string& file_prefix,
@@ -353,6 +379,10 @@
dump_data.emplace_back(DumpData{abs_path, std::move(fd), st.st_mtime});
}
+ if (!dump_data.empty()) {
+ std::sort(dump_data.begin(), dump_data.end(),
+ [](const auto& a, const auto& b) { return a.mtime > b.mtime; });
+ }
return dump_data;
}
@@ -1362,6 +1392,46 @@
printf("\n");
}
+static void DumpstateLimitedOnly() {
+ // Trimmed-down version of dumpstate to only include a whitelisted
+ // set of logs (system log, event log, and system server / system app
+ // crashes, and networking logs). See b/136273873 and b/138459828
+ // for context.
+ DurationReporter duration_reporter("DUMPSTATE");
+ unsigned long timeout_ms;
+ // calculate timeout
+ timeout_ms = logcat_timeout({"main", "system", "crash"});
+ RunCommand("SYSTEM LOG",
+ {"logcat", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+ CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+ timeout_ms = logcat_timeout({"events"});
+ RunCommand(
+ "EVENT LOG",
+ {"logcat", "-b", "events", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+ CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+
+ printf("========================================================\n");
+ printf("== Networking Service\n");
+ printf("========================================================\n");
+
+ RunDumpsys("DUMPSYS NETWORK_SERVICE_LIMITED", {"wifi", "-a"},
+ CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10));
+
+ printf("========================================================\n");
+ printf("== Dropbox crashes\n");
+ printf("========================================================\n");
+
+ RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"});
+ RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"});
+
+ printf("========================================================\n");
+ printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(),
+ ds.progress_->GetMax(), ds.progress_->GetInitialMax());
+ printf("========================================================\n");
+ printf("== dumpstate: done (id %d)\n", ds.id_);
+ printf("========================================================\n");
+}
+
// Dumps various things. Returns early with status USER_CONSENT_DENIED if user denies consent
// via the consent they are shown. Ignores other errors that occur while running various
// commands. The consent checking is currently done around long running tasks, which happen to
@@ -2047,11 +2117,12 @@
static void ShowUsage() {
fprintf(stderr,
- "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-d] [-p] "
- "[-z] [-s] [-S] [-q] [-P] [-R] [-V version]\n"
+ "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-d] [-p] "
+ "[-z] [-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n"
" -h: display this help message\n"
" -b: play sound file instead of vibrate, at beginning of job\n"
" -e: play sound file instead of vibrate, at end of job\n"
+ " -o: write to custom directory (only in limited mode)\n"
" -d: append date to filename\n"
" -p: capture screenshot to filename.png\n"
" -z: generate zipped file\n"
@@ -2061,6 +2132,7 @@
" -P: send broadcast when started and do progress updates\n"
" -R: take bugreport in remote mode (requires -z and -d, shouldn't be used with -P)\n"
" -w: start binder service and make it wait for a call to startBugreport\n"
+ " -L: output limited information that is safe for submission in feedback reports\n"
" -v: prints the dumpstate header and exit\n");
}
@@ -2221,6 +2293,13 @@
do_text_file = false;
}
}
+
+ std::string final_path = ds.path_;
+ if (ds.options_->OutputToCustomFile()) {
+ final_path = ds.GetPath(ds.options_->out_dir, ".zip");
+ android::os::CopyFileToFile(ds.path_, final_path);
+ }
+
if (ds.options_->use_control_socket) {
if (do_text_file) {
dprintf(ds.control_socket_fd_,
@@ -2228,7 +2307,7 @@
"for more details\n",
ds.log_path_.c_str());
} else {
- dprintf(ds.control_socket_fd_, "OK:%s\n", ds.path_.c_str());
+ dprintf(ds.control_socket_fd_, "OK:%s\n", final_path.c_str());
}
}
}
@@ -2306,13 +2385,12 @@
"do_zip_file: %d do_vibrate: %d use_socket: %d use_control_socket: %d do_screenshot: %d "
"is_remote_mode: %d show_header_only: %d do_start_service: %d telephony_only: %d "
"wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s dumpstate_hal_mode: %s "
- "args: %s\n",
+ "limited_only: %d args: %s\n",
options.do_zip_file, options.do_vibrate, options.use_socket, options.use_control_socket,
options.do_screenshot, options.is_remote_mode, options.show_header_only,
- options.do_start_service,
- options.telephony_only, options.wifi_only, options.do_progress_updates,
- options.bugreport_fd.get(), options.bugreport_mode.c_str(),
- toString(options.dumpstate_hal_mode).c_str(), options.args.c_str());
+ options.do_start_service, options.telephony_only, options.wifi_only,
+ options.do_progress_updates, options.bugreport_fd.get(), options.bugreport_mode.c_str(),
+ toString(options.dumpstate_hal_mode).c_str(), options.limited_only, options.args.c_str());
}
void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
@@ -2334,11 +2412,12 @@
Dumpstate::RunStatus Dumpstate::DumpOptions::Initialize(int argc, char* argv[]) {
RunStatus status = RunStatus::OK;
int c;
- while ((c = getopt(argc, argv, "dho:svqzpPBRSV:w")) != -1) {
+ while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) {
switch (c) {
// clang-format off
case 'd': do_add_date = true; break;
case 'z': do_zip_file = true; break;
+ case 'o': out_dir = optarg; break;
case 's': use_socket = true; break;
case 'S': use_control_socket = true; break;
case 'v': show_header_only = true; break;
@@ -2346,6 +2425,7 @@
case 'p': do_screenshot = true; break;
case 'P': do_progress_updates = true; break;
case 'R': is_remote_mode = true; break;
+ case 'L': limited_only = true; break;
case 'V': break; // compatibility no-op
case 'w':
// This was already processed
@@ -2459,8 +2539,8 @@
* If zipping, a bunch of other files and dumps also get added to the zip archive. The log file also
* gets added to the archive.
*
- * Bugreports are first generated in a local directory and later copied to the caller's fd if
- * supplied.
+ * Bugreports are first generated in a local directory and later copied to the caller's fd
+ * or directory if supplied.
*/
Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
const std::string& calling_package) {
@@ -2630,6 +2710,7 @@
// duration is logged into MYLOG instead.
PrintHeader();
+ // TODO(b/158737089) reduce code repetition in if branches
if (options_->telephony_only) {
MaybeTakeEarlyScreenshot();
onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
@@ -2641,6 +2722,11 @@
onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
MaybeCheckUserConsent(calling_uid, calling_package);
DumpstateWifiOnly();
+ } else if (options_->limited_only) {
+ MaybeTakeEarlyScreenshot();
+ onUiIntensiveBugreportDumpsFinished(calling_uid, calling_package);
+ MaybeCheckUserConsent(calling_uid, calling_package);
+ DumpstateLimitedOnly();
} else {
// Invoke critical dumpsys first to preserve system state, before doing anything else.
RunDumpsysCritical();
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 28d8936..0d25d30 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -377,6 +377,8 @@
bool do_start_service = false;
bool telephony_only = false;
bool wifi_only = false;
+ // Trimmed-down version of dumpstate to only include whitelisted logs.
+ bool limited_only = false;
// Whether progress updates should be published.
bool do_progress_updates = false;
// The mode we'll use when calling IDumpstateDevice::dumpstateBoard.
@@ -384,10 +386,12 @@
// The HAL is actually an API surface that can be validated, while the AIDL is not (@hide).
::android::hardware::dumpstate::V1_1::DumpstateMode dumpstate_hal_mode =
::android::hardware::dumpstate::V1_1::DumpstateMode::DEFAULT;
- // File descriptor to output zip file.
+ // File descriptor to output zip file. Takes precedence over out_dir.
android::base::unique_fd bugreport_fd;
// File descriptor to screenshot file.
android::base::unique_fd screenshot_fd;
+ // Custom output directory.
+ std::string out_dir;
// Bugreport mode of the bugreport.
std::string bugreport_mode;
// Command-line arguments as string
@@ -413,6 +417,12 @@
// specified, it is preferred. If not bugreport is written to /bugreports.
return !use_socket;
}
+
+ /* Returns if options specified require writing to custom file location */
+ bool OutputToCustomFile() {
+ // Custom location is only honored in limited mode.
+ return limited_only && !out_dir.empty() && bugreport_fd.get() == -1;
+ }
};
// TODO: initialize fields on constructor
diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp
index 68d3733..ec89c0d 100644
--- a/cmds/dumpstate/main.cpp
+++ b/cmds/dumpstate/main.cpp
@@ -30,7 +30,7 @@
bool do_wait = false;
int c;
// Keep flags in sync with Dumpstate::DumpOptions::Initialize.
- while ((c = getopt(argc, argv, "wdho:svqzpPBRSV:")) != -1 && !do_wait) {
+ while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1 && !do_wait) {
switch (c) {
case 'w':
do_wait = true;
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 9871a3b..c7df1bb 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -172,6 +172,7 @@
EXPECT_FALSE(options_.do_add_date);
EXPECT_FALSE(options_.do_zip_file);
+ EXPECT_EQ("", options_.out_dir);
EXPECT_FALSE(options_.use_socket);
EXPECT_FALSE(options_.use_control_socket);
EXPECT_FALSE(options_.show_header_only);
@@ -179,6 +180,7 @@
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
+ EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -206,6 +208,7 @@
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -231,6 +234,7 @@
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
+ EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -249,6 +253,7 @@
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
EXPECT_FALSE(options_.do_start_service);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
@@ -266,6 +271,7 @@
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
@@ -282,6 +288,7 @@
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeWearBugReport) {
@@ -299,6 +306,7 @@
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
@@ -316,6 +324,7 @@
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
@@ -333,6 +342,39 @@
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.limited_only);
+}
+
+TEST_F(DumpOptionsTest, InitializeLimitedOnlyBugreport) {
+ // clang-format off
+ char* argv[] = {
+ const_cast<char*>("dumpstatez"),
+ const_cast<char*>("-S"),
+ const_cast<char*>("-d"),
+ const_cast<char*>("-z"),
+ const_cast<char*>("-q"),
+ const_cast<char*>("-L"),
+ const_cast<char*>("-o abc")
+ };
+ // clang-format on
+
+ Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
+
+ EXPECT_EQ(status, Dumpstate::RunStatus::OK);
+ EXPECT_TRUE(options_.do_add_date);
+ EXPECT_TRUE(options_.do_zip_file);
+ EXPECT_TRUE(options_.use_control_socket);
+ EXPECT_FALSE(options_.do_vibrate);
+ EXPECT_TRUE(options_.limited_only);
+ EXPECT_EQ(" abc", std::string(options_.out_dir));
+
+ // Other options retain default values
+ EXPECT_FALSE(options_.show_header_only);
+ EXPECT_FALSE(options_.do_screenshot);
+ EXPECT_FALSE(options_.do_progress_updates);
+ EXPECT_FALSE(options_.is_remote_mode);
+ EXPECT_FALSE(options_.use_socket);
+ EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
TEST_F(DumpOptionsTest, InitializeDefaultBugReport) {
@@ -361,6 +403,7 @@
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.use_socket);
EXPECT_FALSE(options_.wifi_only);
+ EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializePartial1) {
@@ -390,6 +433,7 @@
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
+ EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -419,6 +463,7 @@
EXPECT_FALSE(options_.do_zip_file);
EXPECT_FALSE(options_.use_socket);
EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 6cc66ec..e7014c8 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -1075,6 +1075,7 @@
res = error(rc, "Failed copying " + from_ce + " to " + to_ce);
return res;
}
+ delete_dir_contents_and_dir(from_ce, true /* ignore_if_missing */);
}
if (needs_de_rollback) {
@@ -1091,6 +1092,7 @@
res = error(rc, "Failed copying " + from_de + " to " + to_de);
return res;
}
+ delete_dir_contents_and_dir(from_de, true /* ignore_if_missing */);
}
// Finally, restore the SELinux label on the app data.
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 2631b14..f195399 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -125,6 +125,8 @@
* Note that {@link ADataSpace} only exposes a few values. This may return
* {@link ADATASPACE_UNKNOWN}, even for Named ColorSpaces, if they have no
* corresponding ADataSpace.
+ *
+ * Available since API level 30.
*/
int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) __INTRODUCED_IN(30);
@@ -189,6 +191,8 @@
/**
* User-defined function for writing the output of compression.
*
+ * Available since API level 30.
+ *
* @param userContext Pointer to user-defined data passed to
* {@link AndroidBitmap_compress}.
* @param data Compressed data of |size| bytes to write.
@@ -202,6 +206,8 @@
/**
* Compress |pixels| as described by |info|.
*
+ * Available since API level 30.
+ *
* @param info Description of the pixels to compress.
* @param dataspace {@link ADataSpace} describing the color space of the
* pixels.
@@ -234,6 +240,8 @@
*
* Client must not modify it while a Bitmap is wrapping it.
*
+ * Available since API level 30.
+ *
* @param bitmap Handle to an android.graphics.Bitmap.
* @param outBuffer On success, is set to a pointer to the
* {@link AHardwareBuffer} associated with bitmap. This acquires
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index c1c4a72..bdf11e4 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -129,9 +129,12 @@
*
* This api is thread-safe. Any thread is allowed to register a new refresh
* rate callback for the choreographer instance.
+ *
+ * Available since API level 30.
*/
void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
- AChoreographer_refreshRateCallback, void* data);
+ AChoreographer_refreshRateCallback, void* data)
+ __INTRODUCED_IN(30);
/**
* Unregisters a callback to be run when the display refresh rate changes, along
@@ -144,9 +147,12 @@
* callback and associated data pointer are unregistered, then there is a
* guarantee that when the unregistration completes that that callback will not
* be run with the data pointer passed.
+ *
+ * Available since API level 30.
*/
void AChoreographer_unregisterRefreshRateCallback(AChoreographer* choreographer,
- AChoreographer_refreshRateCallback, void* data);
+ AChoreographer_refreshRateCallback, void* data)
+ __INTRODUCED_IN(30);
#endif /* __ANDROID_API__ >= 30 */
__END_DECLS
diff --git a/include/android/configuration.h b/include/android/configuration.h
index 05f4340..ccf3e59 100644
--- a/include/android/configuration.h
+++ b/include/android/configuration.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -58,13 +58,13 @@
ACONFIGURATION_ORIENTATION_ANY = 0x0000,
/**
* Orientation: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#OrientationQualifier">port</a>
+ * <a href="/guide/topics/resources/providing-resources.html#OrientationQualifier">port</a>
* resource qualifier.
*/
ACONFIGURATION_ORIENTATION_PORT = 0x0001,
/**
* Orientation: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#OrientationQualifier">land</a>
+ * <a href="/guide/topics/resources/providing-resources.html#OrientationQualifier">land</a>
* resource qualifier.
*/
ACONFIGURATION_ORIENTATION_LAND = 0x0002,
@@ -75,7 +75,7 @@
ACONFIGURATION_TOUCHSCREEN_ANY = 0x0000,
/**
* Touchscreen: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#TouchscreenQualifier">notouch</a>
+ * <a href="/guide/topics/resources/providing-resources.html#TouchscreenQualifier">notouch</a>
* resource qualifier.
*/
ACONFIGURATION_TOUCHSCREEN_NOTOUCH = 0x0001,
@@ -83,7 +83,7 @@
ACONFIGURATION_TOUCHSCREEN_STYLUS = 0x0002,
/**
* Touchscreen: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#TouchscreenQualifier">finger</a>
+ * <a href="/guide/topics/resources/providing-resources.html#TouchscreenQualifier">finger</a>
* resource qualifier.
*/
ACONFIGURATION_TOUCHSCREEN_FINGER = 0x0003,
@@ -92,43 +92,43 @@
ACONFIGURATION_DENSITY_DEFAULT = 0,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">ldpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">ldpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_LOW = 120,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">mdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">mdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_MEDIUM = 160,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">tvdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">tvdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_TV = 213,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">hdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">hdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_HIGH = 240,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">xhdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">xhdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_XHIGH = 320,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">xxhdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">xxhdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_XXHIGH = 480,
/**
* Density: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">xxxhdpi</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">xxxhdpi</a>
* resource qualifier.
*/
ACONFIGURATION_DENSITY_XXXHIGH = 640,
@@ -141,19 +141,19 @@
ACONFIGURATION_KEYBOARD_ANY = 0x0000,
/**
* Keyboard: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ImeQualifier">nokeys</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ImeQualifier">nokeys</a>
* resource qualifier.
*/
ACONFIGURATION_KEYBOARD_NOKEYS = 0x0001,
/**
* Keyboard: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ImeQualifier">qwerty</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ImeQualifier">qwerty</a>
* resource qualifier.
*/
ACONFIGURATION_KEYBOARD_QWERTY = 0x0002,
/**
* Keyboard: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ImeQualifier">12key</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ImeQualifier">12key</a>
* resource qualifier.
*/
ACONFIGURATION_KEYBOARD_12KEY = 0x0003,
@@ -162,25 +162,25 @@
ACONFIGURATION_NAVIGATION_ANY = 0x0000,
/**
* Navigation: value corresponding to the
- * <a href="@@dacRoot/guide/topics/resources/providing-resources.html#NavigationQualifier">nonav</a>
+ * <a href="@/guide/topics/resources/providing-resources.html#NavigationQualifier">nonav</a>
* resource qualifier.
*/
ACONFIGURATION_NAVIGATION_NONAV = 0x0001,
/**
* Navigation: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavigationQualifier">dpad</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavigationQualifier">dpad</a>
* resource qualifier.
*/
ACONFIGURATION_NAVIGATION_DPAD = 0x0002,
/**
* Navigation: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavigationQualifier">trackball</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavigationQualifier">trackball</a>
* resource qualifier.
*/
ACONFIGURATION_NAVIGATION_TRACKBALL = 0x0003,
/**
* Navigation: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavigationQualifier">wheel</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavigationQualifier">wheel</a>
* resource qualifier.
*/
ACONFIGURATION_NAVIGATION_WHEEL = 0x0004,
@@ -189,19 +189,19 @@
ACONFIGURATION_KEYSHIDDEN_ANY = 0x0000,
/**
* Keyboard availability: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keysexposed</a>
+ * <a href="/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keysexposed</a>
* resource qualifier.
*/
ACONFIGURATION_KEYSHIDDEN_NO = 0x0001,
/**
* Keyboard availability: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyshidden</a>
+ * <a href="/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyshidden</a>
* resource qualifier.
*/
ACONFIGURATION_KEYSHIDDEN_YES = 0x0002,
/**
* Keyboard availability: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyssoft</a>
+ * <a href="/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyssoft</a>
* resource qualifier.
*/
ACONFIGURATION_KEYSHIDDEN_SOFT = 0x0003,
@@ -210,13 +210,13 @@
ACONFIGURATION_NAVHIDDEN_ANY = 0x0000,
/**
* Navigation availability: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavAvailQualifier">navexposed</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavAvailQualifier">navexposed</a>
* resource qualifier.
*/
ACONFIGURATION_NAVHIDDEN_NO = 0x0001,
/**
* Navigation availability: value corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavAvailQualifier">navhidden</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavAvailQualifier">navhidden</a>
* resource qualifier.
*/
ACONFIGURATION_NAVHIDDEN_YES = 0x0002,
@@ -226,28 +226,28 @@
/**
* Screen size: value indicating the screen is at least
* approximately 320x426 dp units, corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">small</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">small</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENSIZE_SMALL = 0x01,
/**
* Screen size: value indicating the screen is at least
* approximately 320x470 dp units, corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">normal</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">normal</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENSIZE_NORMAL = 0x02,
/**
* Screen size: value indicating the screen is at least
* approximately 480x640 dp units, corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">large</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">large</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENSIZE_LARGE = 0x03,
/**
* Screen size: value indicating the screen is at least
* approximately 720x960 dp units, corresponding to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">xlarge</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">xlarge</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENSIZE_XLARGE = 0x04,
@@ -256,13 +256,13 @@
ACONFIGURATION_SCREENLONG_ANY = 0x00,
/**
* Screen layout: value that corresponds to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenAspectQualifier">notlong</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenAspectQualifier">notlong</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENLONG_NO = 0x1,
/**
* Screen layout: value that corresponds to the
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a>
* resource qualifier.
*/
ACONFIGURATION_SCREENLONG_YES = 0x2,
@@ -275,13 +275,13 @@
ACONFIGURATION_WIDE_COLOR_GAMUT_ANY = 0x00,
/**
* Wide color gamut: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">no
+ * <a href="/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">no
* nowidecg</a> resource qualifier specified.
*/
ACONFIGURATION_WIDE_COLOR_GAMUT_NO = 0x1,
/**
* Wide color gamut: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">
+ * <a href="/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">
* widecg</a> resource qualifier specified.
*/
ACONFIGURATION_WIDE_COLOR_GAMUT_YES = 0x2,
@@ -290,13 +290,13 @@
ACONFIGURATION_HDR_ANY = 0x00,
/**
* HDR: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#HDRQualifier">
+ * <a href="/guide/topics/resources/providing-resources.html#HDRQualifier">
* lowdr</a> resource qualifier specified.
*/
ACONFIGURATION_HDR_NO = 0x1,
/**
* HDR: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#HDRQualifier">
+ * <a href="/guide/topics/resources/providing-resources.html#HDRQualifier">
* highdr</a> resource qualifier specified.
*/
ACONFIGURATION_HDR_YES = 0x2,
@@ -305,38 +305,38 @@
ACONFIGURATION_UI_MODE_TYPE_ANY = 0x00,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">no
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">no
* UI mode type</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_NORMAL = 0x01,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">desk</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">desk</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">car</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">car</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">television</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">television</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_TELEVISION = 0x04,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">appliance</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">appliance</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_APPLIANCE = 0x05,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">watch</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">watch</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_WATCH = 0x06,
/**
* UI mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">vr</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">vr</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_TYPE_VR_HEADSET = 0x07,
@@ -344,12 +344,12 @@
ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
/**
* UI night mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NightQualifier">notnight</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#NightQualifier">notnight</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
/**
* UI night mode: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NightQualifier">night</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#NightQualifier">night</a> resource qualifier specified.
*/
ACONFIGURATION_UI_MODE_NIGHT_YES = 0x2,
@@ -366,78 +366,78 @@
ACONFIGURATION_LAYOUTDIR_ANY = 0x00,
/**
* Layout direction: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">ldltr</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">ldltr</a> resource qualifier specified.
*/
ACONFIGURATION_LAYOUTDIR_LTR = 0x01,
/**
* Layout direction: value that corresponds to
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">ldrtl</a> resource qualifier specified.
+ * <a href="/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">ldrtl</a> resource qualifier specified.
*/
ACONFIGURATION_LAYOUTDIR_RTL = 0x02,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#MccQualifier">mcc</a>
+ * <a href="/guide/topics/resources/providing-resources.html#MccQualifier">mcc</a>
* configuration.
*/
ACONFIGURATION_MCC = 0x0001,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#MccQualifier">mnc</a>
+ * <a href="/guide/topics/resources/providing-resources.html#MccQualifier">mnc</a>
* configuration.
*/
ACONFIGURATION_MNC = 0x0002,
/**
* Bit mask for
- * <a href="{@docRoot}guide/topics/resources/providing-resources.html#LocaleQualifier">locale</a>
+ * <a href="/guide/topics/resources/providing-resources.html#LocaleQualifier">locale</a>
* configuration.
*/
ACONFIGURATION_LOCALE = 0x0004,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#TouchscreenQualifier">touchscreen</a>
+ * <a href="/guide/topics/resources/providing-resources.html#TouchscreenQualifier">touchscreen</a>
* configuration.
*/
ACONFIGURATION_TOUCHSCREEN = 0x0008,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ImeQualifier">keyboard</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ImeQualifier">keyboard</a>
* configuration.
*/
ACONFIGURATION_KEYBOARD = 0x0010,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyboardHidden</a>
+ * <a href="/guide/topics/resources/providing-resources.html#KeyboardAvailQualifier">keyboardHidden</a>
* configuration.
*/
ACONFIGURATION_KEYBOARD_HIDDEN = 0x0020,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#NavigationQualifier">navigation</a>
+ * <a href="/guide/topics/resources/providing-resources.html#NavigationQualifier">navigation</a>
* configuration.
*/
ACONFIGURATION_NAVIGATION = 0x0040,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#OrientationQualifier">orientation</a>
+ * <a href="/guide/topics/resources/providing-resources.html#OrientationQualifier">orientation</a>
* configuration.
*/
ACONFIGURATION_ORIENTATION = 0x0080,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#DensityQualifier">density</a>
+ * <a href="/guide/topics/resources/providing-resources.html#DensityQualifier">density</a>
* configuration.
*/
ACONFIGURATION_DENSITY = 0x0100,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">screen size</a>
+ * <a href="/guide/topics/resources/providing-resources.html#ScreenSizeQualifier">screen size</a>
* configuration.
*/
ACONFIGURATION_SCREEN_SIZE = 0x0200,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#VersionQualifier">platform version</a>
+ * <a href="/guide/topics/resources/providing-resources.html#VersionQualifier">platform version</a>
* configuration.
*/
ACONFIGURATION_VERSION = 0x0400,
@@ -447,27 +447,27 @@
ACONFIGURATION_SCREEN_LAYOUT = 0x0800,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#UiModeQualifier">ui mode</a>
+ * <a href="/guide/topics/resources/providing-resources.html#UiModeQualifier">ui mode</a>
* configuration.
*/
ACONFIGURATION_UI_MODE = 0x1000,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest screen width</a>
+ * <a href="/guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest screen width</a>
* configuration.
*/
ACONFIGURATION_SMALLEST_SCREEN_SIZE = 0x2000,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">layout direction</a>
+ * <a href="/guide/topics/resources/providing-resources.html#LayoutDirectionQualifier">layout direction</a>
* configuration.
*/
ACONFIGURATION_LAYOUTDIR = 0x4000,
ACONFIGURATION_SCREEN_ROUND = 0x8000,
/**
* Bit mask for
- * <a href="@dacRoot/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">wide color gamut</a>
- * and <a href="@dacRoot/guide/topics/resources/providing-resources.html#HDRQualifier">HDR</a> configurations.
+ * <a href="/guide/topics/resources/providing-resources.html#WideColorGamutQualifier">wide color gamut</a>
+ * and <a href="/guide/topics/resources/providing-resources.html#HDRQualifier">HDR</a> configurations.
*/
ACONFIGURATION_COLOR_MODE = 0x10000,
/**
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index 3a87da0..d7e6e41 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -133,6 +133,8 @@
/**
* Create a new {@link AImageDecoder} from an {@link AAsset}.
*
+ * Available since API level 30.
+ *
* @param asset {@link AAsset} containing encoded image data. Client is still
* responsible for calling {@link AAsset_close} on it, which may be
* done after deleting the returned {@link AImageDecoder}.
@@ -162,6 +164,8 @@
/**
* Create a new {@link AImageDecoder} from a file descriptor.
*
+ * Available since API level 30.
+ *
* @param fd Seekable, readable, open file descriptor for encoded data.
* Client is still responsible for closing it, which may be done
* after deleting the returned {@link AImageDecoder}.
@@ -190,6 +194,8 @@
/**
* Create a new AImageDecoder from a buffer.
*
+ * Available since API level 30.
+ *
* @param buffer Pointer to encoded data. Must be valid for the entire time
* the {@link AImageDecoder} is used.
* @param length Byte length of buffer.
@@ -217,12 +223,16 @@
/**
* Delete the AImageDecoder.
+ *
+ * Available since API level 30.
*/
void AImageDecoder_delete(AImageDecoder* decoder) __INTRODUCED_IN(30);
/**
* Choose the desired output format.
*
+ * Available since API level 30.
+ *
* @param format {@link AndroidBitmapFormat} to use for the output.
* @return {@link ANDROID_IMAGE_DECODER_SUCCESS} on success or a value
* indicating the reason for the failure. On failure, the
@@ -247,6 +257,8 @@
* Pass true to this method to leave them unpremultiplied. This has no effect on an
* opaque image.
*
+ * Available since API level 30.
+ *
* @param unpremultipliedRequired Pass true to leave the pixels unpremultiplied.
* @return {@link ANDROID_IMAGE_DECODER_SUCCESS} on success or a value
* indicating the reason for the failure.
@@ -267,6 +279,8 @@
* Ignored by {@link ANDROID_BITMAP_FORMAT_A_8}, which does not support
* an {@link ADataSpace}.
*
+ * Available since API level 30.
+ *
* @param dataspace The {@link ADataSpace} to decode into. An ADataSpace
* specifies how to interpret the colors. By default,
* AImageDecoder will decode into the ADataSpace specified by
@@ -292,6 +306,8 @@
* specified by width and height, and the output image will be the size of the
* crop rect.
*
+ * Available since API level 30.
+ *
* @param width Width of the output (prior to cropping).
* This will affect future calls to
* {@link AImageDecoder_getMinimumStride}, which will now return
@@ -319,6 +335,8 @@
* others. This computes the most efficient target size to use to reach a
* particular sampleSize.
*
+ * Available since API level 30.
+ *
* @param sampleSize A subsampling rate of the original image. Must be greater
* than or equal to 1. A sampleSize of 2 means to skip every
* other pixel/line, resulting in a width and height that are
@@ -344,6 +362,8 @@
* the specified {@link ARect}. Clients will only need to allocate enough memory
* for the cropped ARect.
*
+ * Available since API level 30.
+ *
* @param crop Rectangle describing a crop of the decode. It must be contained inside of
* the (possibly scaled, by {@link AImageDecoder_setTargetSize})
* image dimensions. This will affect future calls to
@@ -376,6 +396,8 @@
*
* This is owned by the {@link AImageDecoder} and will be destroyed when the
* AImageDecoder is destroyed via {@link AImageDecoder_delete}.
+ *
+ * Available since API level 30.
*/
const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(
const AImageDecoder*) __INTRODUCED_IN(30);
@@ -385,6 +407,8 @@
* pixel width of the output, unless {@link AImageDecoder_setTargetSize} is
* used to choose a different size or {@link AImageDecoder_setCrop} is used to
* set a crop rect.
+ *
+ * Available since API level 30.
*/
int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
@@ -393,12 +417,16 @@
* pixel height of the output, unless {@link AImageDecoder_setTargetSize} is
* used to choose a different size or {@link AImageDecoder_setCrop} is used to
* set a crop rect.
+ *
+ * Available since API level 30.
*/
int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
/**
* Report the mimeType of the encoded image.
*
+ * Available since API level 30.
+ *
* @return a string literal describing the mime type.
*/
const char* AImageDecoderHeaderInfo_getMimeType(
@@ -409,6 +437,8 @@
* by default. {@link AImageDecoder} will try to choose one that is sensible
* for the image and the system. Note that this does not indicate the
* encoded format of the image.
+ *
+ * Available since API level 30.
*/
int32_t AImageDecoderHeaderInfo_getAndroidBitmapFormat(
const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
@@ -419,6 +449,8 @@
* {@link ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE}. If the image may contain alpha,
* this returns {@link ANDROID_BITMAP_FLAGS_ALPHA_PREMUL}, because
* {@link AImageDecoder_decodeImage} will premultiply pixels by default.
+ *
+ * Available since API level 30.
*/
int AImageDecoderHeaderInfo_getAlphaFlags(
const AImageDecoderHeaderInfo*) __INTRODUCED_IN(30);
@@ -429,6 +461,8 @@
* By default, {@link AImageDecoder_decodeImage} will not do any color
* conversion.
*
+ * Available since API level 30.
+ *
* @return The {@link ADataSpace} representing the way the colors
* are encoded (or {@link ADATASPACE_UNKNOWN} if there is not a
* corresponding ADataSpace). This specifies how to interpret the colors
@@ -452,12 +486,16 @@
*
* If the output is scaled (via {@link AImageDecoder_setTargetSize}) and/or
* cropped (via {@link AImageDecoder_setCrop}), this takes those into account.
+ *
+ * Available since API level 30.
*/
size_t AImageDecoder_getMinimumStride(AImageDecoder*) __INTRODUCED_IN(30);
/**
* Decode the image into pixels, using the settings of the {@link AImageDecoder}.
*
+ * Available since API level 30.
+ *
* @param decoder Opaque object representing the decoder.
* @param pixels On success, will be filled with the result
* of the decode. Must be large enough to hold |size| bytes.
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 3247fa1..83582d6 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -60,8 +60,6 @@
extern "C" {
#endif
-#if __ANDROID_API__ >= 30
-
enum AThermalStatus {
/** Error in thermal status. */
ATHERMAL_STATUS_ERROR = -1,
@@ -111,36 +109,45 @@
*/
typedef void (*AThermal_StatusCallback)(void *data, AThermalStatus status);
+#if __ANDROID_API__ >= 30
+
/**
* Acquire an instance of the thermal manager. This must be freed using
* {@link AThermal_releaseManager}.
*
+ * Available since API level 30.
+ *
* @return manager instance on success, nullptr on failure.
- */
-AThermalManager* AThermal_acquireManager();
+ */
+AThermalManager* AThermal_acquireManager() __INTRODUCED_IN(30);
/**
* Release the thermal manager pointer acquired via
* {@link AThermal_acquireManager}.
*
- * @param manager The manager to be released.
+ * Available since API level 30.
*
+ * @param manager The manager to be released.
*/
-void AThermal_releaseManager(AThermalManager *manager);
+void AThermal_releaseManager(AThermalManager *manager) __INTRODUCED_IN(30);
/**
* Gets the current thermal status.
*
+ * Available since API level 30.
+ *
* @param manager The manager instance to use to query the thermal status.
* Acquired via {@link AThermal_acquireManager}.
*
* @return current thermal status, ATHERMAL_STATUS_ERROR on failure.
-*/
-AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager);
+ */
+AThermalStatus AThermal_getCurrentThermalStatus(AThermalManager *manager) __INTRODUCED_IN(30);
/**
* Register the thermal status listener for thermal status change.
*
+ * Available since API level 30.
+ *
* @param manager The manager instance to use to register.
* Acquired via {@link AThermal_acquireManager}.
* @param callback The callback function to be called when thermal status updated.
@@ -152,11 +159,13 @@
* EPIPE if communication with the system service has failed.
*/
int AThermal_registerThermalStatusListener(AThermalManager *manager,
- AThermal_StatusCallback callback, void *data);
+ AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
/**
* Unregister the thermal status listener previously resgistered.
*
+ * Available since API level 30.
+ *
* @param manager The manager instance to use to unregister.
* Acquired via {@link AThermal_acquireManager}.
* @param callback The callback function to be called when thermal status updated.
@@ -168,8 +177,7 @@
* EPIPE if communication with the system service has failed.
*/
int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
- AThermal_StatusCallback callback, void *data);
-
+ AThermal_StatusCallback callback, void *data) __INTRODUCED_IN(30);
#endif // __ANDROID_API__ >= 30
diff --git a/include/input/Input.h b/include/input/Input.h
index 9e47318..ac901ae 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -280,9 +280,9 @@
public:
// Used to divide integer space to ensure no conflict among these sources./
enum class Source : int32_t {
- INPUT_READER = 0x0 << SOURCE_SHIFT,
- INPUT_DISPATCHER = 0x1 << SOURCE_SHIFT,
- OTHER = 0x3 << SOURCE_SHIFT, // E.g. app injected events
+ INPUT_READER = static_cast<int32_t>(0x0u << SOURCE_SHIFT),
+ INPUT_DISPATCHER = static_cast<int32_t>(0x1u << SOURCE_SHIFT),
+ OTHER = static_cast<int32_t>(0x3u << SOURCE_SHIFT), // E.g. app injected events
};
IdGenerator(Source source);
@@ -294,7 +294,7 @@
private:
const Source mSource;
- static constexpr int32_t SOURCE_MASK = 0x3 << SOURCE_SHIFT;
+ static constexpr int32_t SOURCE_MASK = static_cast<int32_t>(0x3u << SOURCE_SHIFT);
};
/**
@@ -462,6 +462,8 @@
nsecs_t eventTime);
void initialize(const KeyEvent& from);
+ static const char* actionToString(int32_t action);
+
protected:
int32_t mAction;
int32_t mFlags;
@@ -725,6 +727,8 @@
static const char* getLabel(int32_t axis);
static int32_t getAxisFromLabel(const char* label);
+ static const char* actionToString(int32_t action);
+
protected:
int32_t mAction;
int32_t mActionButton;
diff --git a/include/input/InputApplication.h b/include/input/InputApplication.h
index 7f04611..86de394 100644
--- a/include/input/InputApplication.h
+++ b/include/input/InputApplication.h
@@ -61,6 +61,11 @@
return mInfo.token ? mInfo.dispatchingTimeout : defaultValue;
}
+ inline std::chrono::nanoseconds getDispatchingTimeout(
+ std::chrono::nanoseconds defaultValue) const {
+ return mInfo.token ? std::chrono::nanoseconds(mInfo.dispatchingTimeout) : defaultValue;
+ }
+
inline sp<IBinder> getApplicationToken() const {
return mInfo.token;
}
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index f2841ea..6740855 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -116,7 +116,6 @@
TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW + 34,
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39,
TYPE_NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW + 40,
- TYPE_TRUSTED_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 42,
LAST_SYSTEM_WINDOW = 2999,
};
@@ -171,6 +170,12 @@
bool hasFocus = false;
bool hasWallpaper = false;
bool paused = false;
+ /* This flag is set when the window is of a trusted type that is allowed to silently
+ * overlay other windows for the purpose of implementing the secure views feature.
+ * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+ * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+ */
+ bool trustedOverlay = false;
int32_t ownerPid = -1;
int32_t ownerUid = -1;
int32_t inputFeatures = 0;
@@ -183,20 +188,15 @@
void addTouchableRegion(const Rect& region);
bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
- bool frameContainsPoint(int32_t x, int32_t y) const;
- /* Returns true if the window is of a trusted type that is allowed to silently
- * overlay other windows for the purpose of implementing the secure views feature.
- * Trusted overlays, such as IME windows, can partly obscure other windows without causing
- * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
- */
- bool isTrustedOverlay() const;
+ bool frameContainsPoint(int32_t x, int32_t y) const;
bool supportsSplitTouch() const;
bool overlaps(const InputWindowInfo* other) const;
status_t write(Parcel& output) const;
+
static InputWindowInfo read(const Parcel& from);
};
@@ -231,6 +231,11 @@
return mInfo.token ? mInfo.dispatchingTimeout : defaultValue;
}
+ inline std::chrono::nanoseconds getDispatchingTimeout(
+ std::chrono::nanoseconds defaultValue) const {
+ return mInfo.token ? std::chrono::nanoseconds(mInfo.dispatchingTimeout) : defaultValue;
+ }
+
/**
* Requests that the state of this object be updated to reflect
* the most current available information about the application.
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index f66673f..258a4e3 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -29,6 +29,8 @@
name: "libarect",
host_supported: true,
vendor_available: true,
+ // TODO(b/153609531): remove when no longer needed.
+ native_bridge_supported: true,
export_include_dirs: ["include"],
target: {
windows: {
diff --git a/libs/binder/ActivityManager.cpp b/libs/binder/ActivityManager.cpp
index 5e4c98f..d005058 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -98,6 +98,15 @@
return PROCESS_STATE_UNKNOWN;
}
+bool ActivityManager::setSchedPolicyCgroup(const int32_t tid, const int32_t group)
+{
+ sp<IActivityManager> service = getService();
+ if (service != nullptr) {
+ return service->setSchedPolicyCgroup(tid, group);
+ }
+ return false;
+}
+
status_t ActivityManager::linkToDeath(const sp<IBinder::DeathRecipient>& recipient) {
sp<IActivityManager> service = getService();
if (service != nullptr) {
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 1eb5363..a3021122 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -104,6 +104,17 @@
}
return reply.readInt32();
}
+
+ virtual bool setSchedPolicyCgroup(const int32_t tid, const int32_t group)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
+ data.writeInt32(tid);
+ data.writeInt32(group);
+ remote()->transact(SET_SCHED_POLICY_CGROUP_TRANSACTION, data, &reply);
+ if (reply.readExceptionCode() != 0) return false;
+ return reply.readBool();
+ }
};
// ------------------------------------------------------------------------------------
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index dab7ad5..05f43e3 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -85,8 +85,8 @@
sp<AidlServiceManager> mTheRealServiceManager;
};
-static std::once_flag gSmOnce;
-static sp<IServiceManager> gDefaultServiceManager;
+[[clang::no_destroy]] static std::once_flag gSmOnce;
+[[clang::no_destroy]] static sp<IServiceManager> gDefaultServiceManager;
sp<IServiceManager> defaultServiceManager()
{
@@ -95,6 +95,7 @@
while (sm == nullptr) {
sm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr));
if (sm == nullptr) {
+ ALOGE("Waiting 1s on context object on %s.", ProcessState::self()->getDriverName().c_str());
sleep(1);
}
}
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index f07f82a..166c1d8 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -2643,7 +2643,7 @@
size_t newSize = ((mDataSize+len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
- : continueWrite(newSize);
+ : continueWrite(std::max(newSize, (size_t) 128));
}
status_t Parcel::restartWrite(size_t desired)
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 9aa7651..c232283 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -7,6 +7,9 @@
"name": "binderVendorDoubleLoadTest"
},
{
+ "name": "binderAllocationLimits"
+ },
+ {
"name": "binderDriverInterfaceTest"
},
{
@@ -29,6 +32,17 @@
},
{
"name": "libbinderthreadstateutils_test"
+ },
+ {
+ "name": "CtsOsTestCases",
+ "options": [
+ {
+ "exclude-filter": "android.os.cts.BuildTest#testSdkInt"
+ },
+ {
+ "exclude-filter": "android.os.cts.StrictModeTest#testNonSdkApiUsage"
+ }
+ ]
}
]
}
diff --git a/libs/binder/include/binder/ActivityManager.h b/libs/binder/include/binder/ActivityManager.h
index 9108e31..7043b17 100644
--- a/libs/binder/include/binder/ActivityManager.h
+++ b/libs/binder/include/binder/ActivityManager.h
@@ -77,7 +77,7 @@
void unregisterUidObserver(const sp<IUidObserver>& observer);
bool isUidActive(const uid_t uid, const String16& callingPackage);
int getUidProcessState(const uid_t uid, const String16& callingPackage);
-
+ bool setSchedPolicyCgroup(const int32_t tid, const int32_t group);
status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
diff --git a/libs/binder/include/binder/IActivityManager.h b/libs/binder/include/binder/IActivityManager.h
index e0248f6..fe58a41 100644
--- a/libs/binder/include/binder/IActivityManager.h
+++ b/libs/binder/include/binder/IActivityManager.h
@@ -39,13 +39,15 @@
virtual void unregisterUidObserver(const sp<IUidObserver>& observer) = 0;
virtual bool isUidActive(const uid_t uid, const String16& callingPackage) = 0;
virtual int32_t getUidProcessState(const uid_t uid, const String16& callingPackage) = 0;
+ virtual bool setSchedPolicyCgroup(const int32_t tid, const int32_t group) = 0;
enum {
OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
REGISTER_UID_OBSERVER_TRANSACTION,
UNREGISTER_UID_OBSERVER_TRANSACTION,
IS_UID_ACTIVE_TRANSACTION,
- GET_UID_PROCESS_STATE_TRANSACTION
+ GET_UID_PROCESS_STATE_TRANSACTION,
+ SET_SCHED_POLICY_CGROUP_TRANSACTION
};
};
diff --git a/libs/binder/include/binder/Parcelable.h b/libs/binder/include/binder/Parcelable.h
index a9166e2..c113279 100644
--- a/libs/binder/include/binder/Parcelable.h
+++ b/libs/binder/include/binder/Parcelable.h
@@ -52,6 +52,12 @@
//
// Returns android::OK on success and an appropriate error otherwise.
virtual status_t readFromParcel(const Parcel* parcel) = 0;
+
+ // 'Stable' means this parcelable is guaranteed to be stable for multiple years.
+ // It must be guaranteed by setting stability field in aidl_interface.
+ // WARNING: isStable() is only expected to be overridden by auto-generated code.
+ // Returns true if this parcelable is stable.
+ virtual bool isStable() const { return false; }
}; // class Parcelable
#if defined(__clang__)
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 4560f22..13dbec1 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -26,6 +26,7 @@
#pragma once
+#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h
index 86b75b8..a031e29 100644
--- a/libs/binder/ndk/include_ndk/android/binder_parcel.h
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h
@@ -26,6 +26,7 @@
#pragma once
+#include <stdbool.h>
#include <stddef.h>
#include <sys/cdefs.h>
diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h
index ab9a144..3a55f94 100644
--- a/libs/binder/ndk/include_ndk/android/binder_status.h
+++ b/libs/binder/ndk/include_ndk/android/binder_status.h
@@ -26,6 +26,7 @@
#pragma once
#include <errno.h>
+#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index c0da2cd..2680e84 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -169,3 +169,18 @@
test_suites: ["device-tests"],
require_root: true,
}
+
+cc_test {
+ name: "binderAllocationLimits",
+ defaults: ["binder_test_defaults"],
+ srcs: ["binderAllocationLimits.cpp"],
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libutils",
+ "libutilscallstack",
+ "libbase",
+ ],
+ test_suites: ["device-tests"],
+ require_root: true,
+}
diff --git a/libs/binder/tests/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
new file mode 100644
index 0000000..e1f5ed5
--- /dev/null
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -0,0 +1,186 @@
+/*
+ * 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/Parcel.h>
+#include <binder/IServiceManager.h>
+#include <gtest/gtest.h>
+#include <utils/CallStack.h>
+
+#include <malloc.h>
+#include <functional>
+#include <vector>
+
+struct DestructionAction {
+ DestructionAction(std::function<void()> f) : mF(std::move(f)) {}
+ ~DestructionAction() { mF(); };
+private:
+ std::function<void()> mF;
+};
+
+// Group of hooks
+struct MallocHooks {
+ decltype(__malloc_hook) malloc_hook;
+ decltype(__realloc_hook) realloc_hook;
+
+ static MallocHooks save() {
+ return {
+ .malloc_hook = __malloc_hook,
+ .realloc_hook = __realloc_hook,
+ };
+ }
+
+ void overwrite() const {
+ __malloc_hook = malloc_hook;
+ __realloc_hook = realloc_hook;
+ }
+};
+
+static const MallocHooks orig_malloc_hooks = MallocHooks::save();
+
+// When malloc is hit, executes lambda.
+namespace LambdaHooks {
+ using AllocationHook = std::function<void(size_t)>;
+ static std::vector<AllocationHook> lambdas = {};
+
+ static void* lambda_realloc_hook(void* ptr, size_t bytes, const void* arg);
+ static void* lambda_malloc_hook(size_t bytes, const void* arg);
+
+ static const MallocHooks lambda_malloc_hooks = {
+ .malloc_hook = lambda_malloc_hook,
+ .realloc_hook = lambda_realloc_hook,
+ };
+
+ static void* lambda_malloc_hook(size_t bytes, const void* arg) {
+ {
+ orig_malloc_hooks.overwrite();
+ lambdas.at(lambdas.size() - 1)(bytes);
+ lambda_malloc_hooks.overwrite();
+ }
+ return orig_malloc_hooks.malloc_hook(bytes, arg);
+ }
+
+ static void* lambda_realloc_hook(void* ptr, size_t bytes, const void* arg) {
+ {
+ orig_malloc_hooks.overwrite();
+ lambdas.at(lambdas.size() - 1)(bytes);
+ lambda_malloc_hooks.overwrite();
+ }
+ return orig_malloc_hooks.realloc_hook(ptr, bytes, arg);
+ }
+
+}
+
+// Action to execute when malloc is hit. Supports nesting. Malloc is not
+// restricted when the allocation hook is being processed.
+__attribute__((warn_unused_result))
+DestructionAction OnMalloc(LambdaHooks::AllocationHook f) {
+ MallocHooks before = MallocHooks::save();
+ LambdaHooks::lambdas.emplace_back(std::move(f));
+ LambdaHooks::lambda_malloc_hooks.overwrite();
+ return DestructionAction([before]() {
+ before.overwrite();
+ LambdaHooks::lambdas.pop_back();
+ });
+}
+
+// exported symbol, to force compiler not to optimize away pointers we set here
+const void* imaginary_use;
+
+TEST(TestTheTest, OnMalloc) {
+ size_t mallocs = 0;
+ {
+ const auto on_malloc = OnMalloc([&](size_t bytes) {
+ mallocs++;
+ EXPECT_EQ(bytes, 40);
+ });
+
+ imaginary_use = new int[10];
+ }
+ EXPECT_EQ(mallocs, 1);
+}
+
+
+__attribute__((warn_unused_result))
+DestructionAction ScopeDisallowMalloc() {
+ return OnMalloc([&](size_t bytes) {
+ ADD_FAILURE() << "Unexpected allocation: " << bytes;
+ using android::CallStack;
+ std::cout << CallStack::stackToString("UNEXPECTED ALLOCATION", CallStack::getCurrent(4 /*ignoreDepth*/).get())
+ << std::endl;
+ });
+}
+
+using android::IBinder;
+using android::Parcel;
+using android::String16;
+using android::defaultServiceManager;
+using android::sp;
+using android::IServiceManager;
+
+static sp<IBinder> GetRemoteBinder() {
+ // This gets binder representing the service manager
+ // the current IServiceManager API doesn't expose the binder, and
+ // I want to avoid adding usages of the AIDL generated interface it
+ // is using underneath, so to avoid people copying it.
+ sp<IBinder> binder = defaultServiceManager()->checkService(String16("manager"));
+ EXPECT_NE(nullptr, binder);
+ return binder;
+}
+
+TEST(BinderAllocation, ParcelOnStack) {
+ const auto m = ScopeDisallowMalloc();
+ Parcel p;
+ imaginary_use = p.data();
+}
+
+TEST(BinderAllocation, GetServiceManager) {
+ defaultServiceManager(); // first call may alloc
+ const auto m = ScopeDisallowMalloc();
+ defaultServiceManager();
+}
+
+// note, ping does not include interface descriptor
+TEST(BinderAllocation, PingTransaction) {
+ sp<IBinder> a_binder = GetRemoteBinder();
+ const auto m = ScopeDisallowMalloc();
+ a_binder->pingBinder();
+}
+
+TEST(BinderAllocation, SmallTransaction) {
+ String16 empty_descriptor = String16("");
+ sp<IServiceManager> manager = defaultServiceManager();
+
+ size_t mallocs = 0;
+ const auto on_malloc = OnMalloc([&](size_t bytes) {
+ mallocs++;
+ // Parcel should allocate a small amount by default
+ EXPECT_EQ(bytes, 128);
+ });
+ manager->checkService(empty_descriptor);
+
+ EXPECT_EQ(mallocs, 1);
+}
+
+int main(int argc, char** argv) {
+ if (getenv("LIBC_HOOKS_ENABLE") == nullptr) {
+ CHECK(0 == setenv("LIBC_HOOKS_ENABLE", "1", true /*overwrite*/));
+ execv(argv[0], argv);
+ return 1;
+ }
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 00f7484..66fb295 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -27,6 +27,11 @@
enabled: true,
support_system_process: true,
},
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
srcs: [
"Gralloc4.cpp"
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
index 8d12754..1a7c2c9 100644
--- a/libs/gralloc/types/include/gralloctypes/Gralloc4.h
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -431,6 +431,12 @@
static_cast<int64_t>(
aidl::android::hardware::graphics::common::PlaneLayoutComponentType::A)};
+static const aidl::android::hardware::graphics::common::ExtendableType
+ PlaneLayoutComponentType_RAW =
+ {GRALLOC4_STANDARD_PLANE_LAYOUT_COMPONENT_TYPE,
+ static_cast<int64_t>(
+ aidl::android::hardware::graphics::common::PlaneLayoutComponentType::RAW)};
+
/*---------------------------------------------------------------------------------------------*/
/**
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 7976ecb..4a4510e 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -130,6 +130,11 @@
cc_library_static {
name: "libgui_bufferqueue_static",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
cflags: [
"-DNO_BUFFERHUB",
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index 8d80833..5023b6b 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -75,10 +75,12 @@
if (NULL != fp) {
const size_t size = 64;
char proc_name[size];
- fgets(proc_name, size, fp);
+ char* result = fgets(proc_name, size, fp);
fclose(fp);
- name = proc_name;
- return NO_ERROR;
+ if (result != nullptr) {
+ name = proc_name;
+ return NO_ERROR;
+ }
}
return INVALID_OPERATION;
}
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 6c9fd07..6881be3 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -689,8 +689,7 @@
return result;
}
- virtual status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) const
- {
+ virtual status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) {
if (!outLayers) {
return UNEXPECTED_NULL;
}
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 2bf8ff7..1efd98b 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1493,7 +1493,7 @@
int result = mGraphicBufferProducer->getLastQueuedBuffer(&graphicBuffer, &spFence, matrix);
if (graphicBuffer != nullptr) {
- *buffer = reinterpret_cast<AHardwareBuffer*>(graphicBuffer.get());
+ *buffer = graphicBuffer->toAHardwareBuffer();
AHardwareBuffer_acquire(*buffer);
} else {
*buffer = nullptr;
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 21ad2b2..a2fb849 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -354,7 +354,7 @@
*
* Requires the ACCESS_SURFACE_FLINGER permission.
*/
- virtual status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) const = 0;
+ virtual status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) = 0;
virtual status_t getColorManagement(bool* outGetColorManagement) const = 0;
diff --git a/libs/gui/include/gui/ISurfaceComposerClient.h b/libs/gui/include/gui/ISurfaceComposerClient.h
index 6366529..3afbabf 100644
--- a/libs/gui/include/gui/ISurfaceComposerClient.h
+++ b/libs/gui/include/gui/ISurfaceComposerClient.h
@@ -33,7 +33,7 @@
DECLARE_META_INTERFACE(SurfaceComposerClient)
// flags for createSurface()
- enum { // (keep in sync with Surface.java)
+ enum { // (keep in sync with SurfaceControl.java)
eHidden = 0x00000004,
eDestroyBackbuffer = 0x00000020,
eSecure = 0x00000080,
@@ -42,6 +42,7 @@
eProtectedByApp = 0x00000800,
eProtectedByDRM = 0x00001000,
eCursorWindow = 0x00002000,
+ eNoColorFill = 0x00004000,
eFXSurfaceBufferQueue = 0x00000000,
eFXSurfaceEffect = 0x00020000,
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 3b80945..81f168c 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -785,7 +785,7 @@
return NO_ERROR;
}
status_t injectVSync(nsecs_t /*when*/) override { return NO_ERROR; }
- status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* /*layers*/) const override {
+ status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* /*layers*/) override {
return NO_ERROR;
}
status_t getCompositionPreference(
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index c243767..31aa685 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -169,6 +169,18 @@
mEventTime = from.mEventTime;
}
+const char* KeyEvent::actionToString(int32_t action) {
+ // Convert KeyEvent action to string
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN:
+ return "DOWN";
+ case AKEY_EVENT_ACTION_UP:
+ return "UP";
+ case AKEY_EVENT_ACTION_MULTIPLE:
+ return "MULTIPLE";
+ }
+ return "UNKNOWN";
+}
// --- PointerCoords ---
@@ -678,6 +690,25 @@
return getAxisByLabel(label);
}
+const char* MotionEvent::actionToString(int32_t action) {
+ // Convert MotionEvent action to string
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ return "DOWN";
+ case AMOTION_EVENT_ACTION_MOVE:
+ return "MOVE";
+ case AMOTION_EVENT_ACTION_UP:
+ return "UP";
+ case AMOTION_EVENT_ACTION_CANCEL:
+ return "CANCEL";
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ return "POINTER_DOWN";
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ return "POINTER_UP";
+ }
+ return "UNKNOWN";
+}
+
// --- FocusEvent ---
void FocusEvent::initialize(int32_t id, bool hasFocus, bool inTouchMode) {
diff --git a/libs/input/InputWindow.cpp b/libs/input/InputWindow.cpp
index 3d3bec8..0455022 100644
--- a/libs/input/InputWindow.cpp
+++ b/libs/input/InputWindow.cpp
@@ -155,21 +155,6 @@
&& y >= frameTop && y < frameBottom;
}
-// TODO(b/155781676): Remove and replace call points with trustedOverlay when that is ready.
-bool InputWindowInfo::isTrustedOverlay() const {
- return layoutParamsType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY ||
- layoutParamsType == TYPE_INPUT_METHOD || layoutParamsType == TYPE_INPUT_METHOD_DIALOG ||
- layoutParamsType == TYPE_MAGNIFICATION_OVERLAY || layoutParamsType == TYPE_STATUS_BAR ||
- layoutParamsType == TYPE_NOTIFICATION_SHADE ||
- layoutParamsType == TYPE_NAVIGATION_BAR ||
- layoutParamsType == TYPE_NAVIGATION_BAR_PANEL ||
- layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY ||
- layoutParamsType == TYPE_DOCK_DIVIDER ||
- layoutParamsType == TYPE_ACCESSIBILITY_OVERLAY ||
- layoutParamsType == TYPE_INPUT_CONSUMER ||
- layoutParamsType == TYPE_TRUSTED_APPLICATION_OVERLAY;
-}
-
bool InputWindowInfo::supportsSplitTouch() const {
return layoutParamsFlags & FLAG_SPLIT_TOUCH;
}
@@ -206,6 +191,7 @@
output.writeBool(hasFocus);
output.writeBool(hasWallpaper);
output.writeBool(paused);
+ output.writeBool(trustedOverlay);
output.writeInt32(ownerPid);
output.writeInt32(ownerUid);
output.writeInt32(inputFeatures);
@@ -244,6 +230,7 @@
ret.hasFocus = from.readBool();
ret.hasWallpaper = from.readBool();
ret.paused = from.readBool();
+ ret.trustedOverlay = from.readBool();
ret.ownerPid = from.readInt32();
ret.ownerUid = from.readInt32();
ret.inputFeatures = from.readInt32();
diff --git a/libs/math/Android.bp b/libs/math/Android.bp
index 8e903e3..22d4712 100644
--- a/libs/math/Android.bp
+++ b/libs/math/Android.bp
@@ -16,6 +16,14 @@
name: "libmath",
host_supported: true,
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ "com.android.neuralnetworks",
+ ],
+ min_sdk_version: "29",
+
export_include_dirs: ["include"],
target: {
windows: {
diff --git a/libs/math/include/math/half.h b/libs/math/include/math/half.h
index 7682973..617a0ab 100644
--- a/libs/math/include/math/half.h
+++ b/libs/math/include/math/half.h
@@ -82,6 +82,7 @@
};
public:
+ CONSTEXPR half() noexcept { }
CONSTEXPR half(float v) noexcept : mBits(ftoh(v)) { }
CONSTEXPR operator float() const noexcept { return htof(mBits); }
diff --git a/libs/math/tests/half_test.cpp b/libs/math/tests/half_test.cpp
index 496a7ef..604072e 100644
--- a/libs/math/tests/half_test.cpp
+++ b/libs/math/tests/half_test.cpp
@@ -35,6 +35,7 @@
EXPECT_EQ(2UL, sizeof(half));
// test +/- zero
+ EXPECT_EQ(0x0000, half().getBits());
EXPECT_EQ(0x0000, half( 0.0f).getBits());
EXPECT_EQ(0x8000, half(-0.0f).getBits());
diff --git a/libs/nativebase/Android.bp b/libs/nativebase/Android.bp
index 9e7e4a2..8399e8c 100644
--- a/libs/nativebase/Android.bp
+++ b/libs/nativebase/Android.bp
@@ -16,6 +16,8 @@
name: "libnativebase_headers",
vendor_available: true,
host_supported: true,
+ // TODO(b/153609531): remove when no longer needed.
+ native_bridge_supported: true,
export_include_dirs: ["include"],
target: {
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index ee006aa..52d73e0 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -25,6 +25,8 @@
name: "libnativewindow_headers",
export_include_dirs: ["include"],
vendor_available: true,
+ // TODO(b/153609531): remove when no longer needed.
+ native_bridge_supported: true,
min_sdk_version: "29",
}
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 25130e2..36aad2e 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -236,7 +236,11 @@
/** Compatibility value for ANativeWindow_setFrameRate. */
enum ANativeWindow_FrameRateCompatibility {
/**
- * There are no inherent restrictions on the frame rate of this window.
+ * There are no inherent restrictions on the frame rate of this window. When
+ * the system selects a frame rate other than what the app requested, the
+ * app will be able to run at the system frame rate without requiring pull
+ * down. This value should be used when displaying game content, UIs, and
+ * anything that isn't video.
*/
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0,
/**
@@ -246,7 +250,7 @@
* to do pull down or use some other technique to adapt to the system's
* frame rate. The user experience is likely to be worse (e.g. more frame
* stuttering) than it would be if the system had chosen the app's requested
- * frame rate.
+ * frame rate. This value should be used for video content.
*/
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1
};
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index f0eb34b..c3fbb60 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -25,7 +25,7 @@
namespace android {
namespace renderengine {
-std::unique_ptr<impl::RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
+std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
RenderEngineType renderEngineType = args.renderEngineType;
// Keep the ability to override by PROPERTIES:
@@ -41,7 +41,8 @@
switch (renderEngineType) {
case RenderEngineType::THREADED:
ALOGD("Threaded RenderEngine with GLES Backend");
- return renderengine::threaded::RenderEngineThreaded::create(args);
+ return renderengine::threaded::RenderEngineThreaded::create(
+ [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); });
case RenderEngineType::GLES:
default:
ALOGD("RenderEngine with GLES Backend");
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 45e19ee..b137023 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -76,7 +76,7 @@
THREADED = 2,
};
- static std::unique_ptr<impl::RenderEngine> create(const RenderEngineCreationArgs& args);
+ static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
virtual ~RenderEngine() = 0;
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 117af0f..69a0e19 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -28,56 +28,44 @@
using testing::Return;
struct RenderEngineThreadedTest : public ::testing::Test {
- RenderEngineThreadedTest() {
- sThreadedRE->setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
- }
-
~RenderEngineThreadedTest() {}
- static void SetUpTestSuite() {
- sThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
- renderengine::RenderEngineCreationArgs::Builder()
- .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::THREADED)
- .build());
+ void SetUp() override {
+ mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
+ [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); });
}
- static void TearDownTestSuite() { sThreadedRE = nullptr; }
-
- // To avoid creating RE on every instantiation of the test, it is kept as a static variable.
- static std::unique_ptr<renderengine::threaded::RenderEngineThreaded> sThreadedRE;
+ std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
};
-std::unique_ptr<renderengine::threaded::RenderEngineThreaded>
- RenderEngineThreadedTest::sThreadedRE = nullptr;
-
TEST_F(RenderEngineThreadedTest, dump) {
std::string testString = "XYZ";
EXPECT_CALL(*mRenderEngine, dump(_));
- sThreadedRE->dump(testString);
+ mThreadedRE->dump(testString);
}
TEST_F(RenderEngineThreadedTest, primeCache) {
EXPECT_CALL(*mRenderEngine, primeCache());
- sThreadedRE->primeCache();
+ mThreadedRE->primeCache();
}
TEST_F(RenderEngineThreadedTest, genTextures) {
uint32_t texName;
EXPECT_CALL(*mRenderEngine, genTextures(1, &texName));
- sThreadedRE->genTextures(1, &texName);
+ mThreadedRE->genTextures(1, &texName);
}
TEST_F(RenderEngineThreadedTest, deleteTextures) {
uint32_t texName;
EXPECT_CALL(*mRenderEngine, deleteTextures(1, &texName));
- sThreadedRE->deleteTextures(1, &texName);
+ mThreadedRE->deleteTextures(1, &texName);
}
TEST_F(RenderEngineThreadedTest, bindExternalBuffer_nullptrBuffer) {
EXPECT_CALL(*mRenderEngine, bindExternalTextureBuffer(0, Eq(nullptr), Eq(nullptr)))
.WillOnce(Return(BAD_VALUE));
- status_t result = sThreadedRE->bindExternalTextureBuffer(0, nullptr, nullptr);
+ status_t result = mThreadedRE->bindExternalTextureBuffer(0, nullptr, nullptr);
ASSERT_EQ(BAD_VALUE, result);
}
@@ -85,119 +73,119 @@
sp<GraphicBuffer> buf = new GraphicBuffer();
EXPECT_CALL(*mRenderEngine, bindExternalTextureBuffer(0, buf, Eq(nullptr)))
.WillOnce(Return(NO_ERROR));
- status_t result = sThreadedRE->bindExternalTextureBuffer(0, buf, nullptr);
+ status_t result = mThreadedRE->bindExternalTextureBuffer(0, buf, nullptr);
ASSERT_EQ(NO_ERROR, result);
}
TEST_F(RenderEngineThreadedTest, cacheExternalTextureBuffer_nullptr) {
EXPECT_CALL(*mRenderEngine, cacheExternalTextureBuffer(Eq(nullptr)));
- sThreadedRE->cacheExternalTextureBuffer(nullptr);
+ mThreadedRE->cacheExternalTextureBuffer(nullptr);
}
TEST_F(RenderEngineThreadedTest, cacheExternalTextureBuffer_withBuffer) {
sp<GraphicBuffer> buf = new GraphicBuffer();
EXPECT_CALL(*mRenderEngine, cacheExternalTextureBuffer(buf));
- sThreadedRE->cacheExternalTextureBuffer(buf);
+ mThreadedRE->cacheExternalTextureBuffer(buf);
}
TEST_F(RenderEngineThreadedTest, unbindExternalTextureBuffer) {
EXPECT_CALL(*mRenderEngine, unbindExternalTextureBuffer(0x0));
- sThreadedRE->unbindExternalTextureBuffer(0x0);
+ mThreadedRE->unbindExternalTextureBuffer(0x0);
}
TEST_F(RenderEngineThreadedTest, bindFrameBuffer_returnsBadValue) {
std::unique_ptr<renderengine::Framebuffer> framebuffer;
EXPECT_CALL(*mRenderEngine, bindFrameBuffer(framebuffer.get())).WillOnce(Return(BAD_VALUE));
- status_t result = sThreadedRE->bindFrameBuffer(framebuffer.get());
+ status_t result = mThreadedRE->bindFrameBuffer(framebuffer.get());
ASSERT_EQ(BAD_VALUE, result);
}
TEST_F(RenderEngineThreadedTest, bindFrameBuffer_returnsNoError) {
std::unique_ptr<renderengine::Framebuffer> framebuffer;
EXPECT_CALL(*mRenderEngine, bindFrameBuffer(framebuffer.get())).WillOnce(Return(NO_ERROR));
- status_t result = sThreadedRE->bindFrameBuffer(framebuffer.get());
+ status_t result = mThreadedRE->bindFrameBuffer(framebuffer.get());
ASSERT_EQ(NO_ERROR, result);
}
TEST_F(RenderEngineThreadedTest, unbindFrameBuffer) {
std::unique_ptr<renderengine::Framebuffer> framebuffer;
EXPECT_CALL(*mRenderEngine, unbindFrameBuffer(framebuffer.get()));
- sThreadedRE->unbindFrameBuffer(framebuffer.get());
+ mThreadedRE->unbindFrameBuffer(framebuffer.get());
}
TEST_F(RenderEngineThreadedTest, getMaxTextureSize_returns20) {
size_t size = 20;
EXPECT_CALL(*mRenderEngine, getMaxTextureSize()).WillOnce(Return(size));
- size_t result = sThreadedRE->getMaxTextureSize();
+ size_t result = mThreadedRE->getMaxTextureSize();
ASSERT_EQ(size, result);
}
TEST_F(RenderEngineThreadedTest, getMaxTextureSize_returns0) {
size_t size = 0;
EXPECT_CALL(*mRenderEngine, getMaxTextureSize()).WillOnce(Return(size));
- size_t result = sThreadedRE->getMaxTextureSize();
+ size_t result = mThreadedRE->getMaxTextureSize();
ASSERT_EQ(size, result);
}
TEST_F(RenderEngineThreadedTest, getMaxViewportDims_returns20) {
size_t dims = 20;
EXPECT_CALL(*mRenderEngine, getMaxViewportDims()).WillOnce(Return(dims));
- size_t result = sThreadedRE->getMaxViewportDims();
+ size_t result = mThreadedRE->getMaxViewportDims();
ASSERT_EQ(dims, result);
}
TEST_F(RenderEngineThreadedTest, getMaxViewportDims_returns0) {
size_t dims = 0;
EXPECT_CALL(*mRenderEngine, getMaxViewportDims()).WillOnce(Return(dims));
- size_t result = sThreadedRE->getMaxViewportDims();
+ size_t result = mThreadedRE->getMaxViewportDims();
ASSERT_EQ(dims, result);
}
TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) {
EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
- status_t result = sThreadedRE->isProtected();
+ status_t result = mThreadedRE->isProtected();
ASSERT_EQ(false, result);
}
TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) {
EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true));
- size_t result = sThreadedRE->isProtected();
+ size_t result = mThreadedRE->isProtected();
ASSERT_EQ(true, result);
}
TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) {
EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false));
- status_t result = sThreadedRE->supportsProtectedContent();
+ status_t result = mThreadedRE->supportsProtectedContent();
ASSERT_EQ(false, result);
}
TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsTrue) {
EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true));
- status_t result = sThreadedRE->supportsProtectedContent();
+ status_t result = mThreadedRE->supportsProtectedContent();
ASSERT_EQ(true, result);
}
TEST_F(RenderEngineThreadedTest, useProtectedContext_returnsFalse) {
EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).WillOnce(Return(false));
- status_t result = sThreadedRE->useProtectedContext(false);
+ status_t result = mThreadedRE->useProtectedContext(false);
ASSERT_EQ(false, result);
}
TEST_F(RenderEngineThreadedTest, useProtectedContext_returnsTrue) {
EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).WillOnce(Return(true));
- status_t result = sThreadedRE->useProtectedContext(false);
+ status_t result = mThreadedRE->useProtectedContext(false);
ASSERT_EQ(true, result);
}
TEST_F(RenderEngineThreadedTest, cleanupPostRender_returnsFalse) {
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return(false));
- status_t result = sThreadedRE->cleanupPostRender();
+ status_t result = mThreadedRE->cleanupPostRender();
ASSERT_EQ(false, result);
}
TEST_F(RenderEngineThreadedTest, cleanupPostRender_returnsTrue) {
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return(true));
- status_t result = sThreadedRE->cleanupPostRender();
+ status_t result = mThreadedRE->cleanupPostRender();
ASSERT_EQ(true, result);
}
@@ -214,7 +202,7 @@
const sp<GraphicBuffer>&, const bool, base::unique_fd&&,
base::unique_fd*) -> status_t { return NO_ERROR; });
- status_t result = sThreadedRE->drawLayers(settings, layers, buffer, false,
+ status_t result = mThreadedRE->drawLayers(settings, layers, buffer, false,
std::move(bufferFence), &drawFence);
ASSERT_EQ(NO_ERROR, result);
}
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index a883ad3..ad61718 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -23,6 +23,7 @@
#include <future>
#include <android-base/stringprintf.h>
+#include <private/gui/SyncFeatures.h>
#include <utils/Trace.h>
#include "gl/GLESRenderEngine.h"
@@ -33,36 +34,15 @@
namespace renderengine {
namespace threaded {
-std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(
- const RenderEngineCreationArgs& args) {
- return std::make_unique<RenderEngineThreaded>(args);
+std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) {
+ return std::make_unique<RenderEngineThreaded>(std::move(factory));
}
-void RenderEngineThreaded::setRenderEngine(
- std::unique_ptr<renderengine::RenderEngine> renderEngine) {
- ATRACE_CALL();
- // In order to ensure this is a thread safe call, it also needs to be put on a stack.
- std::promise<void> resultPromise;
- std::future<void> resultFuture = resultPromise.get_future();
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push(
- [this, &resultPromise, &renderEngine](renderengine::RenderEngine& /*instance*/) {
- ATRACE_NAME("REThreaded::setRenderEngine");
- mRenderEngine = std::move(renderEngine);
- resultPromise.set_value();
- });
- }
- mCondition.notify_one();
- resultFuture.wait();
-}
-
-RenderEngineThreaded::RenderEngineThreaded(const RenderEngineCreationArgs& args)
- : renderengine::impl::RenderEngine(args) {
+RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory) {
ATRACE_CALL();
std::lock_guard lockThread(mThreadMutex);
- mThread = std::thread(&RenderEngineThreaded::threadMain, this, args);
+ mThread = std::thread(&RenderEngineThreaded::threadMain, this, factory);
}
RenderEngineThreaded::~RenderEngineThreaded() {
@@ -78,8 +58,7 @@
}
// NO_THREAD_SAFETY_ANALYSIS is because std::unique_lock presently lacks thread safety annotations.
-void RenderEngineThreaded::threadMain(const RenderEngineCreationArgs& args)
- NO_THREAD_SAFETY_ANALYSIS {
+void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
struct sched_param param = {0};
@@ -88,7 +67,7 @@
ALOGE("Couldn't set SCHED_FIFO");
}
- mRenderEngine = renderengine::gl::GLESRenderEngine::create(args);
+ mRenderEngine = factory();
std::unique_lock<std::mutex> lock(mThreadMutex);
pthread_setname_np(pthread_self(), mThreadName);
@@ -137,6 +116,36 @@
result.assign(resultFuture.get());
}
+bool RenderEngineThreaded::useNativeFenceSync() const {
+ std::promise<bool> resultPromise;
+ std::future<bool> resultFuture = resultPromise.get_future();
+ {
+ std::lock_guard lock(mThreadMutex);
+ mFunctionCalls.push([&resultPromise](renderengine::RenderEngine& /*instance*/) {
+ ATRACE_NAME("REThreaded::useNativeFenceSync");
+ bool returnValue = SyncFeatures::getInstance().useNativeFenceSync();
+ resultPromise.set_value(returnValue);
+ });
+ }
+ mCondition.notify_one();
+ return resultFuture.get();
+}
+
+bool RenderEngineThreaded::useWaitSync() const {
+ std::promise<bool> resultPromise;
+ std::future<bool> resultFuture = resultPromise.get_future();
+ {
+ std::lock_guard lock(mThreadMutex);
+ mFunctionCalls.push([&resultPromise](renderengine::RenderEngine& /*instance*/) {
+ ATRACE_NAME("REThreaded::useWaitSync");
+ bool returnValue = SyncFeatures::getInstance().useWaitSync();
+ resultPromise.set_value(returnValue);
+ });
+ }
+ mCondition.notify_one();
+ return resultFuture.get();
+}
+
void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) {
std::promise<void> resultPromise;
std::future<void> resultFuture = resultPromise.get_future();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 8e4431e..ec18e1f 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -28,21 +28,25 @@
namespace renderengine {
namespace threaded {
+using CreateInstanceFactory = std::function<std::unique_ptr<renderengine::RenderEngine>()>;
+
/**
* This class extends a basic RenderEngine class. It contains a thread. Each time a function of
* this class is called, we create a lambda function that is put on a queue. The main thread then
* executes the functions in order.
*/
-class RenderEngineThreaded : public impl::RenderEngine {
+class RenderEngineThreaded : public RenderEngine {
public:
- static std::unique_ptr<RenderEngineThreaded> create(const RenderEngineCreationArgs& args);
+ static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory);
- RenderEngineThreaded(const RenderEngineCreationArgs& args);
+ RenderEngineThreaded(CreateInstanceFactory factory);
~RenderEngineThreaded() override;
void primeCache() const override;
void dump(std::string& result) override;
+ bool useNativeFenceSync() const override;
+ bool useWaitSync() const override;
void genTextures(size_t count, uint32_t* names) override;
void deleteTextures(size_t count, uint32_t const* names) override;
void bindExternalTextureImage(uint32_t texName, const Image& image) override;
@@ -64,13 +68,12 @@
const std::vector<const LayerSettings*>& layers,
const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
- void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>);
protected:
Framebuffer* getFramebufferForDrawing() override;
private:
- void threadMain(const RenderEngineCreationArgs& args);
+ void threadMain(CreateInstanceFactory factory);
/* ------------------------------------------------------------------------
* Threading
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 8ed09f8..a6b0aaf 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -216,14 +216,25 @@
int32_t type;
Vector<float> floats;
Vector<int32_t> ints;
+ uint32_t count;
handle = data.readInt32();
type = data.readInt32();
- floats.resize(data.readUint32());
+
+ count = data.readUint32();
+ if (count > (data.dataAvail() / sizeof(float))) {
+ return BAD_VALUE;
+ }
+ floats.resize(count);
for (auto &i : floats) {
i = data.readFloat();
}
- ints.resize(data.readUint32());
+
+ count = data.readUint32();
+ if (count > (data.dataAvail() / sizeof(int32_t))) {
+ return BAD_VALUE;
+ }
+ ints.resize(count);
for (auto &i : ints) {
i = data.readInt32();
}
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index bb78895..f3edd3c 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -48,6 +48,8 @@
"//apex_available:anyapex",
"//apex_available:platform",
],
+ min_sdk_version: "apex_inherit",
+
shared_libs: [
"libutils",
],
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 2fcee7b..37c19d4 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -16,6 +16,12 @@
name: "libbufferhub_headers",
export_include_dirs: ["include"],
vendor_available: true, // TODO(b/112338314): Does shouldn't be available to vendor.
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
}
sourceFiles = [
diff --git a/libs/vr/libdvr/Android.bp b/libs/vr/libdvr/Android.bp
index 81a9b2d..340d7bf 100644
--- a/libs/vr/libdvr/Android.bp
+++ b/libs/vr/libdvr/Android.bp
@@ -17,6 +17,12 @@
name: "libdvr_headers",
export_include_dirs: ["include"],
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
}
cc_library_headers {
diff --git a/libs/vr/libpdx/fuzz/Android.bp b/libs/vr/libpdx/fuzz/Android.bp
new file mode 100644
index 0000000..20a03ea
--- /dev/null
+++ b/libs/vr/libpdx/fuzz/Android.bp
@@ -0,0 +1,19 @@
+cc_fuzz {
+ name: "libpdx_fuzz",
+ clang: true,
+ srcs: [
+ "service_dispatcher_fuzzer.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ static_libs: [
+ "libpdx",
+ ],
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+}
diff --git a/libs/vr/libpdx/fuzz/helpers.h b/libs/vr/libpdx/fuzz/helpers.h
new file mode 100644
index 0000000..83ec409
--- /dev/null
+++ b/libs/vr/libpdx/fuzz/helpers.h
@@ -0,0 +1,294 @@
+/*
+ * 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.
+ */
+// Authors: corbin.souffrant@leviathansecurity.com
+// brian.balling@leviathansecurity.com
+
+#ifndef LEV_FUZZERS_LIBPDX_HELPERS_H_
+#define LEV_FUZZERS_LIBPDX_HELPERS_H_
+
+#define UNUSED(expr) \
+ do { \
+ (void)(expr); \
+ } while (0)
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <pdx/client.h>
+#include <pdx/service.h>
+#include <pdx/service_dispatcher.h>
+#include <pdx/service_endpoint.h>
+#include <sys/eventfd.h>
+#include <memory>
+#include <vector>
+
+using namespace android::pdx;
+
+// Vector of operations we can call in the dispatcher.
+static const std::vector<std::function<void(
+ const std::unique_ptr<ServiceDispatcher>&, FuzzedDataProvider*)>>
+ dispatcher_operations = {
+ [](const std::unique_ptr<ServiceDispatcher>& dispatcher,
+ FuzzedDataProvider*) -> void { dispatcher->EnterDispatchLoop(); },
+ [](const std::unique_ptr<ServiceDispatcher>& dispatcher,
+ FuzzedDataProvider*) -> void { dispatcher->ReceiveAndDispatch(); },
+ [](const std::unique_ptr<ServiceDispatcher>& dispatcher,
+ FuzzedDataProvider* fdp) -> void {
+ dispatcher->ReceiveAndDispatch(fdp->ConsumeIntegral<int>());
+ }};
+
+// Most of the fuzzing occurs within the endpoint, which is derived from an
+// abstract class. So we are returning garbage data for most functions besides
+// the ones we added or need to actually use.
+class FuzzEndpoint : public Endpoint {
+ public:
+ explicit FuzzEndpoint(FuzzedDataProvider* fdp) {
+ _fdp = fdp;
+ _epoll_fd = eventfd(0, 0);
+ }
+
+ ~FuzzEndpoint() { close(_epoll_fd); }
+
+ // Returns an fd that can be used with epoll() to wait for incoming messages
+ // from this endpoint.
+ int epoll_fd() const { return _epoll_fd; }
+
+ // Associates a Service instance with an endpoint by setting the service
+ // context pointer to the address of the Service. Only one Service may be
+ // associated with a given endpoint.
+ Status<void> SetService(Service* service) {
+ _service = service;
+ return Status<void>(0);
+ }
+
+ // Set the channel context for the given channel.
+ Status<void> SetChannel(int channel_id, Channel* channel) {
+ UNUSED(channel_id);
+ _channel = std::shared_ptr<Channel>(channel);
+ return Status<void>(0);
+ }
+
+ // Receives a message on the given endpoint file descriptor.
+ // This is called by the dispatcher to determine what operations
+ // to make, so we are fuzzing the response.
+ Status<void> MessageReceive(Message* message) {
+ // Create a randomized MessageInfo struct.
+ MessageInfo info;
+ eventfd_t wakeup_val = 0;
+ info.pid = _fdp->ConsumeIntegral<int>();
+ info.tid = _fdp->ConsumeIntegral<int>();
+ info.cid = _fdp->ConsumeIntegral<int>();
+ info.mid = _fdp->ConsumeIntegral<int>();
+ info.euid = _fdp->ConsumeIntegral<int>();
+ info.egid = _fdp->ConsumeIntegral<int>();
+ info.op = _fdp->ConsumeIntegral<int32_t>();
+ info.flags = _fdp->ConsumeIntegral<uint32_t>();
+ info.service = _service;
+ info.channel = _channel.get();
+ info.send_len = _fdp->ConsumeIntegral<size_t>();
+ info.recv_len = _fdp->ConsumeIntegral<size_t>();
+ info.fd_count = _fdp->ConsumeIntegral<size_t>();
+ if (_fdp->remaining_bytes() >= 32) {
+ std::vector<uint8_t> impulse_vec = _fdp->ConsumeBytes<uint8_t>(32);
+ memcpy(info.impulse, impulse_vec.data(), 32);
+ }
+
+ *message = Message(info);
+ eventfd_read(_epoll_fd, &wakeup_val);
+
+ return Status<void>();
+ }
+
+ // Returns a tag that uniquely identifies a specific underlying IPC
+ // transport.
+ uint32_t GetIpcTag() const { return 0; }
+
+ // Close a channel, signaling the client file object and freeing the channel
+ // id. Once closed, the client side of the channel always returns the error
+ // ESHUTDOWN and signals the poll/epoll events POLLHUP and POLLFREE.
+ Status<void> CloseChannel(int channel_id) {
+ UNUSED(channel_id);
+ return Status<void>();
+ }
+
+ // Update the event bits for the given channel (given by id), using the
+ // given clear and set masks.
+ Status<void> ModifyChannelEvents(int channel_id, int clear_mask,
+ int set_mask) {
+ UNUSED(channel_id);
+ UNUSED(clear_mask);
+ UNUSED(set_mask);
+ return Status<void>();
+ }
+
+ // Create a new channel and push it as a file descriptor to the process
+ // sending the |message|. |flags| may be set to O_NONBLOCK and/or
+ // O_CLOEXEC to control the initial behavior of the new file descriptor (the
+ // sending process may change these later using fcntl()). The internal
+ // Channel instance associated with this channel is set to |channel|,
+ // which may be nullptr. The new channel id allocated for this channel is
+ // returned in |channel_id|, which may also be nullptr if not needed.
+ Status<RemoteChannelHandle> PushChannel(Message* message, int flags,
+ Channel* channel, int* channel_id) {
+ UNUSED(message);
+ UNUSED(flags);
+ UNUSED(channel);
+ UNUSED(channel_id);
+ return Status<RemoteChannelHandle>();
+ }
+
+ // Check whether the |ref| is a reference to a channel to the service
+ // represented by the |endpoint|. If the channel reference in question is
+ // valid, the Channel object is returned in |channel| when non-nullptr and
+ // the channel ID is returned through the Status object.
+ Status<int> CheckChannel(const Message* message, ChannelReference ref,
+ Channel** channel) {
+ UNUSED(message);
+ UNUSED(ref);
+ UNUSED(channel);
+ return Status<int>();
+ }
+
+ // Replies to the message with a return code.
+ Status<void> MessageReply(Message* message, int return_code) {
+ UNUSED(message);
+ UNUSED(return_code);
+ return Status<void>();
+ }
+
+ // Replies to the message with a file descriptor.
+ Status<void> MessageReplyFd(Message* message, unsigned int push_fd) {
+ UNUSED(message);
+ UNUSED(push_fd);
+ return Status<void>();
+ }
+
+ // Replies to the message with a local channel handle.
+ Status<void> MessageReplyChannelHandle(Message* message,
+ const LocalChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<void>();
+ }
+
+ // Replies to the message with a borrowed local channel handle.
+ Status<void> MessageReplyChannelHandle(Message* message,
+ const BorrowedChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<void>();
+ }
+
+ // Replies to the message with a remote channel handle.
+ Status<void> MessageReplyChannelHandle(Message* message,
+ const RemoteChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<void>();
+ }
+
+ // Reads message data into an array of memory buffers.
+ Status<size_t> ReadMessageData(Message* message, const iovec* vector,
+ size_t vector_length) {
+ UNUSED(message);
+ UNUSED(vector);
+ UNUSED(vector_length);
+ return Status<size_t>();
+ }
+
+ // Sends reply data for message.
+ Status<size_t> WriteMessageData(Message* message, const iovec* vector,
+ size_t vector_length) {
+ UNUSED(message);
+ UNUSED(vector);
+ UNUSED(vector_length);
+ return Status<size_t>();
+ }
+
+ // Records a file descriptor into the message buffer and returns the
+ // remapped reference to be sent to the remote process.
+ Status<FileReference> PushFileHandle(Message* message,
+ const LocalHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<FileReference>();
+ }
+
+ Status<FileReference> PushFileHandle(Message* message,
+ const BorrowedHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<FileReference>();
+ }
+
+ Status<FileReference> PushFileHandle(Message* message,
+ const RemoteHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<FileReference>();
+ }
+
+ Status<ChannelReference> PushChannelHandle(Message* message,
+ const LocalChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<ChannelReference>();
+ }
+
+ Status<ChannelReference> PushChannelHandle(
+ Message* message, const BorrowedChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<ChannelReference>();
+ }
+
+ Status<ChannelReference> PushChannelHandle(
+ Message* message, const RemoteChannelHandle& handle) {
+ UNUSED(message);
+ UNUSED(handle);
+ return Status<ChannelReference>();
+ }
+
+ // Obtains a file descriptor/channel handle from a message for the given
+ // reference.
+ LocalHandle GetFileHandle(Message* message, FileReference ref) const {
+ UNUSED(message);
+ UNUSED(ref);
+ return LocalHandle();
+ }
+
+ LocalChannelHandle GetChannelHandle(Message* message,
+ ChannelReference ref) const {
+ UNUSED(message);
+ UNUSED(ref);
+ return LocalChannelHandle();
+ }
+
+ // Transport-specific message state management.
+ void* AllocateMessageState() { return nullptr; }
+
+ void FreeMessageState(void* state) { UNUSED(state); }
+
+ // Cancels the endpoint, unblocking any receiver threads waiting for a
+ // message.
+ Status<void> Cancel() { return Status<void>(); }
+
+ private:
+ FuzzedDataProvider* _fdp;
+ std::shared_ptr<Channel> _channel;
+ Service* _service;
+ int _epoll_fd;
+};
+
+#endif // LEV_FUZZERS_LIBPDX_HELPERS_H_
diff --git a/libs/vr/libpdx/fuzz/service_dispatcher_fuzzer.cpp b/libs/vr/libpdx/fuzz/service_dispatcher_fuzzer.cpp
new file mode 100644
index 0000000..3a3bfd9
--- /dev/null
+++ b/libs/vr/libpdx/fuzz/service_dispatcher_fuzzer.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+// Authors: corbin.souffrant@leviathansecurity.com
+// brian.balling@leviathansecurity.com
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <helpers.h>
+#include <pdx/client_channel.h>
+#include <pdx/service.h>
+#include <pdx/service_dispatcher.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/eventfd.h>
+#include <thread>
+
+using namespace android::pdx;
+
+// Dispatch fuzzer entry point. This fuzzer creates a ServiceDispatcher
+// and creates an endpoint that returns fuzzed messages that are passed
+// to the ReceiveAndDispatch and DispatchLoop functions.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ eventfd_t wakeup_val = 1;
+ FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+
+ // Endpoint is only used to be immediately wrapped as a unique_ptr,
+ // so it is ok to be using a raw ptr and new here without freeing.
+ FuzzEndpoint* endpoint = new FuzzEndpoint(&fdp);
+ std::unique_ptr<ServiceDispatcher> dispatcher = ServiceDispatcher::Create();
+ std::shared_ptr<Channel> channel(nullptr);
+ std::shared_ptr<Client> client(nullptr);
+ std::shared_ptr<Service> service(
+ new Service("FuzzService", std::unique_ptr<Endpoint>(endpoint)));
+
+ service->SetChannel(0, std::shared_ptr<Channel>(channel));
+ dispatcher->AddService(service);
+
+ // Dispatcher blocks, so needs to run in its own thread.
+ std::thread run_dispatcher([&]() {
+ uint8_t opt = 0;
+
+ // Right now the only operations block, so the while loop is pointless
+ // but leaving it in, just in case that ever changes.
+ while (fdp.remaining_bytes() > sizeof(MessageInfo)) {
+ opt = fdp.ConsumeIntegral<uint8_t>() % dispatcher_operations.size();
+ dispatcher_operations[opt](dispatcher, &fdp);
+ }
+ });
+
+ // Continuously wake up the epoll so the dispatcher can run.
+ while (fdp.remaining_bytes() > sizeof(MessageInfo)) {
+ eventfd_write(endpoint->epoll_fd(), wakeup_val);
+ }
+
+ // Cleanup the dispatcher and thread.
+ dispatcher->SetCanceled(true);
+ if (run_dispatcher.joinable())
+ run_dispatcher.join();
+ dispatcher->RemoveService(service);
+
+ return 0;
+}
diff --git a/opengl/include/EGL/eglext_angle.h b/opengl/include/EGL/eglext_angle.h
index 0556ea1..1f1bcb3 100644
--- a/opengl/include/EGL/eglext_angle.h
+++ b/opengl/include/EGL/eglext_angle.h
@@ -4,12 +4,12 @@
// found in the LICENSE file.
//
// eglext_angle.h: ANGLE modifications to the eglext.h header file.
-// Currently we don't include this file directly, we patch eglext.h
-// to include it implicitly so it is visible throughout our code.
#ifndef INCLUDE_EGL_EGLEXT_ANGLE_
#define INCLUDE_EGL_EGLEXT_ANGLE_
+#include <EGL/eglext.h>
+
// clang-format off
#ifndef EGL_ANGLE_robust_resource_initialization
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 3997134..3c76c62 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -100,12 +100,8 @@
"libgraphicsenv",
"libnativewindow",
"libbacktrace",
+ "libbase",
],
- target: {
- vendor: {
- exclude_shared_libs: ["libgraphicsenv"],
- },
- },
}
cc_library_static {
diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp
index 74c4d7d..74fb019 100644
--- a/opengl/libs/EGL/BlobCache.cpp
+++ b/opengl/libs/EGL/BlobCache.cpp
@@ -21,7 +21,7 @@
#include <errno.h>
#include <inttypes.h>
-#include <cutils/properties.h>
+#include <android-base/properties.h>
#include <log/log.h>
#include <chrono>
@@ -165,7 +165,8 @@
}
size_t BlobCache::getFlattenedSize() const {
- size_t size = align4(sizeof(Header) + PROPERTY_VALUE_MAX);
+ auto buildId = base::GetProperty("ro.build.id", "");
+ size_t size = align4(sizeof(Header) + buildId.size());
for (const CacheEntry& e : mCacheEntries) {
std::shared_ptr<Blob> const& keyBlob = e.getKey();
std::shared_ptr<Blob> const& valueBlob = e.getValue();
@@ -185,9 +186,9 @@
header->mBlobCacheVersion = blobCacheVersion;
header->mDeviceVersion = blobCacheDeviceVersion;
header->mNumEntries = mCacheEntries.size();
- char buildId[PROPERTY_VALUE_MAX];
- header->mBuildIdLength = property_get("ro.build.id", buildId, "");
- memcpy(header->mBuildId, buildId, header->mBuildIdLength);
+ auto buildId = base::GetProperty("ro.build.id", "");
+ header->mBuildIdLength = buildId.size();
+ memcpy(header->mBuildId, buildId.c_str(), header->mBuildIdLength);
// Write cache entries
uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer);
@@ -238,12 +239,11 @@
ALOGE("unflatten: bad magic number: %" PRIu32, header->mMagicNumber);
return -EINVAL;
}
- char buildId[PROPERTY_VALUE_MAX];
- int len = property_get("ro.build.id", buildId, "");
+ auto buildId = base::GetProperty("ro.build.id", "");
if (header->mBlobCacheVersion != blobCacheVersion ||
- header->mDeviceVersion != blobCacheDeviceVersion ||
- len != header->mBuildIdLength ||
- strncmp(buildId, header->mBuildId, len)) {
+ header->mDeviceVersion != blobCacheDeviceVersion ||
+ buildId.size() != header->mBuildIdLength ||
+ strncmp(buildId.c_str(), header->mBuildId, buildId.size())) {
// We treat version mismatches as an empty cache.
return 0;
}
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 391a410..85e2c15 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -17,27 +17,23 @@
//#define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#include <EGL/Loader.h>
+#include "EGL/Loader.h"
+
+#include <android-base/properties.h>
+#include <android/dlext.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <graphicsenv/GraphicsEnv.h>
+#include <log/log.h>
+#include <utils/Timers.h>
+#include <vndksupport/linker.h>
#include <string>
-#include <dirent.h>
-#include <dlfcn.h>
-
-#include <android/dlext.h>
-#include <cutils/properties.h>
-#include <log/log.h>
-#include <utils/Timers.h>
-
-#ifndef __ANDROID_VNDK__
-#include <graphicsenv/GraphicsEnv.h>
-#endif
-#include <vndksupport/linker.h>
-
+#include "EGL/eglext_angle.h"
#include "egl_platform_entries.h"
#include "egl_trace.h"
#include "egldefs.h"
-#include <EGL/eglext_angle.h>
namespace android {
@@ -159,13 +155,11 @@
return true;
}
-#ifndef __ANDROID_VNDK__
// Return true if updated driver namespace is set.
ns = android::GraphicsEnv::getInstance().getDriverNamespace();
if (ns) {
return true;
}
-#endif
return false;
}
@@ -241,12 +235,12 @@
// i.e.:
// libGLES_${prop}.so, or:
// libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so
- char prop[PROPERTY_VALUE_MAX + 1];
for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
- if (property_get(key, prop, nullptr) <= 0) {
+ auto prop = base::GetProperty(key, "");
+ if (prop.empty()) {
continue;
}
- hnd = attempt_to_load_system_driver(cnx, prop, true);
+ hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
if (hnd) {
break;
} else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) {
@@ -510,9 +504,9 @@
.library_namespace = ns,
};
void* so = nullptr;
- char prop[PROPERTY_VALUE_MAX + 1];
for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
- if (property_get(key, prop, nullptr) <= 0) {
+ auto prop = base::GetProperty(key, "");
+ if (prop.empty()) {
continue;
}
std::string name = std::string("lib") + kind + "_" + prop + ".so";
@@ -569,7 +563,7 @@
Loader::driver_t* Loader::attempt_to_load_updated_driver(egl_connection_t* cnx) {
ATRACE_CALL();
-#ifndef __ANDROID_VNDK__
+
android_namespace_t* ns = android::GraphicsEnv::getInstance().getDriverNamespace();
if (!ns) {
return nullptr;
@@ -599,9 +593,6 @@
hnd->set(dso, GLESv2);
}
return hnd;
-#else
- return nullptr;
-#endif
}
Loader::driver_t* Loader::attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix,
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index d5d57d7..43f7a07 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -18,7 +18,7 @@
#include <EGL/egl.h>
-#include <cutils/properties.h>
+#include <android-base/properties.h>
#include <log/log.h>
@@ -58,9 +58,7 @@
} else {
LOG_ALWAYS_FATAL(error);
}
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.callstack", value, "0");
- if (atoi(value)) {
+ if (base::GetBoolProperty("debug.egl.callstack", false)) {
CallStack::log(LOG_TAG);
}
}
@@ -224,9 +222,7 @@
pthread_mutex_unlock(&sLogPrintMutex);
if (printLog) {
ALOGE("called unimplemented OpenGL ES API");
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.callstack", value, "0");
- if (atoi(value)) {
+ if (base::GetBoolProperty("debug.egl.callstack", false)) {
CallStack::log(LOG_TAG);
}
}
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index 00caff2..97dc0f1 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -16,7 +16,6 @@
#if defined(__ANDROID__)
-#include <cutils/properties.h>
#include "Loader.h"
#include "egl_angle_platform.h"
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 6af7cd2..8c6f284 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -19,25 +19,22 @@
#include "egl_display.h"
+#include <SurfaceFlingerProperties.h>
+#include <android-base/properties.h>
+#include <android/dlext.h>
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
+#include <configstore/Utils.h>
+#include <dlfcn.h>
+#include <graphicsenv/GraphicsEnv.h>
+
#include "../egl_impl.h"
-
-#include <EGL/eglext_angle.h>
-#include <private/EGL/display.h>
-
-#include <cutils/properties.h>
+#include "EGL/eglext_angle.h"
#include "Loader.h"
#include "egl_angle_platform.h"
#include "egl_cache.h"
#include "egl_object.h"
#include "egl_tls.h"
-
-#include <SurfaceFlingerProperties.h>
-#include <android/dlext.h>
-#include <dlfcn.h>
-#include <graphicsenv/GraphicsEnv.h>
-
-#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
-#include <configstore/Utils.h>
+#include "private/EGL/display.h"
using namespace android::hardware::configstore;
using namespace android::hardware::configstore::V1_0;
@@ -73,7 +70,7 @@
}
bool needsAndroidPEglMitigation() {
- static const int32_t vndk_version = property_get_int32("ro.vndk.version", -1);
+ static const int32_t vndk_version = base::GetIntProperty("ro.vndk.version", -1);
return vndk_version <= 28;
}
@@ -151,10 +148,8 @@
attrs.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE);
attrs.push_back(EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE);
- char prop[PROPERTY_VALUE_MAX];
- property_get("debug.angle.validation", prop, "0");
attrs.push_back(EGL_PLATFORM_ANGLE_DEBUG_LAYERS_ENABLED_ANGLE);
- attrs.push_back(atoi(prop));
+ attrs.push_back(base::GetBoolProperty("debug.angle.validation", false));
attrs.push_back(EGL_NONE);
@@ -372,16 +367,8 @@
egl_cache_t::get()->initialize(this);
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.finish", value, "0");
- if (atoi(value)) {
- finishOnSwap = true;
- }
-
- property_get("debug.egl.traceGpuCompletion", value, "0");
- if (atoi(value)) {
- traceGpuCompletion = true;
- }
+ finishOnSwap = base::GetBoolProperty("debug.egl.finish", false);
+ traceGpuCompletion = base::GetBoolProperty("debug.egl.traceGpuCompletion", false);
// TODO: If device doesn't provide 1.4 or 1.5 then we'll be
// changing the behavior from the past where we always advertise
diff --git a/opengl/libs/EGL/egl_layers.cpp b/opengl/libs/EGL/egl_layers.cpp
index 44a1c0b..ea86c9a 100644
--- a/opengl/libs/EGL/egl_layers.cpp
+++ b/opengl/libs/EGL/egl_layers.cpp
@@ -18,9 +18,9 @@
#include <EGL/egl.h>
#include <android-base/file.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/dlext.h>
-#include <cutils/properties.h>
#include <dlfcn.h>
#include <graphicsenv/GraphicsEnv.h>
#include <log/log.h>
@@ -157,9 +157,7 @@
if (debug_layers.empty()) {
// Only check system properties if Java settings are empty
- char prop[PROPERTY_VALUE_MAX];
- property_get("debug.gles.layers", prop, "");
- debug_layers = prop;
+ debug_layers = base::GetProperty("debug.gles.layers", "");
}
return debug_layers;
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index c976c60..1119e4a 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -18,36 +18,32 @@
#include "egl_platform_entries.h"
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android/hardware_buffer.h>
#include <ctype.h>
+#include <cutils/compiler.h>
#include <dlfcn.h>
+#include <graphicsenv/GraphicsEnv.h>
+#include <log/log.h>
+#include <private/android/AHardwareBufferHelpers.h>
#include <stdlib.h>
#include <string.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <EGL/eglext_angle.h>
-
-#include <android/hardware_buffer.h>
-#include <android-base/strings.h>
-#include <graphicsenv/GraphicsEnv.h>
-#include <private/android/AHardwareBufferHelpers.h>
-
-#include <cutils/compiler.h>
-#include <cutils/properties.h>
-#include <log/log.h>
-
#include <condition_variable>
#include <deque>
#include <mutex>
-#include <unordered_map>
#include <string>
#include <thread>
+#include <unordered_map>
#include "../egl_impl.h"
-
+#include "EGL/egl.h"
+#include "EGL/eglext.h"
+#include "EGL/eglext_angle.h"
#include "egl_display.h"
-#include "egl_object.h"
#include "egl_layers.h"
+#include "egl_object.h"
#include "egl_tls.h"
#include "egl_trace.h"
@@ -96,7 +92,7 @@
"EGL_EXT_surface_CTA861_3_metadata "
;
-// Whitelist of extensions exposed to applications if implemented in the vendor driver.
+// Allowed list of extensions exposed to applications if implemented in the vendor driver.
char const * const gExtensionString =
"EGL_KHR_image " // mandatory
"EGL_KHR_image_base " // mandatory
@@ -381,10 +377,7 @@
egl_connection_t* const cnx = &gEGLImpl;
if (cnx->dso) {
if (attrib_list) {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.force_msaa", value, "false");
-
- if (!strcmp(value, "true")) {
+ if (base::GetBoolProperty("debug.egl.force_msaa", false)) {
size_t attribCount = 0;
EGLint attrib = attrib_list[0];
@@ -2251,15 +2244,8 @@
}
EGLClientBuffer eglGetNativeClientBufferANDROIDImpl(const AHardwareBuffer *buffer) {
- // AHardwareBuffer_to_ANativeWindowBuffer is a platform-only symbol and thus
- // this function cannot be implemented when this libEGL is built for
- // vendors.
-#ifndef __ANDROID_VNDK__
if (!buffer) return setError(EGL_BAD_PARAMETER, (EGLClientBuffer) nullptr);
return const_cast<ANativeWindowBuffer *>(AHardwareBuffer_to_ANativeWindowBuffer(buffer));
-#else
- return setError(EGL_BAD_PARAMETER, (EGLClientBuffer) nullptr);
-#endif
}
// ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/egl_tls.cpp b/opengl/libs/EGL/egl_tls.cpp
index aaecb62..8d118e0 100644
--- a/opengl/libs/EGL/egl_tls.cpp
+++ b/opengl/libs/EGL/egl_tls.cpp
@@ -18,7 +18,7 @@
#include <stdlib.h>
-#include <cutils/properties.h>
+#include <android-base/properties.h>
#include <log/log.h>
#include "CallStack.h"
#include "egl_platform_entries.h"
@@ -96,9 +96,7 @@
if (!quiet) {
ALOGE("%s:%d error %x (%s)",
caller, line, error, egl_strerror(error));
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.callstack", value, "0");
- if (atoi(value)) {
+ if (base::GetBoolProperty("debug.egl.callstack", false)) {
CallStack::log(LOG_TAG);
}
}
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index ffb7b5e..5c7f344 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -2,4 +2,3 @@
class hal
user graphics
group automotive_evs
- disabled # will not automatically start with its class; must be explicitly started.
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 6eed24a..04fe1e6 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -21,6 +21,7 @@
"libbinder",
"libcutils",
"libgfxstats",
+ "libgpumem",
"libgraphicsenv",
"liblog",
"libutils",
@@ -85,6 +86,10 @@
name: "gpuservice",
defaults: ["libgpuservice_binary"],
init_rc: ["gpuservice.rc"],
+ required: [
+ "bpfloader",
+ "gpu_mem.o",
+ ],
srcs: [":gpuservice_binary_sources"],
shared_libs: [
"libgpuservice",
diff --git a/services/gpuservice/CleanSpec.mk b/services/gpuservice/CleanSpec.mk
new file mode 100644
index 0000000..482fc6d
--- /dev/null
+++ b/services/gpuservice/CleanSpec.mk
@@ -0,0 +1,52 @@
+# 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# Remove gpu_mem.o
+$(call add-clean-step, rm -rf $(OUT_DIR)/soong/.intermediates/frameworks/native/services/gpuservice/bpf)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/FAKE/gpu_mem.o_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/ETC/gpu_mem.o_gpu_mem.o_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/bpf/gpu_mem.o)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/fake_packages/gpu_mem.o-timestamp)
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 7bb0e4a..bf0d6a9 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -24,11 +24,11 @@
#include <binder/Parcel.h>
#include <binder/PermissionCache.h>
#include <cutils/properties.h>
+#include <gpumem/GpuMem.h>
#include <gpustats/GpuStats.h>
#include <private/android_filesystem_config.h>
#include <utils/String8.h>
#include <utils/Trace.h>
-
#include <vkjson.h>
namespace android {
@@ -45,7 +45,10 @@
const char* const GpuService::SERVICE_NAME = "gpu";
-GpuService::GpuService() : mGpuStats(std::make_unique<GpuStats>()){};
+GpuService::GpuService()
+ : mGpuMem(std::make_unique<GpuMem>()), mGpuStats(std::make_unique<GpuStats>()) {
+ mGpuMem->initialize();
+};
void GpuService::setGpuStats(const std::string& driverPackageName,
const std::string& driverVersionName, uint64_t driverVersionCode,
@@ -90,24 +93,31 @@
} else {
bool dumpAll = true;
bool dumpDriverInfo = false;
+ bool dumpMem = false;
bool dumpStats = false;
size_t numArgs = args.size();
if (numArgs) {
- dumpAll = false;
for (size_t index = 0; index < numArgs; ++index) {
if (args[index] == String16("--gpustats")) {
dumpStats = true;
} else if (args[index] == String16("--gpudriverinfo")) {
dumpDriverInfo = true;
+ } else if (args[index] == String16("--gpumem")) {
+ dumpMem = true;
}
}
+ dumpAll = !(dumpDriverInfo || dumpMem || dumpStats);
}
if (dumpAll || dumpDriverInfo) {
dumpGameDriverInfo(&result);
result.append("\n");
}
+ if (dumpAll || dumpMem) {
+ mGpuMem->dump(args, &result);
+ result.append("\n");
+ }
if (dumpAll || dumpStats) {
mGpuStats->dump(args, &result);
result.append("\n");
diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h
index b3e34d5..f4b8597 100644
--- a/services/gpuservice/GpuService.h
+++ b/services/gpuservice/GpuService.h
@@ -28,6 +28,7 @@
namespace android {
+class GpuMem;
class GpuStats;
class GpuService : public BnGpuService, public PriorityDumper {
@@ -72,6 +73,7 @@
/*
* Attributes
*/
+ std::unique_ptr<GpuMem> mGpuMem;
std::unique_ptr<GpuStats> mGpuStats;
};
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index 538506d..db81f5d 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -19,11 +19,16 @@
address: true,
},
srcs: [
+ "GpuMemTest.cpp",
"GpuStatsTest.cpp",
],
shared_libs: [
+ "libbase",
+ "libbpf",
+ "libbpf_android",
"libcutils",
"libgfxstats",
+ "libgpumem",
"libgraphicsenv",
"liblog",
"libstatslog",
diff --git a/services/gpuservice/tests/unittests/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
new file mode 100644
index 0000000..aad79ae
--- /dev/null
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 <android-base/stringprintf.h>
+#include <bpf/BpfMap.h>
+#include <gmock/gmock.h>
+#include <gpumem/GpuMem.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include "TestableGpuMem.h"
+
+namespace android {
+namespace {
+
+using base::StringPrintf;
+using testing::HasSubstr;
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint64_t TEST_GLOBAL_KEY = 0;
+constexpr uint64_t TEST_GLOBAL_VAL = 123;
+constexpr uint64_t TEST_PROC_KEY_1 = 1;
+constexpr uint64_t TEST_PROC_VAL_1 = 234;
+constexpr uint64_t TEST_PROC_KEY_2 = 4294967298; // (1 << 32) + 2
+constexpr uint64_t TEST_PROC_VAL_2 = 345;
+
+class GpuMemTest : public testing::Test {
+public:
+ GpuMemTest() {
+ 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());
+ }
+
+ ~GpuMemTest() {
+ 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 {
+ mGpuMem = std::make_unique<GpuMem>();
+ mTestableGpuMem = TestableGpuMem(mGpuMem.get());
+ mTestMap = bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE,
+ BPF_F_NO_PREALLOC);
+
+ EXPECT_LE(0, mTestMap.getMap().get());
+ EXPECT_TRUE(mTestMap.isValid());
+ }
+
+ std::string dumpsys() {
+ std::string result;
+ Vector<String16> args;
+ mGpuMem->dump(args, &result);
+ return result;
+ }
+
+ std::unique_ptr<GpuMem> mGpuMem;
+ TestableGpuMem mTestableGpuMem;
+ bpf::BpfMap<uint64_t, uint64_t> mTestMap;
+};
+
+TEST_F(GpuMemTest, validGpuMemTotalBpfPaths) {
+ EXPECT_EQ(mTestableGpuMem.getGpuMemTraceGroup(), "gpu_mem");
+ EXPECT_EQ(mTestableGpuMem.getGpuMemTotalTracepoint(), "gpu_mem_total");
+ EXPECT_EQ(mTestableGpuMem.getGpuMemTotalProgPath(),
+ "/sys/fs/bpf/prog_gpu_mem_tracepoint_gpu_mem_gpu_mem_total");
+ EXPECT_EQ(mTestableGpuMem.getGpuMemTotalMapPath(), "/sys/fs/bpf/map_gpu_mem_gpu_mem_total_map");
+}
+
+TEST_F(GpuMemTest, bpfInitializationFailed) {
+ EXPECT_EQ(dumpsys(), "Failed to initialize GPU memory eBPF\n");
+}
+
+TEST_F(GpuMemTest, gpuMemTotalMapEmpty) {
+ mTestableGpuMem.setGpuMemTotalMap(mTestMap);
+
+ EXPECT_EQ(dumpsys(), "GPU memory total usage map is empty\n");
+}
+
+TEST_F(GpuMemTest, globalMemTotal) {
+ ASSERT_RESULT_OK(mTestMap.writeValue(TEST_GLOBAL_KEY, TEST_GLOBAL_VAL, BPF_ANY));
+ mTestableGpuMem.setGpuMemTotalMap(mTestMap);
+
+ EXPECT_THAT(dumpsys(), HasSubstr(StringPrintf("Global total: %" PRIu64 "\n", TEST_GLOBAL_VAL)));
+}
+
+TEST_F(GpuMemTest, missingGlobalMemTotal) {
+ ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_1, TEST_PROC_VAL_1, BPF_ANY));
+ mTestableGpuMem.setGpuMemTotalMap(mTestMap);
+
+ EXPECT_THAT(dumpsys(), HasSubstr("Global total: N/A"));
+}
+
+TEST_F(GpuMemTest, procMemTotal) {
+ 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);
+
+ EXPECT_THAT(dumpsys(),
+ HasSubstr(StringPrintf("Memory snapshot for GPU %u:\n",
+ (uint32_t)(TEST_PROC_KEY_1 >> 32))));
+ EXPECT_THAT(dumpsys(),
+ HasSubstr(StringPrintf("Proc %u total: %" PRIu64 "\n", (uint32_t)TEST_PROC_KEY_1,
+ TEST_PROC_VAL_1)));
+ EXPECT_THAT(dumpsys(),
+ HasSubstr(StringPrintf("Memory snapshot for GPU %u:\n",
+ (uint32_t)(TEST_PROC_KEY_2 >> 32))));
+ EXPECT_THAT(dumpsys(),
+ HasSubstr(StringPrintf("Proc %u total: %" PRIu64 "\n", (uint32_t)TEST_PROC_KEY_2,
+ TEST_PROC_VAL_2)));
+}
+
+} // namespace
+} // namespace android
diff --git a/services/gpuservice/tests/unittests/TestableGpuMem.h b/services/gpuservice/tests/unittests/TestableGpuMem.h
new file mode 100644
index 0000000..0e4b01c
--- /dev/null
+++ b/services/gpuservice/tests/unittests/TestableGpuMem.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <bpf/BpfMap.h>
+#include <gpumem/GpuMem.h>
+
+namespace android {
+
+class TestableGpuMem {
+public:
+ TestableGpuMem() = default;
+ explicit TestableGpuMem(GpuMem *gpuMem) : mGpuMem(gpuMem) {}
+
+ void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+ mGpuMem->setGpuMemTotalMap(map);
+ }
+
+ std::string getGpuMemTraceGroup() { return mGpuMem->kGpuMemTraceGroup; }
+
+ std::string getGpuMemTotalTracepoint() { return mGpuMem->kGpuMemTotalTracepoint; }
+
+ std::string getGpuMemTotalProgPath() { return mGpuMem->kGpuMemTotalProgPath; }
+
+ std::string getGpuMemTotalMapPath() { return mGpuMem->kGpuMemTotalMapPath; }
+
+private:
+ GpuMem *mGpuMem;
+};
+
+} // namespace android
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index a98f4b4..d29d8df 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -22,6 +22,7 @@
filegroup {
name: "libinputdispatcher_sources",
srcs: [
+ "AnrTracker.cpp",
"Connection.cpp",
"Entry.cpp",
"InjectionState.cpp",
@@ -67,4 +68,5 @@
export_header_lib_headers: [
"libinputdispatcher_headers",
],
+ logtags: ["EventLogTags.logtags"],
}
diff --git a/services/inputflinger/dispatcher/AnrTracker.cpp b/services/inputflinger/dispatcher/AnrTracker.cpp
new file mode 100644
index 0000000..c3f611e
--- /dev/null
+++ b/services/inputflinger/dispatcher/AnrTracker.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 "AnrTracker.h"
+
+namespace android::inputdispatcher {
+
+template <typename T>
+static T max(const T& a, const T& b) {
+ return a < b ? b : a;
+}
+
+void AnrTracker::insert(nsecs_t timeoutTime, sp<IBinder> token) {
+ mAnrTimeouts.insert(std::make_pair(timeoutTime, std::move(token)));
+}
+
+/**
+ * Erase a single entry only. If there are multiple duplicate entries
+ * (same time, same connection), then only remove one of them.
+ */
+void AnrTracker::erase(nsecs_t timeoutTime, const sp<IBinder>& token) {
+ auto pair = std::make_pair(timeoutTime, token);
+ auto it = mAnrTimeouts.find(pair);
+ if (it != mAnrTimeouts.end()) {
+ mAnrTimeouts.erase(it);
+ }
+}
+
+void AnrTracker::eraseToken(const sp<IBinder>& token) {
+ for (auto it = mAnrTimeouts.begin(); it != mAnrTimeouts.end();) {
+ if (it->second == token) {
+ it = mAnrTimeouts.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+bool AnrTracker::empty() const {
+ return mAnrTimeouts.empty();
+}
+
+// If empty() is false, return the time at which the next connection should cause an ANR
+// If empty() is true, return LONG_LONG_MAX
+nsecs_t AnrTracker::firstTimeout() const {
+ if (mAnrTimeouts.empty()) {
+ return std::numeric_limits<nsecs_t>::max();
+ }
+ return mAnrTimeouts.begin()->first;
+}
+
+const sp<IBinder>& AnrTracker::firstToken() const {
+ return mAnrTimeouts.begin()->second;
+}
+
+void AnrTracker::clear() {
+ mAnrTimeouts.clear();
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/AnrTracker.h b/services/inputflinger/dispatcher/AnrTracker.h
new file mode 100644
index 0000000..097dba5
--- /dev/null
+++ b/services/inputflinger/dispatcher/AnrTracker.h
@@ -0,0 +1,60 @@
+/*
+ * 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_INPUT_INPUTDISPATCHER_ANRTRACKER_H
+#define _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H
+
+#include <binder/IBinder.h>
+#include <utils/Timers.h>
+#include <set>
+
+namespace android::inputdispatcher {
+
+/**
+ * Keeps track of the times when each connection is going to ANR.
+ * Provides the ability to quickly find the connection that is going to cause ANR next.
+ */
+class AnrTracker {
+public:
+ void insert(nsecs_t timeoutTime, sp<IBinder> token);
+ void erase(nsecs_t timeoutTime, const sp<IBinder>& token);
+ void eraseToken(const sp<IBinder>& token);
+ void clear();
+
+ bool empty() const;
+ // If empty() is false, return the time at which the next connection should cause an ANR
+ // If empty() is true, return LONG_LONG_MAX
+ nsecs_t firstTimeout() const;
+ // Return the token of the next connection that should cause an ANR.
+ // Do not call this unless empty() is false, you will encounter undefined behaviour.
+ const sp<IBinder>& firstToken() const;
+
+private:
+ // Optimization: use a multiset to keep track of the event timeouts. When an event is sent
+ // to the InputConsumer, we add an entry to this structure. We look at the smallest value to
+ // determine if any of the connections is unresponsive, and to determine when we should wake
+ // next for the future ANR check.
+ // Using a multiset helps quickly look up the next timeout due.
+ //
+ // We must use a multi-set, because it is plausible (although highly unlikely) to have entries
+ // from the same connection and same timestamp, but different sequence numbers.
+ // We are not tracking sequence numbers, and just allow duplicates to exist.
+ std::multiset<std::pair<nsecs_t /*timeoutTime*/, sp<IBinder> /*connectionToken*/>> mAnrTimeouts;
+};
+
+} // namespace android::inputdispatcher
+
+#endif // _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index 188212b..f5ea563 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -26,8 +26,7 @@
inputChannel(inputChannel),
monitor(monitor),
inputPublisher(inputChannel),
- inputState(idGenerator),
- inputPublisherBlocked(false) {}
+ inputState(idGenerator) {}
Connection::~Connection() {}
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index bb3f2fe..3b33f29 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -47,9 +47,10 @@
InputPublisher inputPublisher;
InputState inputState;
- // True if the socket is full and no further events can be published until
- // the application consumes some of the input.
- bool inputPublisherBlocked;
+ // True if this connection is responsive.
+ // If this connection is not responsive, avoid publishing more events to it until the
+ // application consumes some of the input.
+ bool responsive = true;
// Queue of events that need to be published to the connection.
std::deque<DispatchEntry*> outboundQueue;
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index a1eb007..fdbb1d1 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -28,38 +28,6 @@
namespace android::inputdispatcher {
-static std::string motionActionToString(int32_t action) {
- // Convert MotionEvent action to string
- switch (action & AMOTION_EVENT_ACTION_MASK) {
- case AMOTION_EVENT_ACTION_DOWN:
- return "DOWN";
- case AMOTION_EVENT_ACTION_MOVE:
- return "MOVE";
- case AMOTION_EVENT_ACTION_UP:
- return "UP";
- case AMOTION_EVENT_ACTION_CANCEL:
- return "CANCEL";
- case AMOTION_EVENT_ACTION_POINTER_DOWN:
- return "POINTER_DOWN";
- case AMOTION_EVENT_ACTION_POINTER_UP:
- return "POINTER_UP";
- }
- return StringPrintf("%" PRId32, action);
-}
-
-static std::string keyActionToString(int32_t action) {
- // Convert KeyEvent action to string
- switch (action) {
- case AKEY_EVENT_ACTION_DOWN:
- return "DOWN";
- case AKEY_EVENT_ACTION_UP:
- return "UP";
- case AKEY_EVENT_ACTION_MULTIPLE:
- return "MULTIPLE";
- }
- return StringPrintf("%" PRId32, action);
-}
-
VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry) {
return {{VerifiedInputEvent::Type::KEY, entry.deviceId, entry.eventTime, entry.source,
entry.displayId},
@@ -102,6 +70,12 @@
releaseInjectionState();
}
+std::string EventEntry::getDescription() const {
+ std::string result;
+ appendDescription(result);
+ return result;
+}
+
void EventEntry::release() {
refCount -= 1;
if (refCount == 0) {
@@ -185,7 +159,7 @@
msg += StringPrintf("(deviceId=%d, source=0x%08x, displayId=%" PRId32 ", action=%s, "
"flags=0x%08x, keyCode=%d, scanCode=%d, metaState=0x%08x, "
"repeatCount=%d), policyFlags=0x%08x",
- deviceId, source, displayId, keyActionToString(action).c_str(), flags,
+ deviceId, source, displayId, KeyEvent::actionToString(action), flags,
keyCode, scanCode, metaState, repeatCount, policyFlags);
}
@@ -247,7 +221,7 @@
"buttonState=0x%08x, "
"classification=%s, edgeFlags=0x%08x, xPrecision=%.1f, yPrecision=%.1f, "
"xCursorPosition=%0.1f, yCursorPosition=%0.1f, pointers=[",
- deviceId, source, displayId, motionActionToString(action).c_str(),
+ deviceId, source, displayId, MotionEvent::actionToString(action),
actionButton, flags, metaState, buttonState,
motionClassificationToString(classification), edgeFlags, xPrecision,
yPrecision, xCursorPosition, yCursorPosition);
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index ab481bd..6b7697d 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -83,6 +83,8 @@
virtual void appendDescription(std::string& msg) const = 0;
+ std::string getDescription() const;
+
protected:
EventEntry(int32_t id, Type type, nsecs_t eventTime, uint32_t policyFlags);
virtual ~EventEntry();
@@ -196,7 +198,11 @@
float globalScaleFactor;
float windowXScale = 1.0f;
float windowYScale = 1.0f;
+ // Both deliveryTime and timeoutTime are only populated when the entry is sent to the app,
+ // and will be undefined before that.
nsecs_t deliveryTime; // time when the event was actually delivered
+ // An ANR will be triggered if a response for this entry is not received by timeoutTime
+ nsecs_t timeoutTime;
// Set to the resolved ID, action and flags when the event is enqueued.
int32_t resolvedEventId;
diff --git a/services/inputflinger/dispatcher/EventLogTags.logtags b/services/inputflinger/dispatcher/EventLogTags.logtags
new file mode 100644
index 0000000..9c0f80e
--- /dev/null
+++ b/services/inputflinger/dispatcher/EventLogTags.logtags
@@ -0,0 +1,42 @@
+# The entries in this file map a sparse set of log tag numbers to tag names.
+# This is installed on the device, in /system/etc, and parsed by logcat.
+#
+# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the
+# negative values alone for now.)
+#
+# Tag names are one or more ASCII letters and numbers or underscores, i.e.
+# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former
+# impacts log readability, the latter makes regex searches more annoying).
+#
+# Tag numbers and names are separated by whitespace. Blank lines and lines
+# starting with '#' are ignored.
+#
+# Optionally, after the tag names can be put a description for the value(s)
+# of the tag. Description are in the format
+# (<name>|data type[|data unit])
+# Multiple values are separated by commas.
+#
+# The data type is a number from the following values:
+# 1: int
+# 2: long
+# 3: string
+# 4: list
+#
+# The data unit is a number taken from the following list:
+# 1: Number of objects
+# 2: Number of bytes
+# 3: Number of milliseconds
+# 4: Number of allocations
+# 5: Id
+# 6: Percent
+# Default value for data of type int/long is 2 (bytes).
+#
+# See system/core/logcat/event.logtags for the master copy of the tags.
+
+# 62000 - 62199 reserved for inputflinger
+
+62000 input_interaction (windows|4)
+62001 input_focus (window|3)
+
+# NOTE - the range 1000000-2000000 is reserved for partners and others who
+# want to define their own log tags without conflicting with the core platform.
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 1d5870a..c139dc5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -62,6 +62,7 @@
#include <binder/Binder.h>
#include <input/InputDevice.h>
#include <log/log.h>
+#include <log/log_event_list.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <powermanager/PowerManager.h>
@@ -78,7 +79,7 @@
// Default input dispatching timeout if there is no focused application or paused window
// from which to determine an appropriate dispatching timeout.
-constexpr nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+constexpr std::chrono::nanoseconds DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s;
// Amount of time to allow for all pending events to be processed when an app switch
// key is on the way. This is used to preempt input dispatch and drop input events
@@ -89,21 +90,24 @@
// before considering it stale and dropping it.
constexpr nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
-// Amount of time to allow touch events to be streamed out to a connection before requiring
-// that the first event be finished. This value extends the ANR timeout by the specified
-// amount. For example, if streaming is allowed to get ahead by one second relative to the
-// queue of waiting unfinished events, then ANRs will similarly be delayed by one second.
-constexpr nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec
-
// Log a warning when an event takes longer than this to process, even if an ANR does not occur.
constexpr nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
// Log a warning when an interception call takes longer than this to process.
constexpr std::chrono::milliseconds SLOW_INTERCEPTION_THRESHOLD = 50ms;
+// Additional key latency in case a connection is still processing some motion events.
+// This will help with the case when a user touched a button that opens a new window,
+// and gives us the chance to dispatch the key to this new window.
+constexpr std::chrono::nanoseconds KEY_WAITING_FOR_EVENTS_TIMEOUT = 500ms;
+
// Number of recent events to keep for debugging purposes.
constexpr size_t RECENT_QUEUE_MAX_SIZE = 10;
+// Event log tags. See EventLogTags.logtags for reference
+constexpr int LOGTAG_INPUT_INTERACTION = 62000;
+constexpr int LOGTAG_INPUT_FOCUS = 62001;
+
static inline nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
@@ -404,8 +408,7 @@
// To avoid leaking stack in case that call never comes, and for tests,
// initialize it here anyways.
mInTouchMode(true),
- mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
- mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+ mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -465,6 +468,11 @@
nextWakeupTime = LONG_LONG_MIN;
}
+ // If we are still waiting for ack on some events,
+ // we might have to wake up earlier to check if an app is anr'ing.
+ const nsecs_t nextAnrCheck = processAnrsLocked();
+ nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
+
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
if (nextWakeupTime == LONG_LONG_MAX) {
@@ -478,6 +486,55 @@
mLooper->pollOnce(timeoutMillis);
}
+/**
+ * Check if any of the connections' wait queues have events that are too old.
+ * If we waited for events to be ack'ed for more than the window timeout, raise an ANR.
+ * Return the time at which we should wake up next.
+ */
+nsecs_t InputDispatcher::processAnrsLocked() {
+ const nsecs_t currentTime = now();
+ nsecs_t nextAnrCheck = LONG_LONG_MAX;
+ // Check if we are waiting for a focused window to appear. Raise ANR if waited too long
+ if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
+ if (currentTime >= *mNoFocusedWindowTimeoutTime) {
+ onAnrLocked(mAwaitedFocusedApplication);
+ mAwaitedFocusedApplication.clear();
+ return LONG_LONG_MIN;
+ } else {
+ // Keep waiting
+ const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime);
+ ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining);
+ nextAnrCheck = *mNoFocusedWindowTimeoutTime;
+ }
+ }
+
+ // Check if any connection ANRs are due
+ nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
+ if (currentTime < nextAnrCheck) { // most likely scenario
+ return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
+ }
+
+ // If we reached here, we have an unresponsive connection.
+ sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
+ if (connection == nullptr) {
+ ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
+ return nextAnrCheck;
+ }
+ connection->responsive = false;
+ // Stop waking up for this unresponsive connection
+ mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
+ onAnrLocked(connection);
+ return LONG_LONG_MIN;
+}
+
+nsecs_t InputDispatcher::getDispatchingTimeoutLocked(const sp<IBinder>& token) {
+ sp<InputWindowHandle> window = getWindowHandleLocked(token);
+ if (window != nullptr) {
+ return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT).count();
+ }
+ return DEFAULT_INPUT_DISPATCHING_TIMEOUT.count();
+}
+
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
nsecs_t currentTime = now();
@@ -541,9 +598,6 @@
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(*mPendingEvent);
}
-
- // Get ready to dispatch the event.
- resetAnrTimeoutsLocked();
}
// Now we have an event to dispatch.
@@ -632,6 +686,66 @@
}
}
+/**
+ * Return true if the events preceding this incoming motion event should be dropped
+ * Return false otherwise (the default behaviour)
+ */
+bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) {
+ const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN &&
+ (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER);
+
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
+ int32_t displayId = motionEntry.displayId;
+ int32_t x = static_cast<int32_t>(
+ motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = static_cast<int32_t>(
+ motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> touchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y, nullptr);
+ if (touchedWindowHandle != nullptr &&
+ touchedWindowHandle->getApplicationToken() !=
+ mAwaitedFocusedApplication->getApplicationToken()) {
+ // User touched a different application than the one we are waiting on.
+ ALOGI("Pruning input queue because user touched a different application while waiting "
+ "for %s",
+ mAwaitedFocusedApplication->getName().c_str());
+ return true;
+ }
+
+ // Alternatively, maybe there's a gesture monitor that could handle this event
+ std::vector<TouchedMonitor> gestureMonitors =
+ findTouchedGestureMonitorsLocked(displayId, {});
+ for (TouchedMonitor& gestureMonitor : gestureMonitors) {
+ sp<Connection> connection =
+ getConnectionLocked(gestureMonitor.monitor.inputChannel->getConnectionToken());
+ if (connection->responsive) {
+ // This monitor could take more input. Drop all events preceding this
+ // event, so that gesture monitor could get a chance to receive the stream
+ ALOGW("Pruning the input queue because %s is unresponsive, but we have a "
+ "responsive gesture monitor that may handle the event",
+ mAwaitedFocusedApplication->getName().c_str());
+ return true;
+ }
+ }
+ }
+
+ // Prevent getting stuck: if we have a pending key event, and some motion events that have not
+ // yet been processed by some connections, the dispatcher will wait for these motion
+ // events to be processed before dispatching the key event. This is because these motion events
+ // may cause a new window to be launched, which the user might expect to receive focus.
+ // To prevent waiting forever for such events, just send the key to the currently focused window
+ if (isPointerDownEvent && mKeyIsWaitingForEventsTimeout) {
+ ALOGD("Received a new pointer down event, stop waiting for events to process and "
+ "just send the pending key event to the focused window.");
+ mKeyIsWaitingForEventsTimeout = now();
+ }
+ return false;
+}
+
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
bool needWake = mInboundQueue.empty();
mInboundQueue.push_back(entry);
@@ -661,36 +775,18 @@
}
case EventEntry::Type::MOTION: {
- // Optimize case where the current application is unresponsive and the user
- // decides to touch a window in a different application.
- // If the application takes too long to catch up then we drop all events preceding
- // the touch into the other window.
- MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
- if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN &&
- (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) &&
- mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY &&
- mInputTargetWaitApplicationToken != nullptr) {
- int32_t displayId = motionEntry->displayId;
- int32_t x =
- int32_t(motionEntry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
- int32_t y =
- int32_t(motionEntry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
- sp<InputWindowHandle> touchedWindowHandle =
- findTouchedWindowAtLocked(displayId, x, y, nullptr);
- if (touchedWindowHandle != nullptr &&
- touchedWindowHandle->getApplicationToken() !=
- mInputTargetWaitApplicationToken) {
- // User touched a different application than the one we are waiting on.
- // Flag the event, and start pruning the input queue.
- mNextUnblockedEvent = motionEntry;
- needWake = true;
- }
+ if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(*entry))) {
+ mNextUnblockedEvent = entry;
+ needWake = true;
}
break;
}
- case EventEntry::Type::CONFIGURATION_CHANGED:
- case EventEntry::Type::DEVICE_RESET:
case EventEntry::Type::FOCUS: {
+ LOG_ALWAYS_FATAL("Focus events should be inserted using enqueueFocusEventLocked");
+ break;
+ }
+ case EventEntry::Type::CONFIGURATION_CHANGED:
+ case EventEntry::Type::DEVICE_RESET: {
// nothing to do
break;
}
@@ -893,7 +989,6 @@
void InputDispatcher::releasePendingEventLocked() {
if (mPendingEvent) {
- resetAnrTimeoutsLocked();
releaseInboundEventLocked(mPendingEvent);
mPendingEvent = nullptr;
}
@@ -985,9 +1080,24 @@
}
void InputDispatcher::enqueueFocusEventLocked(const InputWindowHandle& window, bool hasFocus) {
+ if (mPendingEvent != nullptr) {
+ // Move the pending event to the front of the queue. This will give the chance
+ // for the pending event to get dispatched to the newly focused window
+ mInboundQueue.push_front(mPendingEvent);
+ mPendingEvent = nullptr;
+ }
+
FocusEntry* focusEntry =
new FocusEntry(mIdGenerator.nextId(), now(), window.getToken(), hasFocus);
- enqueueInboundEventLocked(focusEntry);
+
+ // This event should go to the front of the queue, but behind all other focus events
+ // Find the last focus event, and insert right after it
+ std::deque<EventEntry*>::reverse_iterator it =
+ std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),
+ [](EventEntry* event) { return event->type == EventEntry::Type::FOCUS; });
+
+ // Maintain the order of focus events. Insert the entry after all other focus events.
+ mInboundQueue.insert(it.base(), focusEntry);
}
void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, FocusEntry* entry) {
@@ -999,7 +1109,9 @@
target.inputChannel = channel;
target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
entry->dispatchInProgress = true;
-
+ std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
+ channel->getName();
+ android_log_event_list(LOGTAG_INPUT_FOCUS) << message << LOG_ID_EVENTS;
dispatchEventLocked(currentTime, entry, {target});
}
@@ -1237,6 +1349,8 @@
ALOGD("dispatchEventToCurrentInputTargets");
#endif
+ updateInteractionTokensLocked(*eventEntry, inputTargets);
+
ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
pokeUserActivityLocked(*eventEntry);
@@ -1256,109 +1370,29 @@
}
}
-int32_t InputDispatcher::handleTargetsNotReadyLocked(
- nsecs_t currentTime, const EventEntry& entry,
- const sp<InputApplicationHandle>& applicationHandle,
- const sp<InputWindowHandle>& windowHandle, nsecs_t* nextWakeupTime, const char* reason) {
- if (applicationHandle == nullptr && windowHandle == nullptr) {
- if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
- if (DEBUG_FOCUS) {
- ALOGD("Waiting for system to become ready for input. Reason: %s", reason);
- }
- mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
- mInputTargetWaitStartTime = currentTime;
- mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
- mInputTargetWaitTimeoutExpired = false;
- mInputTargetWaitApplicationToken.clear();
- }
- } else {
- if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
- ALOGI("Waiting for application to become ready for input: %s. Reason: %s",
- getApplicationWindowLabel(applicationHandle, windowHandle).c_str(), reason);
- nsecs_t timeout;
- if (windowHandle != nullptr) {
- timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
- } else if (applicationHandle != nullptr) {
- timeout =
- applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
- } else {
- timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
- }
-
- mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
- mInputTargetWaitStartTime = currentTime;
- mInputTargetWaitTimeoutTime = currentTime + timeout;
- mInputTargetWaitTimeoutExpired = false;
- mInputTargetWaitApplicationToken.clear();
-
- if (windowHandle != nullptr) {
- mInputTargetWaitApplicationToken = windowHandle->getApplicationToken();
- }
- if (mInputTargetWaitApplicationToken == nullptr && applicationHandle != nullptr) {
- mInputTargetWaitApplicationToken = applicationHandle->getApplicationToken();
- }
- }
- }
-
- if (mInputTargetWaitTimeoutExpired) {
- return INPUT_EVENT_INJECTION_TIMED_OUT;
- }
-
- if (currentTime >= mInputTargetWaitTimeoutTime) {
- onAnrLocked(currentTime, applicationHandle, windowHandle, entry.eventTime,
- mInputTargetWaitStartTime, reason);
-
- // Force poll loop to wake up immediately on next iteration once we get the
- // ANR response back from the policy.
- *nextWakeupTime = LONG_LONG_MIN;
- return INPUT_EVENT_INJECTION_PENDING;
- } else {
- // Force poll loop to wake up when timeout is due.
- if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
- *nextWakeupTime = mInputTargetWaitTimeoutTime;
- }
- return INPUT_EVENT_INJECTION_PENDING;
+void InputDispatcher::cancelEventsForAnrLocked(const sp<Connection>& connection) {
+ // We will not be breaking any connections here, even if the policy wants us to abort dispatch.
+ // If the policy decides to close the app, we will get a channel removal event via
+ // unregisterInputChannel, and will clean up the connection that way. We are already not
+ // sending new pointers to the connection when it blocked, but focused events will continue to
+ // pile up.
+ ALOGW("Canceling events for %s because it is unresponsive",
+ connection->inputChannel->getName().c_str());
+ if (connection->status == Connection::STATUS_NORMAL) {
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "application not responding");
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
}
}
-void InputDispatcher::removeWindowByTokenLocked(const sp<IBinder>& token) {
- for (std::pair<const int32_t, TouchState>& pair : mTouchStatesByDisplay) {
- TouchState& state = pair.second;
- state.removeWindowByToken(token);
- }
-}
-
-void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(
- nsecs_t newTimeout, const sp<IBinder>& inputConnectionToken) {
- if (newTimeout > 0) {
- // Extend the timeout.
- mInputTargetWaitTimeoutTime = now() + newTimeout;
- } else {
- // Give up.
- mInputTargetWaitTimeoutExpired = true;
-
- // Input state will not be realistic. Mark it out of sync.
- sp<Connection> connection = getConnectionLocked(inputConnectionToken);
- if (connection != nullptr) {
- removeWindowByTokenLocked(inputConnectionToken);
-
- if (connection->status == Connection::STATUS_NORMAL) {
- CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
- "application not responding");
- synthesizeCancelationEventsForConnectionLocked(connection, options);
- }
- }
- }
-}
-
-void InputDispatcher::resetAnrTimeoutsLocked() {
+void InputDispatcher::resetNoFocusedWindowTimeoutLocked() {
if (DEBUG_FOCUS) {
ALOGD("Resetting ANR timeouts.");
}
// Reset input target wait timeout.
- mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
- mInputTargetWaitApplicationToken.clear();
+ mNoFocusedWindowTimeoutTime = std::nullopt;
+ mAwaitedFocusedApplication.clear();
}
/**
@@ -1389,6 +1423,36 @@
return displayId == ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId;
}
+bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime,
+ const char* focusedWindowName) {
+ if (mAnrTracker.empty()) {
+ // already processed all events that we waited for
+ mKeyIsWaitingForEventsTimeout = std::nullopt;
+ return false;
+ }
+
+ if (!mKeyIsWaitingForEventsTimeout.has_value()) {
+ // Start the timer
+ ALOGD("Waiting to send key to %s because there are unprocessed events that may cause "
+ "focus to change",
+ focusedWindowName);
+ mKeyIsWaitingForEventsTimeout = currentTime + KEY_WAITING_FOR_EVENTS_TIMEOUT.count();
+ return true;
+ }
+
+ // We still have pending events, and already started the timer
+ if (currentTime < *mKeyIsWaitingForEventsTimeout) {
+ return true; // Still waiting
+ }
+
+ // Waited too long, and some connection still hasn't processed all motions
+ // Just send the key to the focused window
+ ALOGW("Dispatching key to %s even though there are other unprocessed events",
+ focusedWindowName);
+ mKeyIsWaitingForEventsTimeout = std::nullopt;
+ return false;
+}
+
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry& entry,
std::vector<InputTarget>& inputTargets,
@@ -1403,31 +1467,70 @@
// If there is no currently focused window and no focused application
// then drop the event.
- if (focusedWindowHandle == nullptr) {
- if (focusedApplicationHandle != nullptr) {
- return handleTargetsNotReadyLocked(currentTime, entry, focusedApplicationHandle,
- nullptr, nextWakeupTime,
- "Waiting because no window has focus but there is "
- "a focused application that may eventually add a "
- "window when it finishes starting up.");
- }
-
- ALOGI("Dropping event because there is no focused window or focused application in display "
- "%" PRId32 ".",
- displayId);
+ 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);
return INPUT_EVENT_INJECTION_FAILED;
}
+ // Compatibility behavior: raise ANR if there is a focused application, but no focused window.
+ // Only start counting when we have a focused event to dispatch. The ANR is canceled if we
+ // start interacting with another application via touch (app switch). This code can be removed
+ // if the "no focused window ANR" is moved to the policy. Input doesn't know whether
+ // an app is expected to have a focused window.
+ if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
+ if (!mNoFocusedWindowTimeoutTime.has_value()) {
+ // We just discovered that there's no focused window. Start the ANR timer
+ const nsecs_t timeout = focusedApplicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT.count());
+ mNoFocusedWindowTimeoutTime = currentTime + timeout;
+ mAwaitedFocusedApplication = focusedApplicationHandle;
+ ALOGW("Waiting because no window has focus but %s may eventually add a "
+ "window when it finishes starting up. Will wait for %" PRId64 "ms",
+ mAwaitedFocusedApplication->getName().c_str(), ns2ms(timeout));
+ *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
+ return INPUT_EVENT_INJECTION_PENDING;
+ } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
+ // Already raised ANR. Drop the event
+ ALOGE("Dropping %s event because there is no focused window",
+ EventEntry::typeToString(entry.type));
+ return INPUT_EVENT_INJECTION_FAILED;
+ } else {
+ // Still waiting for the focused window
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+ }
+
+ // we have a valid, non-null focused window
+ resetNoFocusedWindowTimeoutLocked();
+
// Check permissions.
if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
return INPUT_EVENT_INJECTION_PERMISSION_DENIED;
}
- // Check whether the window is ready for more input.
- reason = checkWindowReadyForMoreInputLocked(currentTime, focusedWindowHandle, entry, "focused");
- if (!reason.empty()) {
- return handleTargetsNotReadyLocked(currentTime, entry, focusedApplicationHandle,
- focusedWindowHandle, nextWakeupTime, reason.c_str());
+ if (focusedWindowHandle->getInfo()->paused) {
+ ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+
+ // If the event is a key event, then we must wait for all previous events to
+ // complete before delivering it because previous events may have the
+ // side-effect of transferring focus to a different window and we want to
+ // ensure that the following keys are sent to the new window.
+ //
+ // Suppose the user touches a button in a window then immediately presses "A".
+ // If the button causes a pop-up window to appear then we want to ensure that
+ // the "A" key is delivered to the new pop-up window. This is because users
+ // often anticipate pending UI changes when typing on a keyboard.
+ // To obtain this behavior, we must serialize key events with respect to all
+ // prior input events.
+ if (entry.type == EventEntry::Type::KEY) {
+ if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
+ *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
}
// Success! Output targets.
@@ -1439,6 +1542,32 @@
return INPUT_EVENT_INJECTION_SUCCEEDED;
}
+/**
+ * Given a list of monitors, remove the ones we cannot find a connection for, and the ones
+ * that are currently unresponsive.
+ */
+std::vector<TouchedMonitor> InputDispatcher::selectResponsiveMonitorsLocked(
+ const std::vector<TouchedMonitor>& monitors) const {
+ std::vector<TouchedMonitor> responsiveMonitors;
+ std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
+ [this](const TouchedMonitor& monitor) REQUIRES(mLock) {
+ sp<Connection> connection = getConnectionLocked(
+ monitor.monitor.inputChannel->getConnectionToken());
+ if (connection == nullptr) {
+ ALOGE("Could not find connection for monitor %s",
+ monitor.monitor.inputChannel->getName().c_str());
+ return false;
+ }
+ if (!connection->responsive) {
+ ALOGW("Unresponsive monitor %s will not get the new gesture",
+ connection->inputChannel->getName().c_str());
+ return false;
+ }
+ return true;
+ });
+ return responsiveMonitors;
+}
+
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry& entry,
std::vector<InputTarget>& inputTargets,
@@ -1554,6 +1683,29 @@
newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
}
+ if (newTouchedWindowHandle != nullptr && newTouchedWindowHandle->getInfo()->paused) {
+ ALOGI("Not sending touch event to %s because it is paused",
+ newTouchedWindowHandle->getName().c_str());
+ newTouchedWindowHandle = nullptr;
+ }
+
+ if (newTouchedWindowHandle != nullptr) {
+ sp<Connection> connection = getConnectionLocked(newTouchedWindowHandle->getToken());
+ if (connection == nullptr) {
+ ALOGI("Could not find connection for %s",
+ newTouchedWindowHandle->getName().c_str());
+ newTouchedWindowHandle = nullptr;
+ } else if (!connection->responsive) {
+ // don't send the new touch to an unresponsive window
+ ALOGW("Unresponsive window %s will not get the new gesture at %" PRIu64,
+ newTouchedWindowHandle->getName().c_str(), entry.eventTime);
+ newTouchedWindowHandle = nullptr;
+ }
+ }
+
+ // Also don't send the new touch event to unresponsive gesture monitors
+ newGestureMonitors = selectResponsiveMonitorsLocked(newGestureMonitors);
+
if (newTouchedWindowHandle == nullptr && newGestureMonitors.empty()) {
ALOGI("Dropping event because there is no touchable window or gesture monitor at "
"(%d, %d) in display %" PRId32 ".",
@@ -1720,21 +1872,6 @@
}
}
- // Ensure all touched foreground windows are ready for new input.
- for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
- // Check whether the window is ready for more input.
- std::string reason =
- checkWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle,
- entry, "touched");
- if (!reason.empty()) {
- return handleTargetsNotReadyLocked(currentTime, entry, nullptr,
- touchedWindow.windowHandle, nextWakeupTime,
- reason.c_str());
- }
- }
- }
-
// If this is the first pointer going down and the touched window has a wallpaper
// then also add the touched wallpaper windows so they are locked in for the duration
// of the touch gesture.
@@ -1969,7 +2106,7 @@
// windows with an input channel from the same PID as occluding, and so we
// preserve this behavior with the getToken() == null check.
return false;
- } else if (otherInfo->isTrustedOverlay()) {
+ } else if (otherInfo->trustedOverlay) {
return false;
} else if (otherInfo->displayId != info->displayId) {
return false;
@@ -2011,106 +2148,17 @@
return false;
}
-std::string InputDispatcher::checkWindowReadyForMoreInputLocked(
- nsecs_t currentTime, const sp<InputWindowHandle>& windowHandle,
- const EventEntry& eventEntry, const char* targetType) {
- // If the window is paused then keep waiting.
- if (windowHandle->getInfo()->paused) {
- return StringPrintf("Waiting because the %s window is paused.", targetType);
- }
-
- // If the window's connection is not registered then keep waiting.
- sp<Connection> connection = getConnectionLocked(windowHandle->getToken());
- if (connection == nullptr) {
- return StringPrintf("Waiting because the %s window's input channel is not "
- "registered with the input dispatcher. The window may be in the "
- "process of being removed.",
- targetType);
- }
-
- // If the connection is dead then keep waiting.
- if (connection->status != Connection::STATUS_NORMAL) {
- return StringPrintf("Waiting because the %s window's input connection is %s."
- "The window may be in the process of being removed.",
- targetType, connection->getStatusLabel());
- }
-
- // If the connection is backed up then keep waiting.
- if (connection->inputPublisherBlocked) {
- return StringPrintf("Waiting because the %s window's input channel is full. "
- "Outbound queue length: %zu. Wait queue length: %zu.",
- targetType, connection->outboundQueue.size(),
- connection->waitQueue.size());
- }
-
- // Ensure that the dispatch queues aren't too far backed up for this event.
- if (eventEntry.type == EventEntry::Type::KEY) {
- // If the event is a key event, then we must wait for all previous events to
- // complete before delivering it because previous events may have the
- // side-effect of transferring focus to a different window and we want to
- // ensure that the following keys are sent to the new window.
- //
- // Suppose the user touches a button in a window then immediately presses "A".
- // If the button causes a pop-up window to appear then we want to ensure that
- // the "A" key is delivered to the new pop-up window. This is because users
- // often anticipate pending UI changes when typing on a keyboard.
- // To obtain this behavior, we must serialize key events with respect to all
- // prior input events.
- if (!connection->outboundQueue.empty() || !connection->waitQueue.empty()) {
- return StringPrintf("Waiting to send key event because the %s window has not "
- "finished processing all of the input events that were previously "
- "delivered to it. Outbound queue length: %zu. Wait queue length: "
- "%zu.",
- targetType, connection->outboundQueue.size(),
- connection->waitQueue.size());
- }
- } else {
- // Touch events can always be sent to a window immediately because the user intended
- // to touch whatever was visible at the time. Even if focus changes or a new
- // window appears moments later, the touch event was meant to be delivered to
- // whatever window happened to be on screen at the time.
- //
- // Generic motion events, such as trackball or joystick events are a little trickier.
- // Like key events, generic motion events are delivered to the focused window.
- // Unlike key events, generic motion events don't tend to transfer focus to other
- // windows and it is not important for them to be serialized. So we prefer to deliver
- // generic motion events as soon as possible to improve efficiency and reduce lag
- // through batching.
- //
- // The one case where we pause input event delivery is when the wait queue is piling
- // up with lots of events because the application is not responding.
- // This condition ensures that ANRs are detected reliably.
- if (!connection->waitQueue.empty() &&
- currentTime >=
- connection->waitQueue.front()->deliveryTime + STREAM_AHEAD_EVENT_TIMEOUT) {
- return StringPrintf("Waiting to send non-key event because the %s window has not "
- "finished processing certain input events that were delivered to "
- "it over "
- "%0.1fms ago. Wait queue length: %zu. Wait queue head age: "
- "%0.1fms.",
- targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
- connection->waitQueue.size(),
- (currentTime - connection->waitQueue.front()->deliveryTime) *
- 0.000001f);
- }
- }
- return "";
-}
-
std::string InputDispatcher::getApplicationWindowLabel(
const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle) {
if (applicationHandle != nullptr) {
if (windowHandle != nullptr) {
- std::string label(applicationHandle->getName());
- label += " - ";
- label += windowHandle->getName();
- return label;
+ return applicationHandle->getName() + " - " + windowHandle->getName();
} else {
return applicationHandle->getName();
}
} else if (windowHandle != nullptr) {
- return windowHandle->getName();
+ return windowHandle->getInfo()->applicationInfo.name + " - " + windowHandle->getName();
} else {
return "<unknown application or window>";
}
@@ -2390,6 +2438,73 @@
traceOutboundQueueLength(connection);
}
+/**
+ * This function is purely for debugging. It helps us understand where the user interaction
+ * was taking place. For example, if user is touching launcher, we will see a log that user
+ * started interacting with launcher. In that example, the event would go to the wallpaper as well.
+ * We will see both launcher and wallpaper in that list.
+ * Once the interaction with a particular set of connections starts, no new logs will be printed
+ * until the set of interacted connections changes.
+ *
+ * The following items are skipped, to reduce the logspam:
+ * ACTION_OUTSIDE: any windows that are receiving ACTION_OUTSIDE are not logged
+ * ACTION_UP: any windows that receive ACTION_UP are not logged (for both keys and motions).
+ * This includes situations like the soft BACK button key. When the user releases (lifts up the
+ * finger) the back button, then navigation bar will inject KEYCODE_BACK with ACTION_UP.
+ * Both of those ACTION_UP events would not be logged
+ * Monitors (both gesture and global): any gesture monitors or global monitors receiving events
+ * will not be logged. This is omitted to reduce the amount of data printed.
+ * If you see <none>, it's likely that one of the gesture monitors pilfered the event, and therefore
+ * gesture monitor is the only connection receiving the remainder of the gesture.
+ */
+void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry,
+ const std::vector<InputTarget>& targets) {
+ // Skip ACTION_UP events, and all events other than keys and motions
+ if (entry.type == EventEntry::Type::KEY) {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+ if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
+ return;
+ }
+ } else if (entry.type == EventEntry::Type::MOTION) {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
+ if (motionEntry.action == AMOTION_EVENT_ACTION_UP) {
+ return;
+ }
+ } else {
+ return; // Not a key or a motion
+ }
+
+ std::unordered_set<sp<IBinder>, IBinderHash> newConnections;
+ for (const InputTarget& target : targets) {
+ if ((target.flags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) ==
+ InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ continue; // Skip windows that receive ACTION_OUTSIDE
+ }
+
+ sp<IBinder> token = target.inputChannel->getConnectionToken();
+ sp<Connection> connection = getConnectionLocked(token); // get connection
+ if (connection->monitor) {
+ continue; // We only need to keep track of the non-monitor connections.
+ }
+
+ newConnections.insert(std::move(token));
+ }
+ if (newConnections == mInteractionConnections) {
+ return; // no change
+ }
+ mInteractionConnections = newConnections;
+ std::string windowList;
+ for (const sp<IBinder>& token : newConnections) {
+ sp<Connection> connection = getConnectionLocked(token);
+ windowList += connection->getWindowName() + ", ";
+ }
+ std::string message = "Interaction with windows: " + windowList;
+ if (windowList.empty()) {
+ message += "<none>";
+ }
+ android_log_event_list(LOGTAG_INPUT_INTERACTION) << message << LOG_ID_EVENTS;
+}
+
void InputDispatcher::dispatchPointerDownOutsideFocus(uint32_t source, int32_t action,
const sp<IBinder>& newToken) {
int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
@@ -2432,6 +2547,9 @@
while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.front();
dispatchEntry->deliveryTime = currentTime;
+ const nsecs_t timeout =
+ getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
+ dispatchEntry->timeoutTime = currentTime + timeout;
// Publish the event.
status_t status;
@@ -2551,7 +2669,6 @@
"waiting for the application to catch up",
connection->getInputChannelName().c_str());
#endif
- connection->inputPublisherBlocked = true;
}
} else {
ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
@@ -2568,6 +2685,10 @@
dispatchEntry));
traceOutboundQueueLength(connection);
connection->waitQueue.push_back(dispatchEntry);
+ if (connection->responsive) {
+ mAnrTracker.insert(dispatchEntry->timeoutTime,
+ connection->inputChannel->getConnectionToken());
+ }
traceWaitQueueLength(connection);
}
}
@@ -2602,8 +2723,6 @@
connection->getInputChannelName().c_str(), seq, toString(handled));
#endif
- connection->inputPublisherBlocked = false;
-
if (connection->status == Connection::STATUS_BROKEN ||
connection->status == Connection::STATUS_ZOMBIE) {
return;
@@ -3611,7 +3730,8 @@
continue;
}
- if (oldHandlesById.find(handle->getId()) != oldHandlesById.end()) {
+ if ((oldHandlesById.find(handle->getId()) != oldHandlesById.end()) &&
+ (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
const sp<InputWindowHandle>& oldHandle = oldHandlesById.at(handle->getId());
oldHandle->updateFrom(handle);
newHandles.push_back(oldHandle);
@@ -3758,15 +3878,17 @@
sp<InputApplicationHandle> oldFocusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
+
+ if (oldFocusedApplicationHandle == mAwaitedFocusedApplication &&
+ inputApplicationHandle != oldFocusedApplicationHandle) {
+ resetNoFocusedWindowTimeoutLocked();
+ }
+
if (inputApplicationHandle != nullptr && inputApplicationHandle->updateInfo()) {
if (oldFocusedApplicationHandle != inputApplicationHandle) {
- if (oldFocusedApplicationHandle != nullptr) {
- resetAnrTimeoutsLocked();
- }
mFocusedApplicationHandlesByDisplay[displayId] = inputApplicationHandle;
}
} else if (oldFocusedApplicationHandle != nullptr) {
- resetAnrTimeoutsLocked();
oldFocusedApplicationHandle.clear();
mFocusedApplicationHandlesByDisplay.erase(displayId);
}
@@ -3847,7 +3969,7 @@
if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
if (mDispatchFrozen && !frozen) {
- resetAnrTimeoutsLocked();
+ resetNoFocusedWindowTimeoutLocked();
}
if (mDispatchEnabled && !enabled) {
@@ -3987,8 +4109,9 @@
resetKeyRepeatLocked();
releasePendingEventLocked();
drainInboundQueueLocked();
- resetAnrTimeoutsLocked();
+ resetNoFocusedWindowTimeoutLocked();
+ mAnrTracker.clear();
mTouchStatesByDisplay.clear();
mLastHoverWindowHandle.clear();
mReplacedKeys.clear();
@@ -4018,11 +4141,12 @@
const int32_t displayId = it.first;
const sp<InputApplicationHandle>& applicationHandle = it.second;
dump += StringPrintf(INDENT2 "displayId=%" PRId32
- ", name='%s', dispatchingTimeout=%0.3fms\n",
+ ", name='%s', dispatchingTimeout=%" PRId64 "ms\n",
displayId, applicationHandle->getName().c_str(),
- applicationHandle->getDispatchingTimeout(
- DEFAULT_INPUT_DISPATCHING_TIMEOUT) /
- 1000000.0);
+ ns2ms(applicationHandle
+ ->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT)
+ .count()));
}
} else {
dump += StringPrintf(INDENT "FocusedApplications: <none>\n");
@@ -4102,9 +4226,10 @@
windowInfo->windowXScale, windowInfo->windowYScale);
dumpRegion(dump, windowInfo->touchableRegion);
dump += StringPrintf(", inputFeatures=0x%08x", windowInfo->inputFeatures);
- dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
+ "ms\n",
windowInfo->ownerPid, windowInfo->ownerUid,
- windowInfo->dispatchingTimeout / 1000000.0);
+ ns2ms(windowInfo->dispatchingTimeout));
dump += StringPrintf(INDENT4 " flags: %s\n",
inputWindowFlagsToString(windowInfo->layoutParamsFlags)
.c_str());
@@ -4140,7 +4265,7 @@
for (EventEntry* entry : mRecentQueue) {
dump += INDENT2;
entry->appendDescription(dump);
- dump += StringPrintf(", age=%0.1fms\n", (currentTime - entry->eventTime) * 0.000001f);
+ dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
}
} else {
dump += INDENT "RecentQueue: <empty>\n";
@@ -4151,8 +4276,8 @@
dump += INDENT "PendingEvent:\n";
dump += INDENT2;
mPendingEvent->appendDescription(dump);
- dump += StringPrintf(", age=%0.1fms\n",
- (currentTime - mPendingEvent->eventTime) * 0.000001f);
+ dump += StringPrintf(", age=%" PRId64 "ms\n",
+ ns2ms(currentTime - mPendingEvent->eventTime));
} else {
dump += INDENT "PendingEvent: <none>\n";
}
@@ -4163,7 +4288,7 @@
for (EventEntry* entry : mInboundQueue) {
dump += INDENT2;
entry->appendDescription(dump);
- dump += StringPrintf(", age=%0.1fms\n", (currentTime - entry->eventTime) * 0.000001f);
+ dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
}
} else {
dump += INDENT "InboundQueue: <empty>\n";
@@ -4186,11 +4311,10 @@
for (const auto& pair : mConnectionsByFd) {
const sp<Connection>& connection = pair.second;
dump += StringPrintf(INDENT2 "%i: channelName='%s', windowName='%s', "
- "status=%s, monitor=%s, inputPublisherBlocked=%s\n",
+ "status=%s, monitor=%s, responsive=%s\n",
pair.first, connection->getInputChannelName().c_str(),
connection->getWindowName().c_str(), connection->getStatusLabel(),
- toString(connection->monitor),
- toString(connection->inputPublisherBlocked));
+ toString(connection->monitor), toString(connection->responsive));
if (!connection->outboundQueue.empty()) {
dump += StringPrintf(INDENT3 "OutboundQueue: length=%zu\n",
@@ -4198,9 +4322,10 @@
for (DispatchEntry* entry : connection->outboundQueue) {
dump.append(INDENT4);
entry->eventEntry->appendDescription(dump);
- dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n",
+ dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, age=%" PRId64
+ "ms\n",
entry->targetFlags, entry->resolvedAction,
- (currentTime - entry->eventEntry->eventTime) * 0.000001f);
+ ns2ms(currentTime - entry->eventEntry->eventTime));
}
} else {
dump += INDENT3 "OutboundQueue: <empty>\n";
@@ -4213,10 +4338,10 @@
dump += INDENT4;
entry->eventEntry->appendDescription(dump);
dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, "
- "age=%0.1fms, wait=%0.1fms\n",
+ "age=%" PRId64 "ms, wait=%" PRId64 "ms\n",
entry->targetFlags, entry->resolvedAction,
- (currentTime - entry->eventEntry->eventTime) * 0.000001f,
- (currentTime - entry->deliveryTime) * 0.000001f);
+ ns2ms(currentTime - entry->eventEntry->eventTime),
+ ns2ms(currentTime - entry->deliveryTime));
}
} else {
dump += INDENT3 "WaitQueue: <empty>\n";
@@ -4227,16 +4352,16 @@
}
if (isAppSwitchPendingLocked()) {
- dump += StringPrintf(INDENT "AppSwitch: pending, due in %0.1fms\n",
- (mAppSwitchDueTime - now()) / 1000000.0);
+ dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n",
+ ns2ms(mAppSwitchDueTime - now()));
} else {
dump += INDENT "AppSwitch: not pending\n";
}
dump += INDENT "Configuration:\n";
- dump += StringPrintf(INDENT2 "KeyRepeatDelay: %0.1fms\n", mConfig.keyRepeatDelay * 0.000001f);
- dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %0.1fms\n",
- mConfig.keyRepeatTimeout * 0.000001f);
+ dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay));
+ dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
+ ns2ms(mConfig.keyRepeatTimeout));
}
void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
@@ -4338,8 +4463,7 @@
return BAD_VALUE;
}
- [[maybe_unused]] const bool removed = removeByValue(mConnectionsByFd, connection);
- ALOG_ASSERT(removed);
+ removeConnectionLocked(connection);
mInputChannelsByToken.erase(inputChannel->getConnectionToken());
if (connection->monitor) {
@@ -4442,7 +4566,7 @@
return std::nullopt;
}
-sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) {
+sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
if (inputConnectionToken == nullptr) {
return nullptr;
}
@@ -4457,6 +4581,11 @@
return nullptr;
}
+void InputDispatcher::removeConnectionLocked(const sp<Connection>& connection) {
+ mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
+ removeByValue(mConnectionsByFd, connection);
+}
+
void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
const sp<Connection>& connection, uint32_t seq,
bool handled) {
@@ -4491,17 +4620,69 @@
postCommandLocked(std::move(commandEntry));
}
-void InputDispatcher::onAnrLocked(nsecs_t currentTime,
- const sp<InputApplicationHandle>& applicationHandle,
- const sp<InputWindowHandle>& windowHandle, nsecs_t eventTime,
- nsecs_t waitStartTime, const char* reason) {
- float dispatchLatency = (currentTime - eventTime) * 0.000001f;
- float waitDuration = (currentTime - waitStartTime) * 0.000001f;
- ALOGI("Application is not responding: %s. "
- "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s",
- getApplicationWindowLabel(applicationHandle, windowHandle).c_str(), dispatchLatency,
- waitDuration, reason);
+void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
+ // 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()) {
+ ALOGI("Not raising ANR because the connection %s has recovered",
+ connection->inputChannel->getName().c_str());
+ return;
+ }
+ /**
+ * The "oldestEntry" is the entry that was first sent to the application. That entry, however,
+ * may not be the one that caused the timeout to occur. One possibility is that window timeout
+ * has changed. This could cause newer entries to time out before the already dispatched
+ * entries. In that situation, the newest entries caused ANR. But in all likelihood, the app
+ * processes the events linearly. So providing information about the oldest entry seems to be
+ * most useful.
+ */
+ DispatchEntry* oldestEntry = *connection->waitQueue.begin();
+ const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
+ std::string reason =
+ android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
+ connection->inputChannel->getName().c_str(),
+ ns2ms(currentWait),
+ oldestEntry->eventEntry->getDescription().c_str());
+ updateLastAnrStateLocked(getWindowHandleLocked(connection->inputChannel->getConnectionToken()),
+ reason);
+
+ std::unique_ptr<CommandEntry> commandEntry =
+ std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
+ commandEntry->inputApplicationHandle = nullptr;
+ commandEntry->inputChannel = connection->inputChannel;
+ commandEntry->reason = std::move(reason);
+ postCommandLocked(std::move(commandEntry));
+}
+
+void InputDispatcher::onAnrLocked(const sp<InputApplicationHandle>& application) {
+ std::string reason = android::base::StringPrintf("%s does not have a focused window",
+ application->getName().c_str());
+
+ updateLastAnrStateLocked(application, reason);
+
+ std::unique_ptr<CommandEntry> commandEntry =
+ std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
+ commandEntry->inputApplicationHandle = application;
+ commandEntry->inputChannel = nullptr;
+ commandEntry->reason = std::move(reason);
+ postCommandLocked(std::move(commandEntry));
+}
+
+void InputDispatcher::updateLastAnrStateLocked(const sp<InputWindowHandle>& window,
+ const std::string& reason) {
+ const std::string windowLabel = getApplicationWindowLabel(nullptr, window);
+ updateLastAnrStateLocked(windowLabel, reason);
+}
+
+void InputDispatcher::updateLastAnrStateLocked(const sp<InputApplicationHandle>& application,
+ const std::string& reason) {
+ const std::string windowLabel = getApplicationWindowLabel(application, nullptr);
+ updateLastAnrStateLocked(windowLabel, reason);
+}
+
+void InputDispatcher::updateLastAnrStateLocked(const std::string& windowLabel,
+ const std::string& reason) {
// Capture a record of the InputDispatcher state at the time of the ANR.
time_t t = time(nullptr);
struct tm tm;
@@ -4511,21 +4692,9 @@
mLastAnrState.clear();
mLastAnrState += INDENT "ANR:\n";
mLastAnrState += StringPrintf(INDENT2 "Time: %s\n", timestr);
- mLastAnrState +=
- StringPrintf(INDENT2 "Window: %s\n",
- getApplicationWindowLabel(applicationHandle, windowHandle).c_str());
- mLastAnrState += StringPrintf(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency);
- mLastAnrState += StringPrintf(INDENT2 "WaitDuration: %0.1fms\n", waitDuration);
- mLastAnrState += StringPrintf(INDENT2 "Reason: %s\n", reason);
+ mLastAnrState += StringPrintf(INDENT2 "Reason: %s\n", reason.c_str());
+ mLastAnrState += StringPrintf(INDENT2 "Window: %s\n", windowLabel.c_str());
dumpDispatchStateLocked(mLastAnrState);
-
- std::unique_ptr<CommandEntry> commandEntry =
- std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
- commandEntry->inputApplicationHandle = applicationHandle;
- commandEntry->inputChannel =
- windowHandle != nullptr ? getInputChannelLocked(windowHandle->getToken()) : nullptr;
- commandEntry->reason = reason;
- postCommandLocked(std::move(commandEntry));
}
void InputDispatcher::doNotifyConfigurationChangedLockedInterruptible(CommandEntry* commandEntry) {
@@ -4561,18 +4730,55 @@
commandEntry->inputChannel ? commandEntry->inputChannel->getConnectionToken() : nullptr;
mLock.unlock();
- nsecs_t newTimeout =
+ const nsecs_t timeoutExtension =
mPolicy->notifyAnr(commandEntry->inputApplicationHandle, token, commandEntry->reason);
mLock.lock();
- resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, token);
+ if (timeoutExtension > 0) {
+ extendAnrTimeoutsLocked(commandEntry->inputApplicationHandle, token, timeoutExtension);
+ } else {
+ // stop waking up for events in this connection, it is already not responding
+ sp<Connection> connection = getConnectionLocked(token);
+ if (connection == nullptr) {
+ return;
+ }
+ cancelEventsForAnrLocked(connection);
+ }
+}
+
+void InputDispatcher::extendAnrTimeoutsLocked(const sp<InputApplicationHandle>& application,
+ const sp<IBinder>& connectionToken,
+ nsecs_t timeoutExtension) {
+ sp<Connection> connection = getConnectionLocked(connectionToken);
+ if (connection == nullptr) {
+ if (mNoFocusedWindowTimeoutTime.has_value() && application != nullptr) {
+ // Maybe ANR happened because there's no focused window?
+ mNoFocusedWindowTimeoutTime = now() + timeoutExtension;
+ mAwaitedFocusedApplication = application;
+ } else {
+ // It's also possible that the connection already disappeared. No action necessary.
+ }
+ return;
+ }
+
+ ALOGI("Raised ANR, but the policy wants to keep waiting on %s for %" PRId64 "ms longer",
+ connection->inputChannel->getName().c_str(), ns2ms(timeoutExtension));
+
+ connection->responsive = true;
+ const nsecs_t newTimeout = now() + timeoutExtension;
+ for (DispatchEntry* entry : connection->waitQueue) {
+ if (newTimeout >= entry->timeoutTime) {
+ // Already removed old entries when connection was marked unresponsive
+ entry->timeoutTime = newTimeout;
+ mAnrTracker.insert(entry->timeoutTime, connectionToken);
+ }
+ }
}
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
KeyEntry* entry = commandEntry->keyEntry;
-
KeyEvent event = createKeyEvent(*entry);
mLock.unlock();
@@ -4606,6 +4812,20 @@
mLock.lock();
}
+/**
+ * Connection is responsive if it has no events in the waitQueue that are older than the
+ * current time.
+ */
+static bool isConnectionResponsive(const Connection& connection) {
+ const nsecs_t currentTime = now();
+ for (const DispatchEntry* entry : connection.waitQueue) {
+ if (entry->timeoutTime < currentTime) {
+ return false;
+ }
+ }
+ return true;
+}
+
void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {
sp<Connection> connection = commandEntry->connection;
const nsecs_t finishTime = commandEntry->eventTime;
@@ -4618,14 +4838,10 @@
return;
}
DispatchEntry* dispatchEntry = *dispatchEntryIt;
-
- nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+ const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
- std::string msg =
- StringPrintf("Window '%s' spent %0.1fms processing the last input event: ",
- connection->getWindowName().c_str(), eventDuration * 0.000001f);
- dispatchEntry->eventEntry->appendDescription(msg);
- ALOGI("%s", msg.c_str());
+ ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+ ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
}
reportDispatchStatistics(std::chrono::nanoseconds(eventDuration), *connection, handled);
@@ -4643,13 +4859,18 @@
}
// Dequeue the event and start the next cycle.
- // Note that because the lock might have been released, it is possible that the
+ // Because the lock might have been released, it is possible that the
// contents of the wait queue to have been drained, so we need to double-check
// a few things.
dispatchEntryIt = connection->findWaitQueueEntry(seq);
if (dispatchEntryIt != connection->waitQueue.end()) {
dispatchEntry = *dispatchEntryIt;
connection->waitQueue.erase(dispatchEntryIt);
+ mAnrTracker.erase(dispatchEntry->timeoutTime,
+ connection->inputChannel->getConnectionToken());
+ if (!connection->responsive) {
+ connection->responsive = isConnectionResponsive(*connection);
+ }
traceWaitQueueLength(connection);
if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
connection->outboundQueue.push_front(dispatchEntry);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index e87a306..7c2028a 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -17,6 +17,7 @@
#ifndef _UI_INPUT_DISPATCHER_H
#define _UI_INPUT_DISPATCHER_H
+#include "AnrTracker.h"
#include "CancelationOptions.h"
#include "Entry.h"
#include "InjectionState.h"
@@ -48,6 +49,7 @@
#include <deque>
#include <optional>
#include <unordered_map>
+#include <unordered_set>
#include <InputListener.h>
#include <InputReporterInterface.h>
@@ -198,6 +200,11 @@
// All registered connections mapped by channel file descriptor.
std::unordered_map<int, sp<Connection>> mConnectionsByFd GUARDED_BY(mLock);
+ sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
+ REQUIRES(mLock);
+
+ void removeConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock);
+
struct IBinderHash {
std::size_t operator()(const sp<IBinder>& b) const {
return std::hash<IBinder*>{}(b.get());
@@ -210,8 +217,6 @@
std::optional<int32_t> findGestureMonitorDisplayByTokenLocked(const sp<IBinder>& token)
REQUIRES(mLock);
- sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) 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);
@@ -269,6 +274,9 @@
bool runCommandsLockedInterruptible() REQUIRES(mLock);
void postCommandLocked(std::unique_ptr<CommandEntry> commandEntry) REQUIRES(mLock);
+ nsecs_t processAnrsLocked() REQUIRES(mLock);
+ nsecs_t getDispatchingTimeoutLocked(const sp<IBinder>& token) REQUIRES(mLock);
+
// Input filter processing.
bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock);
bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
@@ -319,6 +327,11 @@
// Dispatcher state at time of last ANR.
std::string mLastAnrState GUARDED_BY(mLock);
+ // The connection token of the channel that was last interacted with.
+ std::unordered_set<sp<IBinder>, IBinderHash> mInteractionConnections GUARDED_BY(mLock);
+ void updateInteractionTokensLocked(const EventEntry& entry,
+ const std::vector<InputTarget>& targets) REQUIRES(mLock);
+
// Dispatch inbound events.
bool dispatchConfigurationChangedLocked(nsecs_t currentTime, ConfigurationChangedEntry* entry)
REQUIRES(mLock);
@@ -334,36 +347,53 @@
void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
void logOutboundMotionDetails(const char* prefix, const MotionEntry& entry);
- // Keeping track of ANR timeouts.
- enum InputTargetWaitCause {
- INPUT_TARGET_WAIT_CAUSE_NONE,
- INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
- INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
- };
+ /**
+ * This field is set if there is no focused window, and we have an event that requires
+ * a focused window to be dispatched (for example, a KeyEvent).
+ * When this happens, we will wait until *mNoFocusedWindowTimeoutTime before
+ * dropping the event and raising an ANR for that application.
+ * This is useful if an application is slow to add a focused window.
+ */
+ std::optional<nsecs_t> mNoFocusedWindowTimeoutTime GUARDED_BY(mLock);
- InputTargetWaitCause mInputTargetWaitCause GUARDED_BY(mLock);
- nsecs_t mInputTargetWaitStartTime GUARDED_BY(mLock);
- nsecs_t mInputTargetWaitTimeoutTime GUARDED_BY(mLock);
- bool mInputTargetWaitTimeoutExpired GUARDED_BY(mLock);
- sp<IBinder> mInputTargetWaitApplicationToken GUARDED_BY(mLock);
+ bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock);
+
+ /**
+ * Time to stop waiting for the events to be processed while trying to dispatch a key.
+ * When this time expires, we just send the pending key event to the currently focused window,
+ * without waiting on other events to be processed first.
+ */
+ std::optional<nsecs_t> mKeyIsWaitingForEventsTimeout GUARDED_BY(mLock);
+ bool shouldWaitToSendKeyLocked(nsecs_t currentTime, const char* focusedWindowName)
+ REQUIRES(mLock);
+
+ /**
+ * The focused application at the time when no focused window was present.
+ * Used to raise an ANR when we have no focused window.
+ */
+ sp<InputApplicationHandle> mAwaitedFocusedApplication GUARDED_BY(mLock);
+
+ // Optimization: AnrTracker is used to quickly find which connection is due for a timeout next.
+ // AnrTracker must be kept in-sync with all responsive connection.waitQueues.
+ // If a connection is not responsive, then the entries should not be added to the AnrTracker.
+ // Once a connection becomes unresponsive, its entries are removed from AnrTracker to
+ // prevent unneeded wakeups.
+ AnrTracker mAnrTracker GUARDED_BY(mLock);
+ void extendAnrTimeoutsLocked(const sp<InputApplicationHandle>& application,
+ const sp<IBinder>& connectionToken, nsecs_t timeoutExtension)
+ REQUIRES(mLock);
// Contains the last window which received a hover event.
sp<InputWindowHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
- // Finding targets for input events.
- int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry& entry,
- const sp<InputApplicationHandle>& applicationHandle,
- const sp<InputWindowHandle>& windowHandle,
- nsecs_t* nextWakeupTime, const char* reason)
- REQUIRES(mLock);
-
- void removeWindowByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
-
- void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
- const sp<IBinder>& inputConnectionToken)
- REQUIRES(mLock);
+ void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime) REQUIRES(mLock);
- void resetAnrTimeoutsLocked() REQUIRES(mLock);
+ // If a focused application changes, we should stop counting down the "no focused window" time,
+ // because we will have no way of knowing when the previous application actually added a window.
+ // This also means that we will miss cases like pulling down notification shade when the
+ // focused application does not have a focused window (no ANR will be raised if notification
+ // shade is pulled down while we are counting down the timeout).
+ void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
int32_t getTargetDisplayId(const EventEntry& entry);
int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry& entry,
@@ -376,6 +406,8 @@
std::vector<TouchedMonitor> findTouchedGestureMonitorsLocked(
int32_t displayId, const std::vector<sp<InputWindowHandle>>& portalWindows) const
REQUIRES(mLock);
+ std::vector<TouchedMonitor> selectResponsiveMonitorsLocked(
+ const std::vector<TouchedMonitor>& gestureMonitors) const REQUIRES(mLock);
void addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags,
BitSet32 pointerIds, std::vector<InputTarget>& inputTargets)
@@ -394,11 +426,6 @@
std::string getApplicationWindowLabel(const sp<InputApplicationHandle>& applicationHandle,
const sp<InputWindowHandle>& windowHandle);
- std::string checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
- const sp<InputWindowHandle>& windowHandle,
- const EventEntry& eventEntry,
- const char* targetType) REQUIRES(mLock);
-
// Manage the dispatch cycle for a single connection.
// These methods are deliberately not Interruptible because doing all of the work
// with the mutex held makes it easier to ensure that connection invariants are maintained.
@@ -468,9 +495,14 @@
REQUIRES(mLock);
void onFocusChangedLocked(const sp<InputWindowHandle>& oldFocus,
const sp<InputWindowHandle>& newFocus) REQUIRES(mLock);
- void onAnrLocked(nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
- const sp<InputWindowHandle>& windowHandle, nsecs_t eventTime,
- nsecs_t waitStartTime, const char* reason) REQUIRES(mLock);
+ void onAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
+ void onAnrLocked(const sp<InputApplicationHandle>& application) REQUIRES(mLock);
+ void updateLastAnrStateLocked(const sp<InputWindowHandle>& window, const std::string& reason)
+ REQUIRES(mLock);
+ void updateLastAnrStateLocked(const sp<InputApplicationHandle>& application,
+ const std::string& reason) REQUIRES(mLock);
+ void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
+ REQUIRES(mLock);
// Outbound policy interactions.
void doNotifyConfigurationChangedLockedInterruptible(CommandEntry* commandEntry)
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 264d287..a1514af 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -1318,7 +1318,7 @@
// joystick and gamepad buttons which are handled like keyboards for the most part.
bool haveKeyboardKeys =
containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC)) ||
- containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
+ containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_WHEEL),
sizeof_bit_array(KEY_MAX + 1));
bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
sizeof_bit_array(BTN_MOUSE)) ||
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 7be4a58..e009221 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -233,7 +233,7 @@
}
bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
- return scanCode < BTN_MOUSE || scanCode >= KEY_OK ||
+ return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL ||
(scanCode >= BTN_MISC && scanCode < BTN_MOUSE) ||
(scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 73d2272..a0d2f4f 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -27,6 +27,7 @@
"libinputflinger_defaults",
],
srcs: [
+ "AnrTracker_test.cpp",
"BlockingQueue_test.cpp",
"EventHub_test.cpp",
"TestInputListener.cpp",
diff --git a/services/inputflinger/tests/AnrTracker_test.cpp b/services/inputflinger/tests/AnrTracker_test.cpp
new file mode 100644
index 0000000..b561da1
--- /dev/null
+++ b/services/inputflinger/tests/AnrTracker_test.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 "../AnrTracker.h"
+
+#include <binder/Binder.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+namespace inputdispatcher {
+
+// --- AnrTrackerTest ---
+
+/**
+ * Add a single entry and ensure it's returned as first, even if the token isn't valid
+ */
+TEST(AnrTrackerTest, SingleEntry_First) {
+ AnrTracker tracker;
+
+ tracker.insert(1, nullptr);
+
+ ASSERT_EQ(1, tracker.firstTimeout());
+ ASSERT_EQ(tracker.firstToken(), nullptr);
+}
+
+TEST(AnrTrackerTest, MultipleEntries_RemoveToken) {
+ AnrTracker tracker;
+
+ sp<IBinder> token1 = new BBinder();
+ sp<IBinder> token2 = new BBinder();
+
+ tracker.insert(1, token1);
+ tracker.insert(2, token2);
+ tracker.insert(3, token1);
+ tracker.insert(4, token2);
+ tracker.insert(5, token1);
+
+ tracker.eraseToken(token1);
+
+ ASSERT_EQ(2, tracker.firstTimeout());
+}
+
+TEST(AnrTrackerTest, AddAndRemove_Empty) {
+ AnrTracker tracker;
+
+ ASSERT_TRUE(tracker.empty());
+
+ tracker.insert(1, nullptr);
+ ASSERT_FALSE(tracker.empty());
+
+ tracker.erase(1, nullptr);
+ ASSERT_TRUE(tracker.empty());
+}
+
+TEST(AnrTrackerTest, Clear) {
+ AnrTracker tracker;
+
+ tracker.insert(1, nullptr);
+ tracker.clear();
+ ASSERT_TRUE(tracker.empty());
+}
+
+TEST(AnrTrackerTest, SingleToken_MaintainsOrder) {
+ AnrTracker tracker;
+
+ ASSERT_TRUE(tracker.empty());
+
+ tracker.insert(2, nullptr);
+ tracker.insert(5, nullptr);
+ tracker.insert(0, nullptr);
+
+ ASSERT_EQ(0, tracker.firstTimeout());
+ ASSERT_EQ(nullptr, tracker.firstToken());
+}
+
+TEST(AnrTrackerTest, MultipleTokens_MaintainsOrder) {
+ AnrTracker tracker;
+
+ sp<IBinder> token1 = new BBinder();
+ sp<IBinder> token2 = new BBinder();
+
+ tracker.insert(2, token1);
+ tracker.insert(5, token2);
+ tracker.insert(0, token2);
+
+ ASSERT_EQ(0, tracker.firstTimeout());
+ ASSERT_EQ(token2, tracker.firstToken());
+}
+
+TEST(AnrTrackerTest, MultipleTokens_IdenticalTimes) {
+ AnrTracker tracker;
+
+ sp<IBinder> token1 = new BBinder();
+ sp<IBinder> token2 = new BBinder();
+
+ tracker.insert(2, token1);
+ tracker.insert(2, token2);
+ tracker.insert(10, token2);
+
+ ASSERT_EQ(2, tracker.firstTimeout());
+ // Doesn't matter which token is returned - both are valid results
+ ASSERT_TRUE(token1 == tracker.firstToken() || token2 == tracker.firstToken());
+}
+
+TEST(AnrTrackerTest, MultipleTokens_IdenticalTimesRemove) {
+ AnrTracker tracker;
+
+ sp<IBinder> token1 = new BBinder();
+ sp<IBinder> token2 = new BBinder();
+
+ tracker.insert(2, token1);
+ tracker.insert(2, token2);
+ tracker.insert(10, token2);
+
+ tracker.erase(2, token2);
+
+ ASSERT_EQ(2, tracker.firstTimeout());
+ ASSERT_EQ(token1, tracker.firstToken());
+}
+
+TEST(AnrTrackerTest, Empty_DoesntCrash) {
+ AnrTracker tracker;
+
+ ASSERT_TRUE(tracker.empty());
+
+ ASSERT_EQ(LONG_LONG_MAX, tracker.firstTimeout());
+ // Can't call firstToken() if tracker.empty()
+}
+
+TEST(AnrTrackerTest, RemoveInvalidItem_DoesntCrash) {
+ AnrTracker tracker;
+
+ tracker.insert(1, nullptr);
+
+ // Remove with non-matching timestamp
+ tracker.erase(2, nullptr);
+ ASSERT_EQ(1, tracker.firstTimeout());
+ ASSERT_EQ(nullptr, tracker.firstToken());
+
+ // Remove with non-matching token
+ tracker.erase(1, new BBinder());
+ ASSERT_EQ(1, tracker.firstTimeout());
+ ASSERT_EQ(nullptr, tracker.firstToken());
+
+ // Remove with both non-matching
+ tracker.erase(2, new BBinder());
+ ASSERT_EQ(1, tracker.firstTimeout());
+ ASSERT_EQ(nullptr, tracker.firstToken());
+}
+
+} // namespace inputdispatcher
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f33cc65..1a133dc 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -17,12 +17,14 @@
#include "../dispatcher/InputDispatcher.h"
#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
#include <binder/Binder.h>
#include <input/Input.h>
#include <gtest/gtest.h>
#include <linux/input.h>
#include <cinttypes>
+#include <thread>
#include <unordered_set>
#include <vector>
@@ -119,11 +121,65 @@
<< "Expected onPointerDownOutsideFocus to not have been called";
}
+ // This function must be called soon after the expected ANR timer starts,
+ // because we are also checking how much time has passed.
+ void assertNotifyAnrWasCalled(std::chrono::nanoseconds timeout,
+ const sp<InputApplicationHandle>& expectedApplication,
+ const sp<IBinder>& expectedToken) {
+ std::pair<sp<InputApplicationHandle>, sp<IBinder>> anrData;
+ ASSERT_NO_FATAL_FAILURE(anrData = getNotifyAnrData(timeout));
+ ASSERT_EQ(expectedApplication, anrData.first);
+ ASSERT_EQ(expectedToken, anrData.second);
+ }
+
+ std::pair<sp<InputApplicationHandle>, sp<IBinder>> getNotifyAnrData(
+ std::chrono::nanoseconds timeout) {
+ const std::chrono::time_point start = std::chrono::steady_clock::now();
+ std::unique_lock lock(mLock);
+ std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
+ android::base::ScopedLockAssertion assumeLocked(mLock);
+
+ // If there is an ANR, Dispatcher won't be idle because there are still events
+ // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
+ // before checking if ANR was called.
+ // Since dispatcher is not guaranteed to call notifyAnr right away, we need to provide
+ // it some time to act. 100ms seems reasonable.
+ mNotifyAnr.wait_for(lock, timeToWait, [this]() REQUIRES(mLock) {
+ return !mAnrApplications.empty() && !mAnrWindowTokens.empty();
+ });
+ const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+ if (mAnrApplications.empty() || mAnrWindowTokens.empty()) {
+ ADD_FAILURE() << "Did not receive ANR callback";
+ }
+ // 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
+ if (std::chrono::abs(timeout - waited) > 100ms) {
+ ADD_FAILURE() << "ANR was raised too early or too late. Expected "
+ << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
+ << "ms, but waited "
+ << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
+ << "ms instead";
+ }
+ std::pair<sp<InputApplicationHandle>, sp<IBinder>> result =
+ std::make_pair(mAnrApplications.front(), mAnrWindowTokens.front());
+ mAnrApplications.pop();
+ mAnrWindowTokens.pop();
+ return result;
+ }
+
+ void assertNotifyAnrWasNotCalled() {
+ std::scoped_lock lock(mLock);
+ ASSERT_TRUE(mAnrApplications.empty());
+ ASSERT_TRUE(mAnrWindowTokens.empty());
+ }
+
void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
mConfig.keyRepeatTimeout = timeout;
mConfig.keyRepeatDelay = delay;
}
+ void setAnrTimeout(std::chrono::nanoseconds timeout) { mAnrTimeout = timeout; }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -131,14 +187,24 @@
sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
+ // ANR handling
+ std::queue<sp<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
+ std::queue<sp<IBinder>> mAnrWindowTokens GUARDED_BY(mLock);
+ std::condition_variable mNotifyAnr;
+ std::chrono::nanoseconds mAnrTimeout = 0ms;
+
virtual void notifyConfigurationChanged(nsecs_t when) override {
std::scoped_lock lock(mLock);
mConfigurationChangedTime = when;
}
- virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>&, const sp<IBinder>&,
- const std::string&) override {
- return 0;
+ virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>& application,
+ const sp<IBinder>& windowToken, const std::string&) override {
+ std::scoped_lock lock(mLock);
+ mAnrApplications.push(application);
+ mAnrWindowTokens.push(windowToken);
+ mNotifyAnr.notify_all();
+ return mAnrTimeout.count();
}
virtual void notifyInputChannelBroken(const sp<IBinder>&) override {}
@@ -309,6 +375,20 @@
mFakePolicy.clear();
mDispatcher.clear();
}
+
+ /**
+ * Used for debugging when writing the test
+ */
+ void dumpDispatcherState() {
+ std::string dump;
+ mDispatcher->dump(dump);
+ std::stringstream ss(dump);
+ std::string to;
+
+ while (std::getline(ss, to, '\n')) {
+ ALOGE("%s", to.c_str());
+ }
+ }
};
@@ -502,13 +582,20 @@
class FakeApplicationHandle : public InputApplicationHandle {
public:
- FakeApplicationHandle() {}
+ FakeApplicationHandle() {
+ mInfo.name = "Fake Application";
+ mInfo.token = new BBinder();
+ mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT.count();
+ }
virtual ~FakeApplicationHandle() {}
virtual bool updateInfo() override {
- mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT.count();
return true;
}
+
+ void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+ mInfo.dispatchingTimeout = timeout.count();
+ }
};
class FakeInputReceiver {
@@ -519,6 +606,20 @@
}
InputEvent* consume() {
+ InputEvent* event;
+ std::optional<uint32_t> consumeSeq = receiveEvent(&event);
+ if (!consumeSeq) {
+ return nullptr;
+ }
+ finishEvent(*consumeSeq);
+ return event;
+ }
+
+ /**
+ * Receive an event without acknowledging it.
+ * Return the sequence number that could later be used to send finished signal.
+ */
+ std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
uint32_t consumeSeq;
InputEvent* event;
@@ -535,23 +636,29 @@
if (status == WOULD_BLOCK) {
// Just means there's no event available.
- return nullptr;
+ return std::nullopt;
}
if (status != OK) {
ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
- return nullptr;
+ return std::nullopt;
}
if (event == nullptr) {
ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
- return nullptr;
+ return std::nullopt;
}
+ if (outEvent != nullptr) {
+ *outEvent = event;
+ }
+ return consumeSeq;
+ }
- status = mConsumer->sendFinishedSignal(consumeSeq, true);
- if (status != OK) {
- ADD_FAILURE() << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
- }
- return event;
+ /**
+ * To be used together with "receiveEvent" to complete the consumption of an event.
+ */
+ void finishEvent(uint32_t consumeSeq) {
+ const status_t status = mConsumer->sendFinishedSignal(consumeSeq, true);
+ ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
}
void consumeEvent(int32_t expectedEventType, int32_t expectedAction, int32_t expectedDisplayId,
@@ -561,7 +668,7 @@
ASSERT_NE(nullptr, event) << mName.c_str()
<< ": consumer should have returned non-NULL event.";
ASSERT_EQ(expectedEventType, event->getType())
- << mName.c_str() << "expected " << inputEventTypeToString(expectedEventType)
+ << mName.c_str() << " expected " << inputEventTypeToString(expectedEventType)
<< " event, got " << inputEventTypeToString(event->getType()) << " event";
EXPECT_EQ(expectedDisplayId, event->getDisplayId());
@@ -606,9 +713,24 @@
void assertNoEvents() {
InputEvent* event = consume();
- ASSERT_EQ(nullptr, event)
- << mName.c_str()
- << ": should not have received any events, so consume() should return NULL";
+ if (event == nullptr) {
+ return;
+ }
+ if (event->getType() == AINPUT_EVENT_TYPE_KEY) {
+ KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
+ ADD_FAILURE() << "Received key event "
+ << KeyEvent::actionToString(keyEvent.getAction());
+ } else if (event->getType() == AINPUT_EVENT_TYPE_MOTION) {
+ MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+ ADD_FAILURE() << "Received motion event "
+ << MotionEvent::actionToString(motionEvent.getAction());
+ } else if (event->getType() == AINPUT_EVENT_TYPE_FOCUS) {
+ FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
+ ADD_FAILURE() << "Received focus event, hasFocus = "
+ << (focusEvent.getHasFocus() ? "true" : "false");
+ }
+ FAIL() << mName.c_str()
+ << ": should not have received any events, so consume() should return NULL";
}
sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
@@ -668,6 +790,12 @@
void setFocus(bool hasFocus) { mInfo.hasFocus = hasFocus; }
+ void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+ mInfo.dispatchingTimeout = timeout.count();
+ }
+
+ void setPaused(bool paused) { mInfo.paused = paused; }
+
void setFrame(const Rect& frame) {
mInfo.frameLeft = frame.left;
mInfo.frameTop = frame.top;
@@ -689,6 +817,10 @@
expectedFlags);
}
+ void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ consumeEvent(AINPUT_EVENT_TYPE_KEY, AKEY_EVENT_ACTION_UP, expectedDisplayId, expectedFlags);
+ }
+
void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
int32_t expectedFlags = 0) {
consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, expectedDisplayId,
@@ -740,6 +872,19 @@
expectedFlags);
}
+ std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
+ if (mInputReceiver == nullptr) {
+ ADD_FAILURE() << "Invalid receive event on window with no receiver";
+ return std::nullopt;
+ }
+ return mInputReceiver->receiveEvent(outEvent);
+ }
+
+ void finishEvent(uint32_t sequenceNum) {
+ ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+ mInputReceiver->finishEvent(sequenceNum);
+ }
+
InputEvent* consume() {
if (mInputReceiver == nullptr) {
return nullptr;
@@ -765,28 +910,42 @@
std::atomic<int32_t> FakeWindowHandle::sId{1};
-static int32_t injectKeyDown(const sp<InputDispatcher>& dispatcher,
- int32_t displayId = ADISPLAY_ID_NONE) {
+static int32_t injectKey(const sp<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
+ int32_t displayId = ADISPLAY_ID_NONE,
+ int32_t syncMode = INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
+ std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT) {
KeyEvent event;
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
// Define a valid key down event.
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, displayId,
- INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
- AMETA_NONE,
- /* repeatCount */ 0, currentTime, currentTime);
+ INVALID_HMAC, action, /* flags */ 0, AKEYCODE_A, KEY_A, AMETA_NONE,
+ repeatCount, currentTime, currentTime);
// Inject event until dispatch out.
- return dispatcher->injectInputEvent(
- &event,
- INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
- INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+ return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, syncMode,
+ injectionTimeout,
+ POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
}
-static int32_t injectMotionEvent(const sp<InputDispatcher>& dispatcher, int32_t action,
- int32_t source, int32_t displayId, int32_t x, int32_t y,
- int32_t xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION,
- int32_t yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION) {
+static int32_t injectKeyDown(const sp<InputDispatcher>& dispatcher,
+ int32_t displayId = ADISPLAY_ID_NONE) {
+ return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId);
+}
+
+static int32_t injectKeyUp(const sp<InputDispatcher>& dispatcher,
+ int32_t displayId = ADISPLAY_ID_NONE) {
+ return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId);
+}
+
+static int32_t injectMotionEvent(
+ const sp<InputDispatcher>& dispatcher, int32_t action, int32_t source, int32_t displayId,
+ const PointF& position,
+ const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION},
+ std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
+ int32_t injectionMode = INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
+ nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC)) {
MotionEvent event;
PointerProperties pointerProperties[1];
PointerCoords pointerCoords[1];
@@ -796,37 +955,33 @@
pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
pointerCoords[0].clear();
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
+ pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
- nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
// Define a valid motion down event.
event.initialize(InputEvent::nextId(), DEVICE_ID, source, displayId, INVALID_HMAC, action,
/* actionButton */ 0,
/* flags */ 0,
/* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
/* xScale */ 1, /* yScale */ 1, /* xOffset */ 0, /* yOffset */ 0,
- /* xPrecision */ 0, /* yPrecision */ 0, xCursorPosition, yCursorPosition,
- currentTime, currentTime,
+ /* xPrecision */ 0, /* yPrecision */ 0, cursorPosition.x, cursorPosition.y,
+ eventTime, eventTime,
/*pointerCount*/ 1, pointerProperties, pointerCoords);
// Inject event until dispatch out.
- return dispatcher->injectInputEvent(
- &event,
- INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
- INJECT_EVENT_TIMEOUT, POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+ return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode,
+ injectionTimeout,
+ POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
}
static int32_t injectMotionDown(const sp<InputDispatcher>& dispatcher, int32_t source,
int32_t displayId, const PointF& location = {100, 200}) {
- return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location.x,
- location.y);
+ return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
}
static int32_t injectMotionUp(const sp<InputDispatcher>& dispatcher, int32_t source,
int32_t displayId, const PointF& location = {100, 200}) {
- return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location.x,
- location.y);
+ return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
}
static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
@@ -1051,7 +1206,7 @@
// left window. This event should be dispatched to the left window.
ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
- ADISPLAY_ID_DEFAULT, 610, 400, 599, 400));
+ ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
windowRight->assertNoEvents();
}
@@ -1327,6 +1482,10 @@
expectedDisplayId, expectedFlags);
}
+ std::optional<int32_t> receiveEvent() { return mInputReceiver->receiveEvent(); }
+
+ void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
+
void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN,
expectedDisplayId, expectedFlags);
@@ -1405,6 +1564,21 @@
monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
}
+TEST_F(InputDispatcherTest, UnresponsiveGestureMonitor_GetsAnr) {
+ FakeMonitorReceiver monitor =
+ FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
+ true /*isGestureMonitor*/);
+
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+ std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
+ ASSERT_TRUE(consumeSeq);
+
+ mFakePolicy->assertNotifyAnrWasCalled(DISPATCHING_TIMEOUT, nullptr, monitor.getToken());
+ monitor.finishEvent(*consumeSeq);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
TEST_F(InputDispatcherTest, TestMoveEvent) {
sp<FakeApplicationHandle> application = new FakeApplicationHandle();
sp<FakeWindowHandle> window =
@@ -2185,4 +2359,676 @@
consumeMotionEvent(mWindow1, AMOTION_EVENT_ACTION_MOVE, expectedPoints);
}
+class InputDispatcherSingleWindowAnr : public InputDispatcherTest {
+ virtual void SetUp() override {
+ InputDispatcherTest::SetUp();
+
+ mApplication = new FakeApplicationHandle();
+ mApplication->setDispatchingTimeout(20ms);
+ mWindow =
+ new FakeWindowHandle(mApplication, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow->setFrame(Rect(0, 0, 30, 30));
+ mWindow->setDispatchingTimeout(10ms);
+ mWindow->setFocus(true);
+ // Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
+ // window.
+ mWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
+
+ // Set focused application.
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mWindow->consumeFocusEvent(true);
+ }
+
+ virtual void TearDown() override {
+ InputDispatcherTest::TearDown();
+ mWindow.clear();
+ }
+
+protected:
+ sp<FakeApplicationHandle> mApplication;
+ sp<FakeWindowHandle> mWindow;
+ static constexpr PointF WINDOW_LOCATION = {20, 20};
+
+ void tapOnWindow() {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ WINDOW_LOCATION));
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ WINDOW_LOCATION));
+ }
+};
+
+// Send a tap and respond, which should not cause an ANR.
+TEST_F(InputDispatcherSingleWindowAnr, WhenTouchIsConsumed_NoAnr) {
+ tapOnWindow();
+ mWindow->consumeMotionDown();
+ mWindow->consumeMotionUp();
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+}
+
+// Send a regular key and respond, which should not cause an ANR.
+TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher));
+ mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+}
+
+// Send an event to the app and have the app not respond right away.
+// When ANR is raised, policy will tell the dispatcher to cancel the events for that window.
+// So InputDispatcher will enqueue ACTION_CANCEL event as well.
+TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ WINDOW_LOCATION));
+
+ std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(sequenceNum);
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+
+ // The remaining lines are not really needed for the test, but kept as a sanity check
+ mWindow->finishEvent(*sequenceNum);
+ mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
+ ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
+// Send a key to the app and have the app not respond right away.
+TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
+ // Inject a key, and don't respond - expect that ANR is called.
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher));
+ std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
+ ASSERT_TRUE(sequenceNum);
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
+// We have a focused application, but no focused window
+TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) {
+ mWindow->setFocus(false);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mWindow->consumeFocusEvent(false);
+
+ // taps on the window work as normal
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ WINDOW_LOCATION));
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
+ mDispatcher->waitForIdle();
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+
+ // Once a focused event arrives, we get an ANR for this application
+ // We specify the injection timeout to be smaller than the application timeout, to ensure that
+ // injection times out (instead of failing).
+ const int32_t result =
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, 10ms);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_TIMED_OUT, result);
+ const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, mApplication, nullptr /*windowToken*/);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
+// We have a focused application, but no focused window
+// If the policy wants to keep waiting on the focused window to be added, make sure
+// that this timeout extension is honored and ANR is raised again.
+TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_ExtendsAnr) {
+ mWindow->setFocus(false);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mWindow->consumeFocusEvent(false);
+ const std::chrono::duration timeout = 5ms;
+ mFakePolicy->setAnrTimeout(timeout);
+
+ // Once a focused event arrives, we get an ANR for this application
+ // We specify the injection timeout to be smaller than the application timeout, to ensure that
+ // injection times out (instead of failing).
+ const int32_t result =
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, 10ms);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_TIMED_OUT, result);
+ const std::chrono::duration appTimeout =
+ mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(appTimeout, mApplication, nullptr /*windowToken*/);
+
+ // After the extended time has passed, ANR should be raised again
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, mApplication, nullptr /*windowToken*/);
+
+ // If we stop extending the timeout, dispatcher should go to idle.
+ // Another ANR may be raised during this time
+ mFakePolicy->setAnrTimeout(0ms);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
+// We have a focused application, but no focused window
+TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) {
+ mWindow->setFocus(false);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mWindow->consumeFocusEvent(false);
+
+ // Once a focused event arrives, we get an ANR for this application
+ const int32_t result =
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, 10ms);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_TIMED_OUT, result);
+
+ const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, mApplication, nullptr /*windowToken*/);
+
+ // Future focused events get dropped right away
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, injectKeyDown(mDispatcher));
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mWindow->assertNoEvents();
+}
+
+/**
+ * Ensure that the implementation is valid. Since we are using multiset to keep track of the
+ * ANR timeouts, we are allowing entries with identical timestamps in the same connection.
+ * If we process 1 of the events, but ANR on the second event with the same timestamp,
+ * the ANR mechanism should still work.
+ *
+ * In this test, we are injecting DOWN and UP events with the same timestamps, and acknowledging the
+ * DOWN event, while not responding on the second one.
+ */
+TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) {
+ nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+ {AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION},
+ 500ms, INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, currentTime);
+
+ // Now send ACTION_UP, with identical timestamp
+ injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+ {AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION},
+ 500ms, INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, currentTime);
+
+ // 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->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+}
+
+// If an app is not responding to a key event, gesture monitors should continue to receive
+// new motion events
+TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnKey) {
+ FakeMonitorReceiver monitor =
+ FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
+ true /*isGestureMonitor*/);
+
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT));
+ mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, injectKeyUp(mDispatcher, ADISPLAY_ID_DEFAULT));
+
+ // Stuck on the ACTION_UP
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr, mWindow->getToken());
+
+ // New tap will go to the gesture monitor, but not to the window
+ tapOnWindow();
+ monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+ mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+ mDispatcher->waitForIdle();
+ mWindow->assertNoEvents();
+ monitor.assertNoEvents();
+}
+
+// If an app is not responding to a motion event, gesture monitors should continue to receive
+// new motion events
+TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnMotion) {
+ FakeMonitorReceiver monitor =
+ FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
+ true /*isGestureMonitor*/);
+
+ tapOnWindow();
+ monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+ mWindow->consumeMotionDown();
+ // Stuck on the ACTION_UP
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr, mWindow->getToken());
+
+ // New tap will go to the gesture monitor, but not to the window
+ tapOnWindow();
+ monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+ mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+ mDispatcher->waitForIdle();
+ mWindow->assertNoEvents();
+ monitor.assertNoEvents();
+}
+
+// If a window is unresponsive, then you get anr. if the window later catches up and starts to
+// process events, you don't get an anr. When the window later becomes unresponsive again, you
+// get an ANR again.
+// 1. tap -> block on ACTION_UP -> receive ANR
+// 2. consume all pending events (= queue becomes healthy again)
+// 3. tap again -> block on ACTION_UP again -> receive ANR second time
+TEST_F(InputDispatcherSingleWindowAnr, SameWindow_CanReceiveAnrTwice) {
+ tapOnWindow();
+
+ mWindow->consumeMotionDown();
+ // Block on ACTION_UP
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+ mWindow->consumeMotionUp(); // Now the connection should be healthy again
+ mDispatcher->waitForIdle();
+ mWindow->assertNoEvents();
+
+ tapOnWindow();
+ mWindow->consumeMotionDown();
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+ mWindow->consumeMotionUp();
+
+ mDispatcher->waitForIdle();
+ mWindow->assertNoEvents();
+}
+
+// If the policy tells us to raise ANR again after some time, ensure that the timeout extension
+// is honored
+TEST_F(InputDispatcherSingleWindowAnr, Policy_CanExtendTimeout) {
+ const std::chrono::duration timeout = 5ms;
+ mFakePolicy->setAnrTimeout(timeout);
+
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ WINDOW_LOCATION));
+
+ const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(windowTimeout, nullptr /*application*/,
+ mWindow->getToken());
+
+ // Since the policy wanted to extend ANR, make sure it is called again after the extension
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/, mWindow->getToken());
+ mFakePolicy->setAnrTimeout(0ms);
+ std::this_thread::sleep_for(windowTimeout);
+ // We are not checking if ANR has been called, because it may have been called again by the
+ // time we set the timeout to 0
+
+ // When the policy finally says stop, we should get ACTION_CANCEL
+ mWindow->consumeMotionDown();
+ mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
+ ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ mWindow->assertNoEvents();
+}
+
+/**
+ * If a window is processing a motion event, and then a key event comes in, the key event should
+ * not to to the focused window until the motion is processed.
+ *
+ * Warning!!!
+ * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
+ * and the injection timeout that we specify when injecting the key.
+ * We must have the injection timeout (10ms) be smaller than
+ * KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
+ *
+ * If that value changes, this test should also change.
+ */
+TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) {
+ mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+
+ tapOnWindow();
+ std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
+ ASSERT_TRUE(downSequenceNum);
+ std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
+ ASSERT_TRUE(upSequenceNum);
+ // Don't finish the events yet, and send a key
+ // Injection will "succeed" because we will eventually give up and send the key to the focused
+ // window even if motions are still being processed. But because the injection timeout is short,
+ // we will receive INJECTION_TIMED_OUT as the result.
+
+ int32_t result =
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, 10ms);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_TIMED_OUT, result);
+ // Key will not be sent to the window, yet, because the window is still processing events
+ // and the key remains pending, waiting for the touch events to be processed
+ std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
+ ASSERT_FALSE(keySequenceNum);
+
+ std::this_thread::sleep_for(500ms);
+ // if we wait long enough though, dispatcher will give up, and still send the key
+ // to the focused window, even though we have not yet finished the motion event
+ mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+ mWindow->finishEvent(*downSequenceNum);
+ mWindow->finishEvent(*upSequenceNum);
+}
+
+/**
+ * If a window is processing a motion event, and then a key event comes in, the key event should
+ * not go to the focused window until the motion is processed.
+ * If then a new motion comes in, then the pending key event should be going to the currently
+ * focused window right away.
+ */
+TEST_F(InputDispatcherSingleWindowAnr,
+ PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn) {
+ mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+
+ tapOnWindow();
+ std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
+ ASSERT_TRUE(downSequenceNum);
+ std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
+ ASSERT_TRUE(upSequenceNum);
+ // Don't finish the events yet, and send a key
+ // Injection is async, so it will succeed
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
+ ADISPLAY_ID_DEFAULT, INPUT_EVENT_INJECTION_SYNC_NONE));
+ // At this point, key is still pending, and should not be sent to the application yet.
+ std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
+ ASSERT_FALSE(keySequenceNum);
+
+ // Now tap down again. It should cause the pending key to go to the focused window right away.
+ tapOnWindow();
+ mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); // it doesn't matter that we haven't ack'd
+ // the other events yet. We can finish events in any order.
+ mWindow->finishEvent(*downSequenceNum); // first tap's ACTION_DOWN
+ mWindow->finishEvent(*upSequenceNum); // first tap's ACTION_UP
+ mWindow->consumeMotionDown();
+ mWindow->consumeMotionUp();
+ mWindow->assertNoEvents();
+}
+
+class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
+ virtual void SetUp() override {
+ InputDispatcherTest::SetUp();
+
+ mApplication = new FakeApplicationHandle();
+ mApplication->setDispatchingTimeout(10ms);
+ mUnfocusedWindow =
+ new FakeWindowHandle(mApplication, mDispatcher, "Unfocused", 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.
+ // Adding FLAG_WATCH_OUTSIDE_TOUCH to receive ACTION_OUTSIDE when another window is tapped
+ mUnfocusedWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL |
+ InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH |
+ InputWindowInfo::FLAG_SPLIT_TOUCH);
+
+ mFocusedWindow =
+ new FakeWindowHandle(mApplication, mDispatcher, "Focused", ADISPLAY_ID_DEFAULT);
+ mFocusedWindow->setDispatchingTimeout(10ms);
+ mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
+ mFocusedWindow->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL |
+ InputWindowInfo::FLAG_SPLIT_TOUCH);
+
+ // Set focused application.
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+ mFocusedWindow->setFocus(true);
+
+ // Expect one focus window exist in display.
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+ mFocusedWindow->consumeFocusEvent(true);
+ }
+
+ virtual void TearDown() override {
+ InputDispatcherTest::TearDown();
+
+ mUnfocusedWindow.clear();
+ mFocusedWindow.clear();
+ }
+
+protected:
+ sp<FakeApplicationHandle> mApplication;
+ sp<FakeWindowHandle> mUnfocusedWindow;
+ sp<FakeWindowHandle> mFocusedWindow;
+ static constexpr PointF UNFOCUSED_WINDOW_LOCATION = {20, 20};
+ static constexpr PointF FOCUSED_WINDOW_LOCATION = {75, 75};
+ static constexpr PointF LOCATION_OUTSIDE_ALL_WINDOWS = {40, 40};
+
+ void tapOnFocusedWindow() { tap(FOCUSED_WINDOW_LOCATION); }
+
+ void tapOnUnfocusedWindow() { tap(UNFOCUSED_WINDOW_LOCATION); }
+
+private:
+ void tap(const PointF& location) {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ location));
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ location));
+ }
+};
+
+// If we have 2 windows that are both unresponsive, the one with the shortest timeout
+// should be ANR'd first.
+TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ FOCUSED_WINDOW_LOCATION))
+ << "Inject motion event should return INPUT_EVENT_INJECTION_SUCCEEDED";
+ mFocusedWindow->consumeMotionDown();
+ mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+ ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ // We consumed all events, so no ANR
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ FOCUSED_WINDOW_LOCATION));
+ std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
+ ASSERT_TRUE(unfocusedSequenceNum);
+ std::optional<uint32_t> focusedSequenceNum = mFocusedWindow->receiveEvent();
+ ASSERT_TRUE(focusedSequenceNum);
+
+ const std::chrono::duration timeout =
+ mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/,
+ mFocusedWindow->getToken());
+
+ mFocusedWindow->finishEvent(*focusedSequenceNum);
+ mUnfocusedWindow->finishEvent(*unfocusedSequenceNum);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+}
+
+// If we have 2 windows with identical timeouts that are both unresponsive,
+// it doesn't matter which order they should have ANR.
+// But we should receive ANR for both.
+TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout) {
+ // Set the timeout for unfocused window to match the focused window
+ mUnfocusedWindow->setDispatchingTimeout(10ms);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+
+ tapOnFocusedWindow();
+ // we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window
+ std::pair<sp<InputApplicationHandle>, sp<IBinder>> anrData1 =
+ mFakePolicy->getNotifyAnrData(10ms);
+ std::pair<sp<InputApplicationHandle>, sp<IBinder>> anrData2 =
+ mFakePolicy->getNotifyAnrData(0ms);
+
+ // We don't know which window will ANR first. But both of them should happen eventually.
+ ASSERT_TRUE(mFocusedWindow->getToken() == anrData1.second ||
+ mFocusedWindow->getToken() == anrData2.second);
+ ASSERT_TRUE(mUnfocusedWindow->getToken() == anrData1.second ||
+ mUnfocusedWindow->getToken() == anrData2.second);
+
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+}
+
+// If a window is already not responding, the second tap on the same window should be ignored.
+// We should also log an error to account for the dropped event (not tested here).
+// At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
+TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
+ tapOnFocusedWindow();
+ mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+ ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ // Receive the events, but don't respond
+ std::optional<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(downEventSequenceNum);
+ std::optional<uint32_t> upEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_UP
+ ASSERT_TRUE(upEventSequenceNum);
+ const std::chrono::duration timeout =
+ mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/,
+ mFocusedWindow->getToken());
+
+ // Tap once again
+ // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ FOCUSED_WINDOW_LOCATION));
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ FOCUSED_WINDOW_LOCATION));
+ // Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a
+ // valid touch target
+ mUnfocusedWindow->assertNoEvents();
+
+ // Consume the first tap
+ mFocusedWindow->finishEvent(*downEventSequenceNum);
+ mFocusedWindow->finishEvent(*upEventSequenceNum);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ // The second tap did not go to the focused window
+ mFocusedWindow->assertNoEvents();
+ // should not have another ANR after the window just became healthy again
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+}
+
+// If you tap outside of all windows, there will not be ANR
+TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ LOCATION_OUTSIDE_ALL_WINDOWS));
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+}
+
+// Since the focused window is paused, tapping on it should not produce any events
+TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) {
+ mFocusedWindow->setPaused(true);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ FOCUSED_WINDOW_LOCATION));
+
+ std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ // Should not ANR because the window is paused, and touches shouldn't go to it
+ mFakePolicy->assertNotifyAnrWasNotCalled();
+
+ mFocusedWindow->assertNoEvents();
+ mUnfocusedWindow->assertNoEvents();
+}
+
+/**
+ * If a window is processing a motion event, and then a key event comes in, the key event should
+ * not to to the focused window until the motion is processed.
+ * If a different window becomes focused at this time, the key should go to that window instead.
+ *
+ * Warning!!!
+ * This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
+ * and the injection timeout that we specify when injecting the key.
+ * We must have the injection timeout (10ms) be smaller than
+ * KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
+ *
+ * If that value changes, this test should also change.
+ */
+TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) {
+ // Set a long ANR timeout to prevent it from triggering
+ mFocusedWindow->setDispatchingTimeout(2s);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+
+ tapOnUnfocusedWindow();
+ std::optional<uint32_t> downSequenceNum = mUnfocusedWindow->receiveEvent();
+ ASSERT_TRUE(downSequenceNum);
+ std::optional<uint32_t> upSequenceNum = mUnfocusedWindow->receiveEvent();
+ ASSERT_TRUE(upSequenceNum);
+ // Don't finish the events yet, and send a key
+ // Injection will succeed because we will eventually give up and send the key to the focused
+ // window even if motions are still being processed.
+
+ int32_t result =
+ injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /*repeatCount*/, ADISPLAY_ID_DEFAULT,
+ INPUT_EVENT_INJECTION_SYNC_NONE, 10ms /*injectionTimeout*/);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED, result);
+ // Key will not be sent to the window, yet, because the window is still processing events
+ // and the key remains pending, waiting for the touch events to be processed
+ std::optional<uint32_t> keySequenceNum = mFocusedWindow->receiveEvent();
+ ASSERT_FALSE(keySequenceNum);
+
+ // Switch the focus to the "unfocused" window that we tapped. Expect the key to go there
+ mFocusedWindow->setFocus(false);
+ mUnfocusedWindow->setFocus(true);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+
+ // Focus events should precede the key events
+ mUnfocusedWindow->consumeFocusEvent(true);
+ mFocusedWindow->consumeFocusEvent(false);
+
+ // Finish the tap events, which should unblock dispatcher
+ mUnfocusedWindow->finishEvent(*downSequenceNum);
+ mUnfocusedWindow->finishEvent(*upSequenceNum);
+
+ // Now that all queues are cleared and no backlog in the connections, the key event
+ // can finally go to the newly focused "mUnfocusedWindow".
+ mUnfocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+ mFocusedWindow->assertNoEvents();
+ mUnfocusedWindow->assertNoEvents();
+}
+
+// When the touch stream is split across 2 windows, and one of them does not respond,
+// then ANR should be raised and the touch should be canceled for the unresponsive window.
+// The other window should not be affected by that.
+TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) {
+ // Touch Window 1
+ NotifyMotionArgs motionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {FOCUSED_WINDOW_LOCATION});
+ mDispatcher->notifyMotion(&motionArgs);
+ mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
+ ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+
+ // Touch Window 2
+ int32_t actionPointerDown =
+ AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+ motionArgs =
+ generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
+ mDispatcher->notifyMotion(&motionArgs);
+
+ const std::chrono::duration timeout =
+ mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyAnrWasCalled(timeout, nullptr /*application*/,
+ mFocusedWindow->getToken());
+
+ mUnfocusedWindow->consumeMotionDown();
+ mFocusedWindow->consumeMotionDown();
+ // Focused window may or may not receive ACTION_MOVE
+ // But it should definitely receive ACTION_CANCEL due to the ANR
+ InputEvent* event;
+ std::optional<int32_t> moveOrCancelSequenceNum = mFocusedWindow->receiveEvent(&event);
+ ASSERT_TRUE(moveOrCancelSequenceNum);
+ mFocusedWindow->finishEvent(*moveOrCancelSequenceNum);
+ ASSERT_NE(nullptr, event);
+ ASSERT_EQ(event->getType(), AINPUT_EVENT_TYPE_MOTION);
+ MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+ if (motionEvent.getAction() == AMOTION_EVENT_ACTION_MOVE) {
+ mFocusedWindow->consumeMotionCancel();
+ } else {
+ ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionEvent.getAction());
+ }
+
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mUnfocusedWindow->assertNoEvents();
+ mFocusedWindow->assertNoEvents();
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 109edfe..c457a15 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1848,6 +1848,28 @@
ASSERT_LE(prevTimestamp, keyArgs.eventTime);
}
+/**
+ * The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons
+ * on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP
+ * are passed to the listener.
+ */
+static_assert(BTN_GEAR_DOWN == BTN_WHEEL);
+TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) {
+ std::unique_ptr<UinputSteamController> controller = createUinputDevice<UinputSteamController>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ NotifyKeyArgs keyArgs;
+
+ controller->pressAndReleaseKey(BTN_GEAR_DOWN);
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP
+ ASSERT_EQ(BTN_GEAR_DOWN, keyArgs.scanCode);
+
+ controller->pressAndReleaseKey(BTN_GEAR_UP);
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP
+ ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode);
+}
+
// --- TouchProcessTest ---
class TouchIntegrationTest : public InputReaderIntegrationTest {
protected:
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index 10e7293..0659511 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -125,6 +125,9 @@
pressAndReleaseKey(KEY_HOME);
}
+// --- UinputSteamController
+UinputSteamController::UinputSteamController() : UinputKeyboard({BTN_GEAR_DOWN, BTN_GEAR_UP}) {}
+
// --- UinputTouchScreen ---
UinputTouchScreen::UinputTouchScreen(const Rect* size)
: UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {}
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index ec3cd9f..22d1f63 100644
--- a/services/inputflinger/tests/UinputDevice.h
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -108,6 +108,16 @@
UinputHomeKey();
};
+// A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key.
+class UinputSteamController : public UinputKeyboard {
+public:
+ template <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+private:
+ UinputSteamController();
+};
+
// --- UinputTouchScreen ---
// A touch screen device with specific size.
class UinputTouchScreen : public UinputDevice {
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index 45e67f7..8a282e2 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -820,7 +820,7 @@
status_t err(NO_ERROR);
// If the min period or min timeout has changed since the last batch call, call batch.
- if (prevBestBatchParams != info.bestBatchParams) {
+ if (prevBestBatchParams != info.bestBatchParams && info.numActiveClients() > 0) {
ALOGD_IF(DEBUG_CONNECTIONS, "\t>>> actuating h/w BATCH 0x%08x %" PRId64 " %" PRId64, handle,
info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch);
err = checkReturnAndGetStatus(mSensors->batch(
@@ -890,14 +890,13 @@
Info& info = mActivationCount.editValueAt(i);
if (info.hasBatchParamsForIdent(ident)) {
- if (updateBatchParamsLocked(handle, info) != NO_ERROR) {
- bool enable = info.numActiveClients() == 0 && info.isActive;
- bool disable = info.numActiveClients() > 0 && !info.isActive;
+ updateBatchParamsLocked(handle, info);
+ bool disable = info.numActiveClients() == 0 && info.isActive;
+ bool enable = info.numActiveClients() > 0 && !info.isActive;
- if ((enable || disable) &&
- doActivateHardwareLocked(handle, enable) == NO_ERROR) {
- info.isActive = enable;
- }
+ if ((enable || disable) &&
+ doActivateHardwareLocked(handle, enable) == NO_ERROR) {
+ info.isActive = enable;
}
}
}
diff --git a/services/sensorservice/SensorDeviceUtils.cpp b/services/sensorservice/SensorDeviceUtils.cpp
index 0dcf8c0..52213cf 100644
--- a/services/sensorservice/SensorDeviceUtils.cpp
+++ b/services/sensorservice/SensorDeviceUtils.cpp
@@ -40,22 +40,12 @@
switch ((SensorTypeV2_1)event->type) {
case SensorTypeV2_1::ACCELEROMETER:
case SensorTypeV2_1::MAGNETIC_FIELD:
- case SensorTypeV2_1::ORIENTATION:
case SensorTypeV2_1::GYROSCOPE:
- case SensorTypeV2_1::GRAVITY:
- case SensorTypeV2_1::LINEAR_ACCELERATION:
case SensorTypeV2_1::MAGNETIC_FIELD_UNCALIBRATED:
case SensorTypeV2_1::GYROSCOPE_UNCALIBRATED:
case SensorTypeV2_1::ACCELEROMETER_UNCALIBRATED:
axes = 3;
break;
- case SensorTypeV2_1::GAME_ROTATION_VECTOR:
- axes = 4;
- break;
- case SensorTypeV2_1::ROTATION_VECTOR:
- case SensorTypeV2_1::GEOMAGNETIC_ROTATION_VECTOR:
- axes = 5;
- break;
case SensorTypeV2_1::DEVICE_ORIENTATION:
case SensorTypeV2_1::LIGHT:
case SensorTypeV2_1::PRESSURE:
@@ -77,11 +67,8 @@
case SensorTypeV2_1::HINGE_ANGLE:
axes = 1;
break;
- case SensorTypeV2_1::POSE_6DOF:
- axes = 15;
- break;
default:
- // No other sensors have data that needs to be rounded.
+ // No other sensors have data that needs to be quantized.
break;
}
diff --git a/services/sensorservice/SensorDeviceUtils.h b/services/sensorservice/SensorDeviceUtils.h
index d7e621c..c232f0b 100644
--- a/services/sensorservice/SensorDeviceUtils.h
+++ b/services/sensorservice/SensorDeviceUtils.h
@@ -36,7 +36,7 @@
// Increase the value of the sensor's nominal resolution to ensure that
// sensor accuracy improvements, like runtime calibration, are not masked
// during requantization.
- double incRes = 0.25 * resolution;
+ double incRes = 0.125 * resolution;
*value = round(static_cast<double>(*value) / incRes) * incRes;
}
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index ffcd0a0..2b4fd7f 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -699,6 +699,9 @@
if (!checkCallingPermission(sManageSensorsPermission, nullptr, nullptr)) {
return PERMISSION_DENIED;
}
+ if (args.size() == 0) {
+ return BAD_INDEX;
+ }
if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
return BAD_VALUE;
}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 0182937..a790d0b 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -38,7 +38,6 @@
"libbinder",
"libbufferhubqueue",
"libcutils",
- "libdl",
"libEGL",
"libfmq",
"libGLESv1_CM",
@@ -73,7 +72,6 @@
"librenderengine",
"libserviceutils",
"libtrace_proto",
- "libvr_manager",
"libvrflinger",
],
header_libs: [
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 97ffe6f..0e69f60 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -148,6 +148,11 @@
virtual status_t updateActiveBuffer() = 0;
virtual status_t updateFrameNumber(nsecs_t latchTime) = 0;
+ // We generate InputWindowHandles for all buffered layers regardless of whether they
+ // have an InputChannel. This is to enable the InputDispatcher to do PID based occlusion
+ // detection.
+ bool needsInputInfo() const override { return true; }
+
protected:
struct BufferInfo {
nsecs_t mDesiredPresentTime;
diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp
index 648d129..8722952 100644
--- a/services/surfaceflinger/BufferLayerConsumer.cpp
+++ b/services/surfaceflinger/BufferLayerConsumer.cpp
@@ -452,6 +452,13 @@
}
void BufferLayerConsumer::onDisconnect() {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
mLayer->onDisconnect();
}
@@ -486,6 +493,13 @@
void BufferLayerConsumer::addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
mLayer->addAndGetFrameTimestamps(newTimestamps, outDelta);
}
diff --git a/services/surfaceflinger/BufferLayerConsumer.h b/services/surfaceflinger/BufferLayerConsumer.h
index c71a1d9..5e3044f 100644
--- a/services/surfaceflinger/BufferLayerConsumer.h
+++ b/services/surfaceflinger/BufferLayerConsumer.h
@@ -331,8 +331,8 @@
// construction time.
const uint32_t mTexName;
- // The layer for this BufferLayerConsumer
- Layer* mLayer;
+ // The layer for this BufferLayerConsumer. Always check mAbandoned before accessing.
+ Layer* mLayer GUARDED_BY(mMutex);
wp<ContentsChangedListener> mContentsChangedListener;
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index e5b94e4..07be791 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -31,6 +31,7 @@
#include "SurfaceInterceptor.h"
#include "FrameTracer/FrameTracer.h"
+#include "Scheduler/LayerHistory.h"
#include "TimeStats/TimeStats.h"
namespace android {
@@ -399,7 +400,8 @@
// Add this buffer from our internal queue tracker
{ // Autolock scope
const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
- mFlinger->mScheduler->recordLayerHistory(this, presentTime);
+ mFlinger->mScheduler->recordLayerHistory(this, presentTime,
+ LayerHistory::LayerUpdateType::Buffer);
Mutex::Autolock lock(mQueueItemLock);
// Reset the frame number tracker when we receive the first buffer after
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index a1ed6d7..790f2ec 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -33,7 +33,6 @@
#include <renderengine/Image.h>
#include "EffectLayer.h"
-#include "FrameTracer/FrameTracer.h"
#include "TimeStats/TimeStats.h"
namespace android {
@@ -98,9 +97,6 @@
// Prevent tracing the same release multiple times.
if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
- mFlinger->mFrameTracer->traceFence(getSequence(), mPreviousBufferId, mPreviousFrameNumber,
- std::make_shared<FenceTime>(releaseFence),
- FrameTracer::FrameEvent::RELEASE_FENCE);
mPreviousReleasedFrameNumber = mPreviousFrameNumber;
}
}
@@ -277,13 +273,11 @@
const int32_t layerId = getSequence();
mFlinger->mTimeStats->setPostTime(layerId, mCurrentState.frameNumber, getName().c_str(),
postTime);
- mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str());
- mFlinger->mFrameTracer->traceTimestamp(layerId, buffer->getId(), mCurrentState.frameNumber,
- postTime, FrameTracer::FrameEvent::POST);
desiredPresentTime = desiredPresentTime <= 0 ? 0 : desiredPresentTime;
mCurrentState.desiredPresentTime = desiredPresentTime;
- mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime);
+ mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime,
+ LayerHistory::LayerUpdateType::Buffer);
addFrameEvent(acquireFence, postTime, desiredPresentTime);
return true;
@@ -579,20 +573,13 @@
status_t err = bindTextureImage();
if (err != NO_ERROR) {
mFlinger->mTimeStats->onDestroy(layerId);
- mFlinger->mFrameTracer->onDestroy(layerId);
return BAD_VALUE;
}
}
- const uint64_t bufferID = getCurrentBufferId();
mFlinger->mTimeStats->setAcquireFence(layerId, mDrawingState.frameNumber,
- mBufferInfo.mFenceTime);
- mFlinger->mFrameTracer->traceFence(layerId, bufferID, mDrawingState.frameNumber,
- mBufferInfo.mFenceTime,
- FrameTracer::FrameEvent::ACQUIRE_FENCE);
+ std::make_shared<FenceTime>(mDrawingState.acquireFence));
mFlinger->mTimeStats->setLatchTime(layerId, mDrawingState.frameNumber, latchTime);
- mFlinger->mFrameTracer->traceTimestamp(layerId, bufferID, mDrawingState.frameNumber, latchTime,
- FrameTracer::FrameEvent::LATCH);
mCurrentStateModified = false;
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index fda451b..b3b9fe5 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -21,7 +21,6 @@
"liblog",
"libnativewindow",
"libprotobuf-cpp-lite",
- "libsync",
"libtimestats",
"libui",
"libutils",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
index 5ce2fdc..f680460 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
@@ -63,6 +63,12 @@
// Sets the dataspace used for rendering the surface
virtual void setBufferDataspace(ui::Dataspace) = 0;
+ // Sets the pixel format used for rendering the surface.
+ // Changing the pixel format of the buffer will result in buffer
+ // reallocation as well as some reconfiguration of the graphics context,
+ // which are both expensive operations.
+ virtual void setBufferPixelFormat(ui::PixelFormat) = 0;
+
// Configures the protected rendering on the surface
virtual void setProtected(bool useProtected) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 9ca7d2f..7a4f738 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -70,11 +70,13 @@
using ChangedTypes = android::HWComposer::DeviceRequestedChanges::ChangedTypes;
using DisplayRequests = android::HWComposer::DeviceRequestedChanges::DisplayRequests;
using LayerRequests = android::HWComposer::DeviceRequestedChanges::LayerRequests;
+ using ClientTargetProperty = android::HWComposer::DeviceRequestedChanges::ClientTargetProperty;
virtual bool anyLayersRequireClientComposition() const;
virtual bool allLayersRequireClientComposition() const;
virtual void applyChangedTypesToLayers(const ChangedTypes&);
virtual void applyDisplayRequests(const DisplayRequests&);
virtual void applyLayerRequestsToLayers(const LayerRequests&);
+ virtual void applyClientTargetRequests(const ClientTargetProperty&);
// Internal
virtual void setConfiguration(const compositionengine::DisplayCreationArgs&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
index 692d78d..5127a6f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
@@ -49,6 +49,7 @@
const sp<Fence>& getClientTargetAcquireFence() const override;
void setBufferDataspace(ui::Dataspace) override;
+ void setBufferPixelFormat(ui::PixelFormat) override;
void setDisplaySize(const ui::Size&) override;
void setProtected(bool useProtected) override;
status_t beginFrame(bool mustRecompose) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
index ed4d492..a0cae6f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
@@ -36,6 +36,7 @@
MOCK_METHOD1(setDisplaySize, void(const ui::Size&));
MOCK_METHOD1(setProtected, void(bool));
MOCK_METHOD1(setBufferDataspace, void(ui::Dataspace));
+ MOCK_METHOD1(setBufferPixelFormat, void(ui::PixelFormat));
MOCK_METHOD1(beginFrame, status_t(bool mustRecompose));
MOCK_METHOD2(prepareFrame, void(bool, bool));
MOCK_METHOD1(dequeueBuffer, sp<GraphicBuffer>(base::unique_fd*));
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index ab26939..d201104 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -259,6 +259,7 @@
applyChangedTypesToLayers(changes->changedTypes);
applyDisplayRequests(changes->displayRequests);
applyLayerRequestsToLayers(changes->layerRequests);
+ applyClientTargetRequests(changes->clientTargetProperty);
}
// Determine what type of composition we are doing from the final state
@@ -326,6 +327,16 @@
}
}
+void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty) {
+ if (clientTargetProperty.dataspace == ui::Dataspace::UNKNOWN) {
+ return;
+ }
+ auto outputState = editState();
+ outputState.dataspace = clientTargetProperty.dataspace;
+ getRenderSurface()->setBufferDataspace(clientTargetProperty.dataspace);
+ getRenderSurface()->setBufferPixelFormat(clientTargetProperty.pixelFormat);
+}
+
compositionengine::Output::FrameFences Display::presentAndGetFrameFences() {
auto result = impl::Output::presentAndGetFrameFences();
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index ca5be48..4835aef 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -38,6 +38,7 @@
out.append("\n ");
+ dumpVal(out, "bounds", bounds);
dumpVal(out, "frame", frame);
dumpVal(out, "viewport", viewport);
dumpVal(out, "sourceClip", sourceClip);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 81f2dd1..1faf775 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -149,9 +149,9 @@
// a modification of the axes of rotation. To account for this we
// need to reorient the inverse rotation in terms of the current
// axes of rotation.
- bool is_h_flipped = (invTransform & HAL_TRANSFORM_FLIP_H) != 0;
- bool is_v_flipped = (invTransform & HAL_TRANSFORM_FLIP_V) != 0;
- if (is_h_flipped == is_v_flipped) {
+ bool isHFlipped = (invTransform & HAL_TRANSFORM_FLIP_H) != 0;
+ bool isVFlipped = (invTransform & HAL_TRANSFORM_FLIP_V) != 0;
+ if (isHFlipped == isVFlipped) {
invTransform ^= HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
}
std::swap(winWidth, winHeight);
@@ -160,18 +160,18 @@
activeCrop.transform(invTransform, bufferSize.getWidth(), bufferSize.getHeight());
// below, crop is intersected with winCrop expressed in crop's coordinate space
- float xScale = crop.getWidth() / float(winWidth);
- float yScale = crop.getHeight() / float(winHeight);
+ const float xScale = crop.getWidth() / float(winWidth);
+ const float yScale = crop.getHeight() / float(winHeight);
- float insetL = winCrop.left * xScale;
- float insetT = winCrop.top * yScale;
- float insetR = (winWidth - winCrop.right) * xScale;
- float insetB = (winHeight - winCrop.bottom) * yScale;
+ const float insetLeft = winCrop.left * xScale;
+ const float insetTop = winCrop.top * yScale;
+ const float insetRight = (winWidth - winCrop.right) * xScale;
+ const float insetBottom = (winHeight - winCrop.bottom) * yScale;
- crop.left += insetL;
- crop.top += insetT;
- crop.right -= insetR;
- crop.bottom -= insetB;
+ crop.left += insetLeft;
+ crop.top += insetTop;
+ crop.right -= insetRight;
+ crop.bottom -= insetBottom;
return crop;
}
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index 660baff..2773fd3 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -28,7 +28,6 @@
#include <log/log.h>
#include <renderengine/RenderEngine.h>
-#include <sync/sync.h>
#include <system/window.h>
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -105,6 +104,10 @@
static_cast<android_dataspace>(dataspace));
}
+void RenderSurface::setBufferPixelFormat(ui::PixelFormat pixelFormat) {
+ native_window_set_buffers_format(mNativeWindow.get(), static_cast<int32_t>(pixelFormat));
+}
+
void RenderSurface::setProtected(bool useProtected) {
uint64_t usageFlags = GRALLOC_USAGE_HW_RENDER;
if (useProtected) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 62977a4..09f37fb 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -654,6 +654,7 @@
{{nullptr, hal::Composition::CLIENT}},
hal::DisplayRequest::FLIP_CLIENT_TARGET,
{{nullptr, hal::LayerRequest::CLEAR_CLIENT_TARGET}},
+ {hal::PixelFormat::RGBA_8888, hal::Dataspace::UNKNOWN},
};
// Since two calls are made to anyLayersRequireClientComposition with different return
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 2cebecb..9aa274b 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -162,19 +162,19 @@
mOrientation = orientation;
const Rect& displayBounds = getCompositionDisplay()->getState().bounds;
- const int w = displayBounds.width();
- const int h = displayBounds.height();
+ const int displayWidth = displayBounds.width();
+ const int displayHeight = displayBounds.height();
- ui::Transform R;
+ ui::Transform rotation;
if (const auto flags = ui::Transform::toRotationFlags(orientation);
flags != ui::Transform::ROT_INVALID) {
- R.set(flags, w, h);
+ rotation.set(flags, displayWidth, displayHeight);
}
if (!frame.isValid()) {
// the destination frame can be invalid if it has never been set,
// in that case we assume the whole display frame.
- frame = Rect(w, h);
+ frame = Rect(displayWidth, displayHeight);
}
if (viewport.isEmpty()) {
@@ -182,45 +182,45 @@
// we assume the whole display size.
// it's also invalid to have an empty viewport, so we handle that
// case in the same way.
- viewport = Rect(w, h);
- if (R.getOrientation() & ui::Transform::ROT_90) {
+ viewport = Rect(displayWidth, displayHeight);
+ if (rotation.getOrientation() & ui::Transform::ROT_90) {
// viewport is always specified in the logical orientation
// of the display (ie: post-rotation).
std::swap(viewport.right, viewport.bottom);
}
}
- ui::Transform TL, TP, S;
- float src_width = viewport.width();
- float src_height = viewport.height();
- float dst_width = frame.width();
- float dst_height = frame.height();
- if (src_width != dst_width || src_height != dst_height) {
- float sx = dst_width / src_width;
- float sy = dst_height / src_height;
- S.set(sx, 0, 0, sy);
+ ui::Transform logicalTranslation, physicalTranslation, scale;
+ const float sourceWidth = viewport.width();
+ const float sourceHeight = viewport.height();
+ const float destWidth = frame.width();
+ const float destHeight = frame.height();
+ if (sourceWidth != destWidth || sourceHeight != destHeight) {
+ const float scaleX = destWidth / sourceWidth;
+ const float scaleY = destHeight / sourceHeight;
+ scale.set(scaleX, 0, 0, scaleY);
}
- float src_x = viewport.left;
- float src_y = viewport.top;
- float dst_x = frame.left;
- float dst_y = frame.top;
- TL.set(-src_x, -src_y);
- TP.set(dst_x, dst_y);
+ const float sourceX = viewport.left;
+ const float sourceY = viewport.top;
+ const float destX = frame.left;
+ const float destY = frame.top;
+ logicalTranslation.set(-sourceX, -sourceY);
+ physicalTranslation.set(destX, destY);
// need to take care of primary display rotation for globalTransform
// for case if the panel is not installed aligned with device orientation
if (isPrimary()) {
if (const auto flags = ui::Transform::toRotationFlags(orientation + mPhysicalOrientation);
flags != ui::Transform::ROT_INVALID) {
- R.set(flags, w, h);
+ rotation.set(flags, displayWidth, displayHeight);
}
}
// The viewport and frame are both in the logical orientation.
// Apply the logical translation, scale to physical size, apply the
// physical translation and finally rotate to the physical orientation.
- ui::Transform globalTransform = R * TP * S * TL;
+ ui::Transform globalTransform = rotation * physicalTranslation * scale * logicalTranslation;
const uint8_t type = globalTransform.getType();
const bool needsFiltering =
@@ -231,6 +231,8 @@
if (destinationClip.isEmpty()) {
destinationClip = displayBounds;
}
+ // Make sure the destination clip is contained in the display bounds
+ destinationClip.intersect(displayBounds, &destinationClip);
uint32_t transformOrientation;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index 97eeea2..a3f1b52 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -1354,6 +1354,12 @@
return error;
}
+Error Composer::getClientTargetProperty(
+ Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) {
+ mReader.takeClientTargetProperty(display, outClientTargetProperty);
+ return Error::NONE;
+}
+
CommandReader::~CommandReader()
{
resetData();
@@ -1662,10 +1668,23 @@
*state = data.presentOrValidateState;
}
+void CommandReader::takeClientTargetProperty(
+ Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) {
+ auto found = mReturnData.find(display);
+
+ // If not found, return the default values.
+ if (found == mReturnData.end()) {
+ outClientTargetProperty->pixelFormat = PixelFormat::RGBA_8888;
+ outClientTargetProperty->dataspace = Dataspace::UNKNOWN;
+ return;
+ }
+
+ ReturnData& data = found->second;
+ *outClientTargetProperty = data.clientTargetProperty;
+}
+
} // namespace impl
-
} // namespace Hwc2
-
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index aa43f09..00ef782 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -240,6 +240,8 @@
const std::vector<uint8_t>& value) = 0;
virtual V2_4::Error getLayerGenericMetadataKeys(
std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) = 0;
+ virtual Error getClientTargetProperty(
+ Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) = 0;
};
namespace impl {
@@ -282,6 +284,10 @@
// Get what stage succeeded during PresentOrValidate: Present or Validate
void takePresentOrValidateStage(Display display, uint32_t * state);
+ // Get the client target properties requested by hardware composer.
+ void takeClientTargetProperty(Display display,
+ IComposerClient::ClientTargetProperty* outClientTargetProperty);
+
private:
void resetData();
@@ -479,6 +485,9 @@
bool mandatory, const std::vector<uint8_t>& value) override;
V2_4::Error getLayerGenericMetadataKeys(
std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
+ Error getClientTargetProperty(
+ Display display,
+ IComposerClient::ClientTargetProperty* outClientTargetProperty) override;
private:
#if defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index fb82033..08559bd 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -208,13 +208,17 @@
*outVsyncPeriod = static_cast<nsecs_t>(vsyncPeriodNanos);
} else {
// Get the default vsync period
- HWConfigId configId = 0;
- auto intError_2_1 = mComposer.getActiveConfig(mId, &configId);
- error = static_cast<Error>(intError_2_1);
- if (error == Error::NONE) {
- auto config = mConfigs.at(configId);
- *outVsyncPeriod = config->getVsyncPeriod();
+ std::shared_ptr<const Display::Config> config;
+ error = getActiveConfig(&config);
+ if (error != Error::NONE) {
+ return error;
}
+ if (!config) {
+ // HWC has updated the display modes and hasn't notified us yet.
+ return Error::BAD_CONFIG;
+ }
+
+ *outVsyncPeriod = config->getVsyncPeriod();
}
return error;
@@ -668,6 +672,11 @@
return static_cast<Error>(intError);
}
+Error Display::getClientTargetProperty(ClientTargetProperty* outClientTargetProperty) {
+ const auto error = mComposer.getClientTargetProperty(mId, outClientTargetProperty);
+ return static_cast<Error>(error);
+}
+
// For use by Device
void Display::setConnected(bool connected) {
@@ -885,6 +894,10 @@
mComposer.setLayerPerFrameMetadata(mDisplayId, mId, perFrameMetadatas));
if (validTypes & HdrMetadata::HDR10PLUS) {
+ if (CC_UNLIKELY(mHdrMetadata.hdr10plus.size() == 0)) {
+ return Error::BAD_PARAMETER;
+ }
+
std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
perFrameMetadataBlobs.push_back(
{Hwc2::PerFrameMetadataKey::HDR10_PLUS_SEI, mHdrMetadata.hdr10plus});
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index f4c7fdd..6819ff4 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -234,6 +234,8 @@
[[clang::warn_unused_result]] virtual hal::Error getSupportedContentTypes(
std::vector<hal::ContentType>*) const = 0;
[[clang::warn_unused_result]] virtual hal::Error setContentType(hal::ContentType) = 0;
+ [[clang::warn_unused_result]] virtual hal::Error getClientTargetProperty(
+ hal::ClientTargetProperty* outClientTargetProperty) = 0;
};
namespace impl {
@@ -305,6 +307,8 @@
hal::Error getSupportedContentTypes(
std::vector<hal::ContentType>* outSupportedContentTypes) const override;
hal::Error setContentType(hal::ContentType) override;
+ hal::Error getClientTargetProperty(hal::ClientTargetProperty* outClientTargetProperty) override;
+
// Other Display methods
hal::HWDisplayId getId() const override { return mId; }
bool isConnected() const override { return mIsConnected; }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 038cec4..7a2f0f3 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -541,9 +541,12 @@
error = hwcDisplay->getRequests(&displayRequests, &layerRequests);
RETURN_IF_HWC_ERROR_FOR("getRequests", error, displayId, BAD_INDEX);
- outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
- std::move(layerRequests)});
+ DeviceRequestedChanges::ClientTargetProperty clientTargetProperty;
+ error = hwcDisplay->getClientTargetProperty(&clientTargetProperty);
+ outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
+ std::move(layerRequests),
+ std::move(clientTargetProperty)});
error = hwcDisplay->acceptChanges();
RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index b7e9f3a..c355ebd 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -66,6 +66,18 @@
class HWComposer {
public:
+ struct DeviceRequestedChanges {
+ using ChangedTypes = std::unordered_map<HWC2::Layer*, hal::Composition>;
+ using ClientTargetProperty = hal::ClientTargetProperty;
+ using DisplayRequests = hal::DisplayRequest;
+ using LayerRequests = std::unordered_map<HWC2::Layer*, hal::LayerRequest>;
+
+ ChangedTypes changedTypes;
+ DisplayRequests displayRequests;
+ LayerRequests layerRequests;
+ ClientTargetProperty clientTargetProperty;
+ };
+
virtual ~HWComposer();
virtual void setConfiguration(HWC2::ComposerCallback* callback, int32_t sequenceId) = 0;
@@ -88,16 +100,6 @@
// Destroy a previously created layer
virtual void destroyLayer(DisplayId displayId, HWC2::Layer* layer) = 0;
- struct DeviceRequestedChanges {
- using ChangedTypes = std::unordered_map<HWC2::Layer*, hal::Composition>;
- using DisplayRequests = hal::DisplayRequest;
- using LayerRequests = std::unordered_map<HWC2::Layer*, hal::LayerRequest>;
-
- ChangedTypes changedTypes;
- DisplayRequests displayRequests;
- LayerRequests layerRequests;
- };
-
// Gets any required composition change requests from the HWC device.
//
// Note that frameUsesClientComposition must be set correctly based on
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 66ee425..bb2888e 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -53,6 +53,7 @@
using Connection = IComposerCallback::Connection;
using ContentType = IComposerClient::ContentType;
using Capability = IComposer::Capability;
+using ClientTargetProperty = IComposerClient::ClientTargetProperty;
using DisplayCapability = IComposerClient::DisplayCapability;
using DisplayRequest = IComposerClient::DisplayRequest;
using DisplayType = IComposerClient::DisplayType;
diff --git a/services/surfaceflinger/FrameTracer/FrameTracer.cpp b/services/surfaceflinger/FrameTracer/FrameTracer.cpp
index b986f38..2dfb9a9 100644
--- a/services/surfaceflinger/FrameTracer/FrameTracer.cpp
+++ b/services/surfaceflinger/FrameTracer/FrameTracer.cpp
@@ -25,7 +25,7 @@
#include "FrameTracer.h"
#include <android-base/stringprintf.h>
-#include <perfetto/trace/clock_snapshot.pbzero.h>
+#include <perfetto/common/builtin_clock.pbzero.h>
#include <algorithm>
#include <mutex>
@@ -34,7 +34,6 @@
namespace android {
-using Clock = perfetto::protos::pbzero::ClockSnapshot::Clock;
void FrameTracer::initialize() {
std::call_once(mInitializationFlag, [this]() {
perfetto::TracingInitArgs args;
@@ -136,7 +135,7 @@
uint64_t bufferID, uint64_t frameNumber, nsecs_t timestamp,
FrameEvent::BufferEventType type, nsecs_t duration) {
auto packet = ctx.NewTracePacket();
- packet->set_timestamp_clock_id(Clock::MONOTONIC);
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
packet->set_timestamp(timestamp);
auto* event = packet->set_graphics_frame_event()->set_buffer_event();
event->set_buffer_id(static_cast<uint32_t>(bufferID));
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index a19b5cb..49112dd 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -119,6 +119,13 @@
mCurrentState.treeHasFrameRateVote = false;
mCurrentState.fixedTransformHint = ui::Transform::ROT_INVALID;
+ if (args.flags & ISurfaceComposerClient::eNoColorFill) {
+ // Set an invalid color so there is no color fill.
+ mCurrentState.color.r = -1.0_hf;
+ mCurrentState.color.g = -1.0_hf;
+ mCurrentState.color.b = -1.0_hf;
+ }
+
// drawing state & current state are identical
mDrawingState = mCurrentState;
@@ -222,13 +229,23 @@
mFlinger->markLayerPendingRemovalLocked(this);
}
+sp<Layer> Layer::getRootLayer() {
+ sp<Layer> parent = getParent();
+ if (parent == nullptr) {
+ return this;
+ }
+ return parent->getRootLayer();
+}
+
void Layer::onRemovedFromCurrentState() {
- auto layersInTree = getLayersInTree(LayerVector::StateSet::Current);
+ // Use the root layer since we want to maintain the hierarchy for the entire subtree.
+ auto layersInTree = getRootLayer()->getLayersInTree(LayerVector::StateSet::Current);
std::sort(layersInTree.begin(), layersInTree.end());
- for (const auto& layer : layersInTree) {
+
+ traverse(LayerVector::StateSet::Current, [&](Layer* layer) {
layer->removeFromCurrentState();
layer->removeRelativeZ(layersInTree);
- }
+ });
}
void Layer::addToCurrentState() {
@@ -848,11 +865,14 @@
}
}
- // If we still have pending updates, wake SurfaceFlinger back up and point
- // it at this layer so we can process them
+ // If we still have pending updates, we need to ensure SurfaceFlinger
+ // will keep calling doTransaction, and so we set the transaction flags.
+ // However, our pending states won't clear until a frame is available,
+ // and so there is no need to specifically trigger a wakeup. Rather
+ // we set the flags and wait for something else to wake us up.
if (!mPendingStates.empty()) {
setTransactionFlags(eTransactionNeeded);
- mFlinger->setTransactionFlags(eTraversalNeeded);
+ mFlinger->setTransactionFlagsNoWake(eTraversalNeeded);
}
mCurrentState.modified = false;
@@ -1353,8 +1373,15 @@
// First traverse the tree and count how many layers has votes
int layersWithVote = 0;
traverseTree([&layersWithVote](Layer* layer) {
- if (layer->mCurrentState.frameRate.rate > 0 ||
- layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote) {
+ const auto layerVotedWithDefaultCompatibility = layer->mCurrentState.frameRate.rate > 0 &&
+ layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
+ const auto layerVotedWithNoVote =
+ layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote;
+
+ // We do not count layers that are ExactOrMultiple for the same reason
+ // we are allowing touch boost for those layers. See
+ // RefreshRateConfigs::getBestRefreshRate for more details.
+ if (layerVotedWithDefaultCompatibility || layerVotedWithNoVote) {
layersWithVote++;
}
});
@@ -1385,7 +1412,8 @@
}
// Activate the layer in Scheduler's LayerHistory
- mFlinger->mScheduler->recordLayerHistory(this, systemTime());
+ mFlinger->mScheduler->recordLayerHistory(this, systemTime(),
+ LayerHistory::LayerUpdateType::SetFrameRate);
mCurrentState.sequence++;
mCurrentState.frameRate = frameRate;
@@ -1483,7 +1511,7 @@
LayerDebugInfo info;
const State& ds = getDrawingState();
info.mName = getName();
- sp<Layer> parent = getParent();
+ sp<Layer> parent = mDrawingParent.promote();
info.mParentName = parent ? parent->getName() : "none"s;
info.mType = getType();
info.mTransparentRegion = ds.activeTransparentRegion_legacy;
@@ -2104,7 +2132,9 @@
// but a transform matrix can define horizontal and vertical scales.
// Let's take the average between both of them and pass into the shader, practically we
// never do this type of transformation on windows anyway.
- parentState.radius *= (t[0][0] + t[1][1]) / 2.0f;
+ auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]);
+ auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]);
+ parentState.radius *= (scaleX + scaleY) / 2.0f;
return parentState;
}
}
@@ -2324,6 +2354,16 @@
}
InputWindowInfo Layer::fillInputInfo() {
+ if (!hasInputInfo()) {
+ mDrawingState.inputInfo.name = getName();
+ mDrawingState.inputInfo.ownerUid = mCallingUid;
+ mDrawingState.inputInfo.ownerPid = mCallingPid;
+ mDrawingState.inputInfo.inputFeatures =
+ InputWindowInfo::INPUT_FEATURE_NO_INPUT_CHANNEL;
+ mDrawingState.inputInfo.layoutParamsFlags = InputWindowInfo::FLAG_NOT_TOUCH_MODAL;
+ mDrawingState.inputInfo.displayId = getLayerStack();
+ }
+
InputWindowInfo info = mDrawingState.inputInfo;
info.id = sequence;
@@ -2405,7 +2445,7 @@
return mDrawingParent.promote()->getClonedRoot();
}
-bool Layer::hasInput() const {
+bool Layer::hasInputInfo() const {
return mDrawingState.inputInfo.token != nullptr;
}
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 3fa935f..068424b 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -947,7 +947,17 @@
void setInputInfo(const InputWindowInfo& info);
InputWindowInfo fillInputInfo();
- bool hasInput() const;
+ /**
+ * Returns whether this layer has an explicitly set input-info.
+ */
+ bool hasInputInfo() const;
+ /**
+ * Return whether this layer needs an input info. For most layer types
+ * this is only true if they explicitly set an input-info but BufferLayer
+ * overrides this so we can generate input-info for Buffered layers that don't
+ * have them (for input occlusion detection checks).
+ */
+ virtual bool needsInputInfo() const { return hasInputInfo(); }
protected:
compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const;
@@ -1088,6 +1098,10 @@
// Find the root of the cloned hierarchy, this means the first non cloned parent.
// This will return null if first non cloned parent is not found.
sp<Layer> getClonedRoot();
+
+ // Finds the top most layer in the hierarchy. This will find the root Layer where the parent is
+ // null.
+ sp<Layer> getRootLayer();
};
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 8958d9a..2925109 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -79,7 +79,8 @@
mLayerInfos.emplace_back(layer, std::move(info));
}
-void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
+void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
+ LayerUpdateType /*updateType*/) {
std::lock_guard lock(mLock);
const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index a1ae35c..228b8a0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -52,8 +52,18 @@
// Sets the display size. Client is responsible for synchronization.
virtual void setDisplayArea(uint32_t displayArea) = 0;
+ // Sets whether a config change is pending to be applied
+ virtual void setConfigChangePending(bool pending) = 0;
+
+ // Represents which layer activity is recorded
+ enum class LayerUpdateType {
+ Buffer, // a new buffer queued
+ AnimationTX, // a new transaction with eAnimation flag set
+ SetFrameRate, // setFrameRate API was called
+ };
+
// Marks the layer as active, and records the given state to its history.
- virtual void record(Layer*, nsecs_t presentTime, nsecs_t now) = 0;
+ virtual void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) = 0;
using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
@@ -78,8 +88,10 @@
void setDisplayArea(uint32_t /*displayArea*/) override {}
+ void setConfigChangePending(bool /*pending*/) override {}
+
// Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
+ void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
// Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
android::scheduler::LayerHistory::Summary summarize(nsecs_t now) override;
@@ -124,7 +136,7 @@
class LayerHistoryV2 : public android::scheduler::LayerHistory {
public:
- LayerHistoryV2();
+ LayerHistoryV2(const scheduler::RefreshRateConfigs&);
virtual ~LayerHistoryV2();
// Layers are unregistered when the weak reference expires.
@@ -134,8 +146,10 @@
// Sets the display size. Client is responsible for synchronization.
void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
+ void setConfigChangePending(bool pending) override { mConfigChangePending = pending; }
+
// Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
+ void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
// Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
@@ -178,6 +192,9 @@
// Whether to use priority sent from WindowManager to determine the relevancy of the layer.
const bool mUseFrameRatePriority;
+
+ // Whether a config change is in progress or not
+ std::atomic<bool> mConfigChangePending = false;
};
} // namespace impl
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index e6c5cc9..ee612b0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -58,44 +58,44 @@
return atoi(value);
}
-void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
+ int fps) {
const auto layer = weak.promote();
if (!layer) return;
- const auto& name = layer->getName();
- const auto noVoteTag = "LFPS NoVote " + name;
- const auto heuristicVoteTag = "LFPS Heuristic " + name;
- const auto explicitDefaultVoteTag = "LFPS ExplicitDefault" + name;
- const auto explicitExactOrMultipleVoteTag = "LFPS ExplicitExactOrMultiple" + name;
- const auto minVoteTag = "LFPS Min " + name;
- const auto maxVoteTag = "LFPS Max " + name;
+ const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+ ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+ };
- ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
- ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
- ATRACE_INT(explicitDefaultVoteTag.c_str(),
- type == LayerHistory::LayerVoteType::ExplicitDefault ? fps : 0);
- ATRACE_INT(explicitExactOrMultipleVoteTag.c_str(),
- type == LayerHistory::LayerVoteType::ExplicitExactOrMultiple ? fps : 0);
- ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
- ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
+ traceType(LayerHistory::LayerVoteType::NoVote, 1);
+ traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+ traceType(LayerHistory::LayerVoteType::Min, 1);
+ traceType(LayerHistory::LayerVoteType::Max, 1);
- ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+ ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
}
} // namespace
-LayerHistoryV2::LayerHistoryV2()
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
+ : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+ LayerInfoV2::setTraceEnabled(mTraceEnabled);
+ LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
+}
+
LayerHistoryV2::~LayerHistoryV2() = default;
void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
LayerVoteType type) {
const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
- auto info = std::make_unique<LayerInfoV2>(highRefreshRatePeriod, type);
+ auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
std::lock_guard lock(mLock);
mLayerInfos.emplace_back(layer, std::move(info));
}
-void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
+void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
+ LayerUpdateType updateType) {
std::lock_guard lock(mLock);
const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -103,7 +103,7 @@
LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
const auto& info = it->second;
- info->setLastPresentTime(presentTime, now);
+ info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
// Activate layer if inactive.
if (const auto end = activeLayers().end(); it >= end) {
@@ -146,7 +146,7 @@
summary.push_back({strong->getName(), type, refreshRate, weight});
if (CC_UNLIKELY(mTraceEnabled)) {
- trace(layer, type, static_cast<int>(std::round(refreshRate)));
+ trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));
}
}
@@ -185,10 +185,10 @@
}
if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
+ trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
}
- info->clearHistory();
+ info->onLayerInactive(now);
std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
}
@@ -209,7 +209,8 @@
std::lock_guard lock(mLock);
for (const auto& [layer, info] : activeLayers()) {
- info->clearHistory();
+ info->clearHistory(systemTime());
}
}
+
} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index cb81ca2..820624b 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -98,9 +98,9 @@
return false;
}
- // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+ // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
if (mElements.size() < HISTORY_SIZE &&
- mElements.back() - mElements.front() < HISTORY_TIME.count()) {
+ mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
return false;
}
@@ -124,7 +124,7 @@
private:
std::deque<nsecs_t> mElements;
- static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
};
friend class LayerHistoryTest;
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index b7d0bdd..44f20d0 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -15,33 +15,51 @@
*/
// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "LayerInfoV2.h"
#include <algorithm>
#include <utility>
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
#undef LOG_TAG
#define LOG_TAG "LayerInfoV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
namespace android::scheduler {
-LayerInfoV2::LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote)
- : mHighRefreshRatePeriod(highRefreshRatePeriod),
- mDefaultVote(defaultVote),
- mLayerVote({defaultVote, 0.0f}) {}
+const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
+bool LayerInfoV2::sTraceEnabled = false;
-void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote)
+ : mName(name),
+ mHighRefreshRatePeriod(highRefreshRatePeriod),
+ mDefaultVote(defaultVote),
+ mLayerVote({defaultVote, 0.0f}),
+ mRefreshRateHistory(name) {}
+
+void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
+ LayerUpdateType updateType, bool pendingConfigChange) {
lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
mLastUpdatedTime = std::max(lastPresentTime, now);
-
- FrameTimeData frameTime = {.presetTime = lastPresentTime, .queueTime = mLastUpdatedTime};
-
- mFrameTimes.push_back(frameTime);
- if (mFrameTimes.size() > HISTORY_SIZE) {
- mFrameTimes.pop_front();
+ switch (updateType) {
+ case LayerUpdateType::AnimationTX:
+ mLastAnimationTime = std::max(lastPresentTime, now);
+ break;
+ case LayerUpdateType::SetFrameRate:
+ case LayerUpdateType::Buffer:
+ FrameTimeData frameTime = {.presetTime = lastPresentTime,
+ .queueTime = mLastUpdatedTime,
+ .pendingConfigChange = pendingConfigChange};
+ mFrameTimes.push_back(frameTime);
+ if (mFrameTimes.size() > HISTORY_SIZE) {
+ mFrameTimes.pop_front();
+ }
+ break;
}
}
@@ -52,21 +70,14 @@
}
bool LayerInfoV2::isFrequent(nsecs_t now) const {
- // Find the first valid frame time
- auto it = mFrameTimes.begin();
- for (; it != mFrameTimes.end(); ++it) {
- if (isFrameTimeValid(*it)) {
- break;
- }
- }
-
// If we know nothing about this layer we consider it as frequent as it might be the start
// of an animation.
- if (std::distance(it, mFrameTimes.end()) < FREQUENT_LAYER_WINDOW_SIZE) {
+ if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
return true;
}
// Find the first active frame
+ auto it = mFrameTimes.begin();
for (; it != mFrameTimes.end(); ++it) {
if (it->queueTime >= getActiveLayerThreshold(now)) {
break;
@@ -83,42 +94,54 @@
return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
}
+bool LayerInfoV2::isAnimating(nsecs_t now) const {
+ return mLastAnimationTime >= getActiveLayerThreshold(now);
+}
+
bool LayerInfoV2::hasEnoughDataForHeuristic() const {
- // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+ // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
if (mFrameTimes.size() < 2) {
+ ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
return false;
}
if (!isFrameTimeValid(mFrameTimes.front())) {
+ ALOGV("stale frames still captured");
return false;
}
- if (mFrameTimes.size() < HISTORY_SIZE &&
- mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
+ const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+ if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+ ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+ totalDuration / 1e9f);
return false;
}
return true;
}
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
- static constexpr float MARGIN = 1.0f; // 1Hz
-
- if (!hasEnoughDataForHeuristic()) {
- ALOGV("Not enough data");
- return std::nullopt;
- }
-
- // Calculate the refresh rate by finding the average delta between frames
+std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
nsecs_t totalPresentTimeDeltas = 0;
nsecs_t totalQueueTimeDeltas = 0;
- auto missingPresentTime = false;
+ bool missingPresentTime = false;
+ int numFrames = 0;
for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+ // Ignore frames captured during a config change
+ if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+ return std::nullopt;
+ }
+
totalQueueTimeDeltas +=
std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+ numFrames++;
- if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
+ 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) {
+ return std::nullopt;
+ }
continue;
}
@@ -126,11 +149,6 @@
std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
}
- // If there are no presentation timestamps provided we can't calculate the refresh rate
- if (missingPresentTime && mLastReportedRefreshRate == 0) {
- return std::nullopt;
- }
-
// Calculate the average frame time based on presentation timestamps. If those
// doesn't exist, we look at the time the buffer was queued only. We can do that only if
// we calculated a refresh rate based on presentation timestamps in the past. The reason
@@ -138,48 +156,142 @@
// when implementing render ahead for specific refresh rates. When hwui no longer provides
// presentation timestamps we look at the queue time to see if the current refresh rate still
// matches the content.
- const float averageFrameTime =
+
+ const auto averageFrameTime =
static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
- (mFrameTimes.size() - 1);
+ numFrames;
+ return static_cast<nsecs_t>(averageFrameTime);
+}
- // Now once we calculated the refresh rate we need to make sure that all the frames we captured
- // are evenly distributed and we don't calculate the average across some burst of frames.
- for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
- const auto presentTimeDeltas = [&] {
- const auto delta = missingPresentTime ? (it + 1)->queueTime - it->queueTime
- : (it + 1)->presetTime - it->presetTime;
- return std::max(delta, mHighRefreshRatePeriod);
- }();
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
+ static constexpr float MARGIN = 1.0f; // 1Hz
+ if (!hasEnoughDataForHeuristic()) {
+ ALOGV("Not enough data");
+ return std::nullopt;
+ }
- if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
- return std::nullopt;
+ const auto averageFrameTime = calculateAverageFrameTime();
+ if (averageFrameTime.has_value()) {
+ const auto refreshRate = 1e9f / *averageFrameTime;
+ const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+ if (refreshRateConsistent) {
+ const auto knownRefreshRate =
+ sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
+
+ // To avoid oscillation, use the last calculated refresh rate if it is
+ // close enough
+ if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+ mLastRefreshRate.reported != knownRefreshRate) {
+ mLastRefreshRate.calculated = refreshRate;
+ mLastRefreshRate.reported = knownRefreshRate;
+ }
+
+ ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
+ } else {
+ ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
}
}
- const auto refreshRate = 1e9f / averageFrameTime;
- if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
- mLastReportedRefreshRate = refreshRate;
- }
-
- ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
- return mLastReportedRefreshRate;
+ return mLastRefreshRate.reported == 0 ? std::nullopt
+ : std::make_optional(mLastRefreshRate.reported);
}
std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+ ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
return {mLayerVote.type, mLayerVote.fps};
}
+ if (isAnimating(now)) {
+ ALOGV("%s is animating", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
+ return {LayerHistory::LayerVoteType::Max, 0};
+ }
+
if (!isFrequent(now)) {
+ ALOGV("%s is infrequent", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
return {LayerHistory::LayerVoteType::Min, 0};
}
- auto refreshRate = calculateRefreshRateIfPossible();
+ // If the layer was previously tagged as animating or infrequent, we clear
+ // the history as it is likely the layer just changed its behavior
+ // and we should not look at stale data
+ if (mLastRefreshRate.animatingOrInfrequent) {
+ clearHistory(now);
+ }
+
+ auto refreshRate = calculateRefreshRateIfPossible(now);
if (refreshRate.has_value()) {
+ ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
}
+ ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
return {LayerHistory::LayerVoteType::Max, 0};
}
+const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+ if (mTraceTags.count(type) == 0) {
+ const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+ mTraceTags.emplace(type, tag);
+ }
+
+ return mTraceTags.at(type).c_str();
+}
+
+LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
+LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
+ const std::string prefix = "LFPS ";
+ const std::string suffix = "Heuristic ";
+ return {.min = prefix + mName + suffix + "min",
+ .max = prefix + mName + suffix + "max",
+ .consistent = prefix + mName + suffix + "consistent",
+ .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfoV2::RefreshRateHistory::clear() {
+ mRefreshRates.clear();
+}
+
+bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+ mRefreshRates.push_back({refreshRate, now});
+ while (mRefreshRates.size() >= HISTORY_SIZE ||
+ now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+ mRefreshRates.pop_front();
+ }
+
+ if (CC_UNLIKELY(sTraceEnabled)) {
+ if (!mHeuristicTraceTagData.has_value()) {
+ mHeuristicTraceTagData = makeHeuristicTraceTagData();
+ }
+
+ ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+ }
+
+ return isConsistent();
+}
+
+bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
+ if (mRefreshRates.empty()) return true;
+
+ 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;
+
+ 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->consistent.c_str(), consistent);
+ }
+
+ return consistent;
+}
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index e36b7f7..33dc66f 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -43,6 +43,8 @@
// Stores history of present times and refresh rates for a layer.
class LayerInfoV2 {
+ using LayerUpdateType = LayerHistory::LayerUpdateType;
+
// Layer is considered frequent if the earliest value in the window of most recent present times
// is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
// favor of a low refresh rate.
@@ -54,7 +56,14 @@
friend class LayerHistoryTestV2;
public:
- LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote);
+ static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
+ static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+ sRefreshRateConfigs = &refreshRateConfigs;
+ }
+
+ LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote);
LayerInfoV2(const LayerInfo&) = delete;
LayerInfoV2& operator=(const LayerInfoV2&) = delete;
@@ -62,7 +71,8 @@
// Records the last requested present time. It also stores information about when
// the layer was last updated. If the present time is farther in the future than the
// updated time, the updated time is the present time.
- void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+ void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+ bool pendingConfigChange);
// Sets an explicit layer vote. This usually comes directly from the application via
// ANativeWindow_setFrameRate API
@@ -82,13 +92,23 @@
// updated time, the updated time is the present time.
nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
- void clearHistory() {
+ // Returns a C string for tracing a vote
+ const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
+ void onLayerInactive(nsecs_t now) {
// Mark mFrameTimeValidSince to now to ignore all previous frame times.
// We are not deleting the old frame to keep track of whether we should treat the first
// buffer as Max as we don't know anything about this layer or Min as this layer is
// posting infrequent updates.
- mFrameTimeValidSince = std::chrono::steady_clock::now();
- mLastReportedRefreshRate = 0.0f;
+ const auto timePoint = std::chrono::nanoseconds(now);
+ mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
+ mLastRefreshRate = {};
+ mRefreshRateHistory.clear();
+ }
+
+ void clearHistory(nsecs_t now) {
+ onLayerInactive(now);
+ mFrameTimes.clear();
}
private:
@@ -96,32 +116,105 @@
struct FrameTimeData {
nsecs_t presetTime; // desiredPresentTime, if provided
nsecs_t queueTime; // buffer queue time
+ bool pendingConfigChange;
+ };
+
+ // Holds information about the calculated and reported refresh rate
+ struct RefreshRateHeuristicData {
+ // Rate calculated on the layer
+ float calculated = 0.0f;
+ // Last reported rate for LayerInfoV2::getRefreshRate()
+ float reported = 0.0f;
+ // Whether the last reported rate for LayerInfoV2::getRefreshRate()
+ // was due to animation or infrequent updates
+ bool animatingOrInfrequent = false;
+ };
+
+ // Holds information about the layer vote
+ struct LayerVote {
+ LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+ float fps = 0.0f;
+ };
+
+ // Class to store past calculated refresh rate and determine whether
+ // the refresh rate calculated is consistent with past values
+ class RefreshRateHistory {
+ public:
+ static constexpr auto HISTORY_SIZE = 90;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+ RefreshRateHistory(const std::string& name) : mName(name) {}
+
+ // Clears History
+ void clear();
+
+ // Adds a new refresh rate and returns true if it is consistent
+ bool add(float refreshRate, nsecs_t now);
+
+ private:
+ friend class LayerHistoryTestV2;
+
+ // Holds the refresh rate when it was calculated
+ struct RefreshRateData {
+ float refreshRate = 0.0f;
+ nsecs_t timestamp = 0;
+
+ bool operator<(const RefreshRateData& other) const {
+ return refreshRate < other.refreshRate;
+ }
+ };
+
+ // Holds tracing strings
+ struct HeuristicTraceTagData {
+ std::string min;
+ std::string max;
+ std::string consistent;
+ std::string average;
+ };
+
+ bool isConsistent() const;
+ HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+ const std::string mName;
+ mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+ std::deque<RefreshRateData> mRefreshRates;
+ static constexpr float MARGIN_FPS = 1.0;
};
bool isFrequent(nsecs_t now) const;
+ bool isAnimating(nsecs_t now) const;
bool hasEnoughDataForHeuristic() const;
- std::optional<float> calculateRefreshRateIfPossible();
+ std::optional<float> 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;
LayerHistory::LayerVoteType mDefaultVote;
+ LayerVote mLayerVote;
+
nsecs_t mLastUpdatedTime = 0;
- float mLastReportedRefreshRate = 0.0f;
+ nsecs_t mLastAnimationTime = 0;
- // Holds information about the layer vote
- struct {
- LayerHistory::LayerVoteType type;
- float fps;
- } mLayerVote;
+ RefreshRateHeuristicData mLastRefreshRate;
std::deque<FrameTimeData> mFrameTimes;
std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
std::chrono::steady_clock::now();
- static constexpr size_t HISTORY_SIZE = 90;
- static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+ static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+ RefreshRateHistory mRefreshRateHistory;
+
+ mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
+
+ // Shared for all LayerInfo instances
+ static const RefreshRateConfigs* sRefreshRateConfigs;
+ static bool sTraceEnabled;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 8d958df..6dbff14 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -31,6 +31,23 @@
using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
using RefreshRate = RefreshRateConfigs::RefreshRate;
+std::string RefreshRateConfigs::layerVoteTypeString(LayerVoteType vote) {
+ switch (vote) {
+ case LayerVoteType::NoVote:
+ return "NoVote";
+ case LayerVoteType::Min:
+ return "Min";
+ case LayerVoteType::Max:
+ return "Max";
+ case LayerVoteType::Heuristic:
+ return "Heuristic";
+ case LayerVoteType::ExplicitDefault:
+ return "ExplicitDefault";
+ case LayerVoteType::ExplicitExactOrMultiple:
+ return "ExplicitExactOrMultiple";
+ }
+}
+
const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
const std::vector<LayerRequirement>& layers) const {
std::lock_guard lock(mLock);
@@ -98,12 +115,24 @@
}
const RefreshRate& RefreshRateConfigs::getBestRefreshRate(
- const std::vector<LayerRequirement>& layers, bool touchActive, bool idle,
- bool* touchConsidered) const {
+ const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
+ GlobalSignals* outSignalsConsidered) const {
ATRACE_CALL();
ALOGV("getRefreshRateForContent %zu layers", layers.size());
- *touchConsidered = false;
+ if (outSignalsConsidered) *outSignalsConsidered = {};
+ const auto setTouchConsidered = [&] {
+ if (outSignalsConsidered) {
+ outSignalsConsidered->touch = true;
+ }
+ };
+
+ const auto setIdleConsidered = [&] {
+ if (outSignalsConsidered) {
+ outSignalsConsidered->idle = true;
+ }
+ };
+
std::lock_guard lock(mLock);
int noVoteLayers = 0;
@@ -128,28 +157,40 @@
}
}
+ const bool hasExplicitVoteLayers =
+ explicitDefaultVoteLayers > 0 || explicitExactOrMultipleVoteLayers > 0;
+
// Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
// selected a refresh rate to see if we should apply touch boost.
- if (touchActive && explicitDefaultVoteLayers == 0 && explicitExactOrMultipleVoteLayers == 0) {
- *touchConsidered = true;
+ if (globalSignals.touch && !hasExplicitVoteLayers) {
+ ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
+ setTouchConsidered();
return getMaxRefreshRateByPolicyLocked();
}
- if (!touchActive && idle) {
+ // If the primary range consists of a single refresh rate then we can only
+ // 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;
+
+ if (!globalSignals.touch && globalSignals.idle &&
+ !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
+ ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
+ setIdleConsidered();
return getMinRefreshRateByPolicyLocked();
}
- if (layers.empty()) {
- return getCurrentRefreshRateByPolicyLocked();
+ if (layers.empty() || noVoteLayers == layers.size()) {
+ return getMaxRefreshRateByPolicyLocked();
}
// Only if all layers want Min we should return Min
if (noVoteLayers + minVoteLayers == layers.size()) {
+ ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
return getMinRefreshRateByPolicyLocked();
}
- const Policy* policy = getCurrentPolicyLocked();
-
// Find the best refresh rate based on score
std::vector<std::pair<const RefreshRate*, float>> scores;
scores.reserve(mAppRequestRefreshRates.size());
@@ -159,7 +200,8 @@
}
for (const auto& layer : layers) {
- ALOGV("Calculating score for %s (type: %d)", layer.name.c_str(), layer.vote);
+ ALOGV("Calculating score for %s (%s, weight %.2f)", layer.name.c_str(),
+ layerVoteTypeString(layer.vote).c_str(), layer.weight);
if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
continue;
}
@@ -169,7 +211,8 @@
for (auto i = 0u; i < scores.size(); i++) {
bool inPrimaryRange =
scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);
- if (!inPrimaryRange && layer.vote != LayerVoteType::ExplicitDefault &&
+ if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
+ layer.vote != LayerVoteType::ExplicitDefault &&
layer.vote != LayerVoteType::ExplicitExactOrMultiple) {
// Only layers with explicit frame rate settings are allowed to score refresh rates
// outside the primary range.
@@ -213,7 +256,7 @@
if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
layer.vote == LayerVoteType::Heuristic) {
- const auto layerScore = [&]() {
+ const auto layerScore = [&] {
// Calculate how many display vsyncs we need to present a single frame for this
// layer
const auto [displayFramesQuot, displayFramesRem] =
@@ -243,9 +286,9 @@
return 1.0f / iter;
}();
- ALOGV("%s (ExplicitExactOrMultiple, weight %.2f) %.2fHz gives %s score of %.2f",
- layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),
- layerScore);
+ ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
+ layerVoteTypeString(layer.vote).c_str(), weight, 1e9f / layerPeriod,
+ scores[i].first->name.c_str(), layerScore);
scores[i].second += weight * layerScore;
continue;
}
@@ -259,14 +302,29 @@
? getBestRefreshRate(scores.rbegin(), scores.rend())
: getBestRefreshRate(scores.begin(), scores.end());
+ if (primaryRangeIsSingleRate) {
+ // 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; })) {
+ ALOGV("layers not scored - choose %s",
+ getMaxRefreshRateByPolicyLocked().getName().c_str());
+ return getMaxRefreshRateByPolicyLocked();
+ } else {
+ return *bestRefreshRate;
+ }
+ }
+
// Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly
// interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
// vote we should not change it if we get a touch event. Only apply touch boost if it will
// actually increase the refresh rate over the normal selection.
const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
- if (touchActive && explicitDefaultVoteLayers == 0 &&
+
+ if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&
bestRefreshRate->fps < touchRefreshRate.fps) {
- *touchConsidered = true;
+ setTouchConsidered();
+ ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
return touchRefreshRate;
}
@@ -340,7 +398,8 @@
RefreshRateConfigs::RefreshRateConfigs(
const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
- HwcConfigIndexType currentConfigId) {
+ HwcConfigIndexType currentConfigId)
+ : mKnownFrameRates(constructKnownFrameRates(configs)) {
LOG_ALWAYS_FATAL_IF(configs.empty());
LOG_ALWAYS_FATAL_IF(currentConfigId.value() >= configs.size());
@@ -500,4 +559,40 @@
&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()) {
+ return *mKnownFrameRates.begin();
+ }
+
+ if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+ return *std::prev(mKnownFrameRates.end());
+ }
+
+ auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+
+ const auto distance1 = std::abs(frameRate - *lowerBound);
+ const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+ return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
+}
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 2657dee..584a5e7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -29,7 +29,6 @@
#include "Scheduler/StrongTyping.h"
namespace android::scheduler {
-class RefreshRateConfigsTest;
using namespace std::chrono_literals;
@@ -87,7 +86,7 @@
private:
friend RefreshRateConfigs;
- friend RefreshRateConfigsTest;
+ friend class RefreshRateConfigsTest;
// The tolerance within which we consider FPS approximately equals.
static constexpr float FPS_EPSILON = 0.001f;
@@ -212,14 +211,22 @@
const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
EXCLUDES(mLock);
+ // Global state describing signals that affect refresh rate choice.
+ struct GlobalSignals {
+ // Whether the user touched the screen recently. Used to apply touch boost.
+ bool touch = false;
+ // True if the system hasn't seen any buffers posted to layers recently.
+ bool idle = false;
+ };
+
// Returns the refresh rate that fits best to the given layers.
// layers - The layer requirements to consider.
- // touchActive - Whether the user touched the screen recently. Used to apply touch boost.
- // idle - True if the system hasn't seen any buffers posted to layers recently.
- // touchConsidered - An output param that tells the caller whether the refresh rate was chosen
- // based on touch boost.
+ // 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,
- bool touchActive, bool idle, bool* touchConsidered) const
+ const GlobalSignals& globalSignals,
+ GlobalSignals* outSignalsConsidered = nullptr) const
EXCLUDES(mLock);
// Returns all the refresh rates supported by the device. This won't change at runtime.
@@ -255,11 +262,21 @@
// Stores the current configId the device operates at
void setCurrentConfigId(HwcConfigIndexType configId) EXCLUDES(mLock);
+ // Returns a string that represents the layer vote type
+ static std::string layerVoteTypeString(LayerVoteType vote);
+
+ // Returns a known frame rate that is the closest to frameRate
+ float findClosestKnownFrameRate(float frameRate) const;
+
RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
HwcConfigIndexType currentConfigId);
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(
const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
@@ -317,6 +334,10 @@
const RefreshRate* mMaxSupportedRefreshRate;
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;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index d73fd8b..5c0ba01 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -62,19 +62,19 @@
namespace android {
-std::unique_ptr<DispSync> createDispSync() {
+std::unique_ptr<DispSync> createDispSync(bool supportKernelTimer) {
// TODO (140302863) remove this and use the vsync_reactor system.
if (property_get_bool("debug.sf.vsync_reactor", true)) {
// TODO (144707443) tune Predictor tunables.
- static constexpr int default_rate = 60;
- static constexpr auto initial_period =
- std::chrono::duration<nsecs_t, std::ratio<1, default_rate>>(1);
+ static constexpr int defaultRate = 60;
+ static constexpr auto initialPeriod =
+ std::chrono::duration<nsecs_t, std::ratio<1, defaultRate>>(1);
static constexpr size_t vsyncTimestampHistorySize = 20;
static constexpr size_t minimumSamplesForPrediction = 6;
static constexpr uint32_t discardOutlierPercent = 20;
auto tracker = std::make_unique<
scheduler::VSyncPredictor>(std::chrono::duration_cast<std::chrono::nanoseconds>(
- initial_period)
+ initialPeriod)
.count(),
vsyncTimestampHistorySize, minimumSamplesForPrediction,
discardOutlierPercent);
@@ -90,7 +90,7 @@
static constexpr size_t pendingFenceLimit = 20;
return std::make_unique<scheduler::VSyncReactor>(std::make_unique<scheduler::SystemClock>(),
std::move(dispatch), std::move(tracker),
- pendingFenceLimit);
+ pendingFenceLimit, supportKernelTimer);
} else {
return std::make_unique<impl::DispSync>("SchedulerDispSync",
sysprop::running_without_sync_framework(true));
@@ -101,9 +101,9 @@
const scheduler::RefreshRateConfigs& refreshRateConfig,
ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
bool useContentDetection)
- : mPrimaryDispSync(createDispSync()),
+ : mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
+ mPrimaryDispSync(createDispSync(mSupportKernelTimer)),
mEventControlThread(new impl::EventControlThread(std::move(function))),
- mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
mSchedulerCallback(schedulerCallback),
mRefreshRateConfigs(refreshRateConfig),
mUseContentDetection(useContentDetection),
@@ -111,7 +111,7 @@
using namespace sysprop;
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(refreshRateConfig);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}
@@ -151,9 +151,9 @@
const scheduler::RefreshRateConfigs& configs,
ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
bool useContentDetection)
- : mPrimaryDispSync(std::move(primaryDispSync)),
+ : mSupportKernelTimer(false),
+ mPrimaryDispSync(std::move(primaryDispSync)),
mEventControlThread(std::move(eventControlThread)),
- mSupportKernelTimer(false),
mSchedulerCallback(schedulerCallback),
mRefreshRateConfigs(configs),
mUseContentDetection(useContentDetection),
@@ -228,8 +228,36 @@
mConnections[handle].thread->onScreenReleased();
}
-void Scheduler::onConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
- HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
+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};
+ onNonPrimaryDisplayConfigChanged(handle, displayId, configId, vsyncPeriod);
+}
+
+void Scheduler::dispatchCachedReportedConfig() {
+ const auto configId = *mFeatures.configId;
+ const auto vsyncPeriod =
+ mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getVsyncPeriod();
+
+ // If there is no change from cached config, there is no need to dispatch an event
+ if (configId == mFeatures.cachedConfigChangedParams->configId &&
+ vsyncPeriod == mFeatures.cachedConfigChangedParams->vsyncPeriod) {
+ return;
+ }
+
+ mFeatures.cachedConfigChangedParams->configId = configId;
+ mFeatures.cachedConfigChangedParams->vsyncPeriod = vsyncPeriod;
+ onNonPrimaryDisplayConfigChanged(mFeatures.cachedConfigChangedParams->handle,
+ mFeatures.cachedConfigChangedParams->displayId,
+ mFeatures.cachedConfigChangedParams->configId,
+ mFeatures.cachedConfigChangedParams->vsyncPeriod);
+}
+
+void Scheduler::onNonPrimaryDisplayConfigChanged(ConnectionHandle handle,
+ PhysicalDisplayId displayId,
+ HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
RETURN_IF_INVALID_HANDLE(handle);
mConnections[handle].thread->onConfigChanged(displayId, configId, vsyncPeriod);
}
@@ -385,46 +413,48 @@
void Scheduler::registerLayer(Layer* layer) {
if (!mLayerHistory) return;
- // If the content detection feature is off, all layers are registered at NoVote. We still
- // keep the layer history, since we use it for other features (like Frame Rate API), so layers
- // still need to be registered.
- if (!mUseContentDetection) {
- mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
- mRefreshRateConfigs.getMaxRefreshRate().getFps(),
+ 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);
- return;
- }
+ } else if (!mUseContentDetection) {
+ // 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);
+ } else if (!mUseContentDetectionV2) {
+ // In V1 of content detection, all layers are registered as Heuristic (unless it's
+ // wallpaper).
+ const auto highFps =
+ layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER ? minFps : maxFps;
- // In V1 of content detection, all layers are registered as Heuristic (unless it's wallpaper).
- if (!mUseContentDetectionV2) {
- const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().getFps();
- const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
- ? lowFps
- : mRefreshRateConfigs.getMaxRefreshRate().getFps();
-
- mLayerHistory->registerLayer(layer, lowFps, highFps,
+ mLayerHistory->registerLayer(layer, minFps, highFps,
scheduler::LayerHistory::LayerVoteType::Heuristic);
} else {
if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
// Running Wallpaper at Min is considered as part of content detection.
- mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
- mRefreshRateConfigs.getMaxRefreshRate().getFps(),
+ mLayerHistory->registerLayer(layer, minFps, maxFps,
scheduler::LayerHistory::LayerVoteType::Min);
- } else if (layer->getWindowType() == InputWindowInfo::TYPE_STATUS_BAR) {
- mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
- mRefreshRateConfigs.getMaxRefreshRate().getFps(),
- scheduler::LayerHistory::LayerVoteType::NoVote);
} else {
- mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().getFps(),
- mRefreshRateConfigs.getMaxRefreshRate().getFps(),
+ mLayerHistory->registerLayer(layer, minFps, maxFps,
scheduler::LayerHistory::LayerVoteType::Heuristic);
}
}
}
-void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime) {
+void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime,
+ LayerHistory::LayerUpdateType updateType) {
if (mLayerHistory) {
- mLayerHistory->record(layer, presentTime, systemTime());
+ mLayerHistory->record(layer, presentTime, systemTime(), updateType);
+ }
+}
+
+void Scheduler::setConfigChangePending(bool pending) {
+ if (mLayerHistory) {
+ mLayerHistory->setConfigChangePending(pending);
}
}
@@ -444,13 +474,21 @@
mFeatures.contentDetectionV1 =
!summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
- newConfigId = calculateRefreshRateConfigIndexType();
+ scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
+ newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
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;
}
mFeatures.configId = newConfigId;
auto& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
- mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
+ mSchedulerCallback.changeRefreshRate(newRefreshRate,
+ consideredSignals.idle ? ConfigEvent::None
+ : ConfigEvent::Changed);
}
}
@@ -520,19 +558,20 @@
}
void Scheduler::idleTimerCallback(TimerState state) {
- handleTimerStateChanged(&mFeatures.idleTimer, state, false /* eventOnContentDetection */);
+ handleTimerStateChanged(&mFeatures.idleTimer, state);
ATRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
}
void Scheduler::touchTimerCallback(TimerState state) {
const TouchState touch = state == TimerState::Reset ? TouchState::Active : TouchState::Inactive;
- handleTimerStateChanged(&mFeatures.touch, touch, true /* eventOnContentDetection */);
+ if (handleTimerStateChanged(&mFeatures.touch, touch)) {
+ mLayerHistory->clear();
+ }
ATRACE_INT("TouchState", static_cast<int>(touch));
}
void Scheduler::displayPowerTimerCallback(TimerState state) {
- handleTimerStateChanged(&mFeatures.displayPowerTimer, state,
- true /* eventOnContentDetection */);
+ handleTimerStateChanged(&mFeatures.displayPowerTimer, state);
ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
}
@@ -549,30 +588,37 @@
}
template <class T>
-void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
- ConfigEvent event = ConfigEvent::None;
+bool Scheduler::handleTimerStateChanged(T* currentState, T newState) {
HwcConfigIndexType newConfigId;
+ scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
{
std::lock_guard<std::mutex> lock(mFeatureStateLock);
if (*currentState == newState) {
- return;
+ return false;
}
*currentState = newState;
- newConfigId = calculateRefreshRateConfigIndexType();
+ newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
if (mFeatures.configId == newConfigId) {
- return;
+ // 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;
}
mFeatures.configId = newConfigId;
- if (eventOnContentDetection && !mFeatures.contentRequirements.empty()) {
- event = ConfigEvent::Changed;
- }
}
const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
- mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
+ mSchedulerCallback.changeRefreshRate(newRefreshRate,
+ consideredSignals.idle ? ConfigEvent::None
+ : ConfigEvent::Changed);
+ return consideredSignals.touch;
}
-HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType() {
+HwcConfigIndexType Scheduler::calculateRefreshRateConfigIndexType(
+ scheduler::RefreshRateConfigs::GlobalSignals* consideredSignals) {
ATRACE_CALL();
+ if (consideredSignals) *consideredSignals = {};
// If Display Power is not in normal operation we want to be in performance mode. When coming
// back to normal mode, a grace period is given with DisplayPowerTimer.
@@ -593,6 +639,7 @@
// If timer has expired as it means there is no new content on the screen.
if (idle) {
+ if (consideredSignals) consideredSignals->idle = true;
return mRefreshRateConfigs.getMinRefreshRateByPolicy().getConfigId();
}
@@ -607,18 +654,10 @@
.getConfigId();
}
- bool touchConsidered;
- const auto& ret = mRefreshRateConfigs
- .getBestRefreshRate(mFeatures.contentRequirements, touchActive, idle,
- &touchConsidered)
- .getConfigId();
- if (touchConsidered) {
- // Clear layer history if refresh rate was selected based on touch to allow
- // the hueristic to pick up with the new rate.
- mLayerHistory->clear();
- }
-
- return ret;
+ return mRefreshRateConfigs
+ .getBestRefreshRate(mFeatures.contentRequirements, {.touch = touchActive, .idle = idle},
+ consideredSignals)
+ .getConfigId();
}
std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 4a0280f..9e24f909 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -39,6 +39,7 @@
namespace android {
using namespace std::chrono_literals;
+using scheduler::LayerHistory;
class DispSync;
class FenceTime;
@@ -80,9 +81,11 @@
sp<EventThreadConnection> getEventConnection(ConnectionHandle);
void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
- void onConfigChanged(ConnectionHandle, PhysicalDisplayId, HwcConfigIndexType configId,
- nsecs_t vsyncPeriod);
-
+ void onPrimaryDisplayConfigChanged(ConnectionHandle, PhysicalDisplayId,
+ HwcConfigIndexType configId, nsecs_t vsyncPeriod)
+ EXCLUDES(mFeatureStateLock);
+ void onNonPrimaryDisplayConfigChanged(ConnectionHandle, PhysicalDisplayId,
+ HwcConfigIndexType configId, nsecs_t vsyncPeriod);
void onScreenAcquired(ConnectionHandle);
void onScreenReleased(ConnectionHandle);
@@ -118,7 +121,8 @@
// Layers are registered on creation, and unregistered when the weak reference expires.
void registerLayer(Layer*);
- void recordLayerHistory(Layer*, nsecs_t presentTime);
+ void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType);
+ void setConfigChangePending(bool pending);
// Detects content using layer history, and selects a matching refresh rate.
void chooseRefreshRateForContent();
@@ -177,14 +181,18 @@
// handles various timer features to change the refresh rate.
template <class T>
- void handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection);
+ bool handleTimerStateChanged(T* currentState, T newState);
void setVsyncPeriod(nsecs_t period);
// This function checks whether individual features that are affecting the refresh rate
// selection were initialized, prioritizes them, and calculates the HwcConfigIndexType
// for the suggested refresh rate.
- HwcConfigIndexType calculateRefreshRateConfigIndexType() REQUIRES(mFeatureStateLock);
+ HwcConfigIndexType calculateRefreshRateConfigIndexType(
+ scheduler::RefreshRateConfigs::GlobalSignals* consideredSignals = nullptr)
+ REQUIRES(mFeatureStateLock);
+
+ void dispatchCachedReportedConfig() REQUIRES(mFeatureStateLock);
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
struct Connection {
@@ -205,14 +213,14 @@
std::atomic<nsecs_t> mLastResyncTime = 0;
+ // Whether to use idle timer callbacks that support the kernel timer.
+ const bool mSupportKernelTimer;
+
std::unique_ptr<DispSync> mPrimaryDispSync;
std::unique_ptr<EventControlThread> mEventControlThread;
// Used to choose refresh rate if content detection is enabled.
- std::unique_ptr<scheduler::LayerHistory> mLayerHistory;
-
- // Whether to use idle timer callbacks that support the kernel timer.
- const bool mSupportKernelTimer;
+ std::unique_ptr<LayerHistory> mLayerHistory;
// Timer that records time between requests for next vsync.
std::optional<scheduler::OneShotTimer> mIdleTimer;
@@ -234,9 +242,19 @@
TimerState displayPowerTimer = TimerState::Expired;
std::optional<HwcConfigIndexType> configId;
- scheduler::LayerHistory::Summary contentRequirements;
+ LayerHistory::Summary contentRequirements;
bool isDisplayPowerStateNormal = true;
+
+ // Used to cache the last parameters of onPrimaryDisplayConfigChanged
+ struct ConfigChangedParams {
+ ConnectionHandle handle;
+ PhysicalDisplayId displayId;
+ HwcConfigIndexType configId;
+ nsecs_t vsyncPeriod;
+ };
+
+ std::optional<ConfigChangedParams> cachedConfigChangedParams;
} mFeatures GUARDED_BY(mFeatureStateLock);
const scheduler::RefreshRateConfigs& mRefreshRateConfigs;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index cd15617..abeacfe 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -87,10 +87,26 @@
return ScheduleResult::Scheduled;
}
+void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(nsecs_t workDuration,
+ nsecs_t earliestVsync) {
+ mWorkloadUpdateInfo = {.earliestVsync = earliestVsync, .duration = workDuration};
+}
+
+bool VSyncDispatchTimerQueueEntry::hasPendingWorkloadUpdate() const {
+ return mWorkloadUpdateInfo.has_value();
+}
+
void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
- if (!mArmedInfo) {
+ if (!mArmedInfo && !mWorkloadUpdateInfo) {
return;
}
+
+ if (mWorkloadUpdateInfo) {
+ mEarliestVsync = mWorkloadUpdateInfo->earliestVsync;
+ mWorkDuration = mWorkloadUpdateInfo->duration;
+ mWorkloadUpdateInfo.reset();
+ }
+
auto const nextVsyncTime =
tracker.nextAnticipatedVSyncTimeFrom(std::max(mEarliestVsync, now + mWorkDuration));
mArmedInfo = {nextVsyncTime - mWorkDuration, nextVsyncTime};
@@ -192,7 +208,7 @@
std::optional<std::string_view> nextWakeupName;
for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
auto& callback = it->second;
- if (!callback->wakeupTime()) {
+ if (!callback->wakeupTime() && !callback->hasPendingWorkloadUpdate()) {
continue;
}
@@ -291,6 +307,15 @@
}
auto& callback = it->second;
auto const now = mTimeKeeper->now();
+
+ /* If the timer thread will run soon, we'll apply this work update via the callback
+ * timer recalculation to avoid cancelling a callback that is about to fire. */
+ auto const rearmImminent = now > mIntendedWakeupTime;
+ if (CC_UNLIKELY(rearmImminent)) {
+ callback->addPendingWorkloadUpdate(workDuration, earliestVsync);
+ return ScheduleResult::Scheduled;
+ }
+
result = callback->schedule(workDuration, earliestVsync, mTracker, now);
if (result == ScheduleResult::CannotSchedule) {
return result;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 26a8ec0..957c0d1 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -64,6 +64,13 @@
// This moves the state from armed->running.
// Store the timestamp that this was intended for as the last called timestamp.
nsecs_t executing();
+
+ // Adds a pending upload of the earliestVSync and workDuration that will be applied on the next
+ // call to update()
+ void addPendingWorkloadUpdate(nsecs_t workDuration, nsecs_t earliestVsync);
+
+ // Checks if there is a pending update to the workload, returning true if so.
+ bool hasPendingWorkloadUpdate() const;
// End: functions that are not threadsafe.
// Invoke the callback with the two given timestamps, moving the state from running->disarmed.
@@ -88,6 +95,12 @@
std::optional<ArmingInfo> mArmedInfo;
std::optional<nsecs_t> mLastDispatchTime;
+ struct WorkloadUpdateInfo {
+ nsecs_t duration;
+ nsecs_t earliestVsync;
+ };
+ std::optional<WorkloadUpdateInfo> mWorkloadUpdateInfo;
+
mutable std::mutex mRunningMutex;
std::condition_variable mCv;
bool mRunning GUARDED_BY(mRunningMutex) = false;
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.cpp b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
index 40a992c..510dc2d 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
@@ -25,6 +25,7 @@
#include <cutils/properties.h>
#include <utils/Trace.h>
+#include <chrono>
#include <cinttypes>
#include <mutex>
@@ -52,6 +53,7 @@
void VSyncModulator::setTransactionStart(Scheduler::TransactionStart transactionStart) {
if (transactionStart == Scheduler::TransactionStart::EARLY) {
mRemainingEarlyFrameCount = MIN_EARLY_FRAME_COUNT_TRANSACTION;
+ mEarlyTxnStartTime = std::chrono::steady_clock::now();
}
// An early transaction stays an early transaction.
@@ -64,6 +66,7 @@
}
void VSyncModulator::onTransactionHandled() {
+ mTxnAppliedTime = std::chrono::steady_clock::now();
if (mTransactionStart == Scheduler::TransactionStart::NORMAL) return;
mTransactionStart = Scheduler::TransactionStart::NORMAL;
updateOffsets();
@@ -87,9 +90,16 @@
void VSyncModulator::onRefreshed(bool usedRenderEngine) {
bool updateOffsetsNeeded = false;
- if (mRemainingEarlyFrameCount > 0) {
- mRemainingEarlyFrameCount--;
- updateOffsetsNeeded = true;
+
+ // Apply a 1ms margin to account for potential data races
+ // This might make us stay in early offsets for one
+ // additional frame but it's better to be conservative here.
+ static const constexpr std::chrono::nanoseconds kMargin = 1ms;
+ if ((mEarlyTxnStartTime.load() + kMargin) < mTxnAppliedTime.load()) {
+ if (mRemainingEarlyFrameCount > 0) {
+ mRemainingEarlyFrameCount--;
+ updateOffsetsNeeded = true;
+ }
}
if (usedRenderEngine) {
mRemainingRenderEngineUsageCount = MIN_EARLY_GL_FRAME_COUNT_TRANSACTION;
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.h b/services/surfaceflinger/Scheduler/VSyncModulator.h
index 704a5d5..d777ef9 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.h
@@ -16,6 +16,7 @@
#pragma once
+#include <chrono>
#include <mutex>
#include "Scheduler.h"
@@ -110,6 +111,8 @@
std::atomic<bool> mRefreshRateChangePending = false;
std::atomic<int> mRemainingEarlyFrameCount = 0;
std::atomic<int> mRemainingRenderEngineUsageCount = 0;
+ std::atomic<std::chrono::steady_clock::time_point> mEarlyTxnStartTime = {};
+ std::atomic<std::chrono::steady_clock::time_point> mTxnAppliedTime = {};
bool mTraceDetailedInfo = false;
};
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 5f0c9ce..c743de0 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -56,14 +56,16 @@
};
VSyncReactor::VSyncReactor(std::unique_ptr<Clock> clock, std::unique_ptr<VSyncDispatch> dispatch,
- std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit)
+ std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit,
+ bool supportKernelIdleTimer)
: mClock(std::move(clock)),
mTracker(std::move(tracker)),
mDispatch(std::move(dispatch)),
mPendingLimit(pendingFenceLimit),
mPredictedVsyncTracer(property_get_bool("debug.sf.show_predicted_vsync", false)
? std::make_unique<PredictedVsyncTracer>(*mDispatch)
- : nullptr) {}
+ : nullptr),
+ mSupportKernelIdleTimer(supportKernelIdleTimer) {}
VSyncReactor::~VSyncReactor() = default;
@@ -249,7 +251,8 @@
ATRACE_INT64("VSR-setPeriod", period);
std::lock_guard lk(mMutex);
mLastHwVsync.reset();
- if (period == getPeriod()) {
+
+ if (!mSupportKernelIdleTimer && period == getPeriod()) {
endPeriodTransition();
} else {
startPeriodTransition(period);
@@ -275,6 +278,13 @@
return false;
}
+ const bool periodIsChanging =
+ mPeriodTransitioningTo && (*mPeriodTransitioningTo != getPeriod());
+ if (mSupportKernelIdleTimer && !periodIsChanging) {
+ // Clear out the Composer-provided period and use the allowance logic below
+ HwcVsyncPeriod = {};
+ }
+
auto const period = mPeriodTransitioningTo ? *mPeriodTransitioningTo : getPeriod();
static constexpr int allowancePercent = 10;
static constexpr std::ratio<allowancePercent, 100> allowancePercentRatio;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 31ddf5a..265d89c 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -36,7 +36,8 @@
class VSyncReactor : public android::DispSync {
public:
VSyncReactor(std::unique_ptr<Clock> clock, std::unique_ptr<VSyncDispatch> dispatch,
- std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit);
+ std::unique_ptr<VSyncTracker> tracker, size_t pendingFenceLimit,
+ bool supportKernelIdleTimer);
~VSyncReactor();
bool addPresentFence(const std::shared_ptr<FenceTime>& fence) final;
@@ -89,6 +90,7 @@
GUARDED_BY(mMutex);
const std::unique_ptr<PredictedVsyncTracer> mPredictedVsyncTracer;
+ const bool mSupportKernelIdleTimer = false;
};
class SystemClock : public Clock {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4549ab3..4492f94 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -115,6 +115,7 @@
#include "Scheduler/DispSyncSource.h"
#include "Scheduler/EventControlThread.h"
#include "Scheduler/EventThread.h"
+#include "Scheduler/LayerHistory.h"
#include "Scheduler/MessageQueue.h"
#include "Scheduler/PhaseOffsets.h"
#include "Scheduler/Scheduler.h"
@@ -125,6 +126,19 @@
#include "android-base/parseint.h"
#include "android-base/stringprintf.h"
+#define MAIN_THREAD ACQUIRE(mStateLock) RELEASE(mStateLock)
+
+#define ON_MAIN_THREAD(expr) \
+ [&] { \
+ LOG_FATAL_IF(std::this_thread::get_id() != mMainThreadId); \
+ UnnecessaryLock lock(mStateLock); \
+ return (expr); \
+ }()
+
+#undef NO_THREAD_SAFETY_ANALYSIS
+#define NO_THREAD_SAFETY_ANALYSIS \
+ _Pragma("GCC error \"Prefer MAIN_THREAD macros or {Conditional,Timed,Unnecessary}Lock.\"")
+
namespace android {
using namespace std::string_literals;
@@ -173,12 +187,12 @@
#pragma clang diagnostic pop
template <typename Mutex>
-struct ConditionalLockGuard {
- ConditionalLockGuard(Mutex& mutex, bool lock) : mutex(mutex), lock(lock) {
+struct SCOPED_CAPABILITY ConditionalLockGuard {
+ ConditionalLockGuard(Mutex& mutex, bool lock) ACQUIRE(mutex) : mutex(mutex), lock(lock) {
if (lock) mutex.lock();
}
- ~ConditionalLockGuard() {
+ ~ConditionalLockGuard() RELEASE() {
if (lock) mutex.unlock();
}
@@ -188,6 +202,27 @@
using ConditionalLock = ConditionalLockGuard<Mutex>;
+struct SCOPED_CAPABILITY TimedLock {
+ TimedLock(Mutex& mutex, nsecs_t timeout, const char* whence) ACQUIRE(mutex)
+ : mutex(mutex), status(mutex.timedLock(timeout)) {
+ ALOGE_IF(!locked(), "%s timed out locking: %s (%d)", whence, strerror(-status), status);
+ }
+
+ ~TimedLock() RELEASE() {
+ if (locked()) mutex.unlock();
+ }
+
+ bool locked() const { return status == NO_ERROR; }
+
+ Mutex& mutex;
+ const status_t status;
+};
+
+struct SCOPED_CAPABILITY UnnecessaryLock {
+ explicit UnnecessaryLock(Mutex& mutex) ACQUIRE(mutex) {}
+ ~UnnecessaryLock() RELEASE() {}
+};
+
// TODO(b/141333600): Consolidate with HWC2::Display::Config::Builder::getDefaultDensity.
constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV;
@@ -223,7 +258,7 @@
const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER");
const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
const String16 sDump("android.permission.DUMP");
-const char* KERNEL_IDLE_TIMER_PROP = "vendor.display.enable_kernel_idle_timer";
+const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
// ---------------------------------------------------------------------------
int64_t SurfaceFlinger::dispSyncPresentTimeOffset;
@@ -980,6 +1015,7 @@
mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+ mScheduler->setConfigChangePending(true);
}
if (mRefreshRateOverlay) {
@@ -995,7 +1031,7 @@
}
auto future = schedule([=]() -> status_t {
- const auto display = getDisplayDeviceLocked(displayToken);
+ const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
if (!display) {
ALOGE("Attempt to set allowed display configs for invalid display token %p",
displayToken.get());
@@ -1045,8 +1081,8 @@
const nsecs_t vsyncPeriod =
mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId)
.getVsyncPeriod();
- mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
- mUpcomingActiveConfig.configId, vsyncPeriod);
+ mScheduler->onPrimaryDisplayConfigChanged(mAppConnectionHandle, display->getId()->value,
+ mUpcomingActiveConfig.configId, vsyncPeriod);
}
}
@@ -1060,6 +1096,7 @@
mScheduler->resyncToHardwareVsync(true, refreshRate.getVsyncPeriod());
mPhaseConfiguration->setRefreshRateFps(refreshRate.getFps());
mVSyncModulator->setPhaseOffsets(mPhaseConfiguration->getCurrentOffsets());
+ mScheduler->setConfigChangePending(false);
}
void SurfaceFlinger::performSetActiveConfig() {
@@ -1180,7 +1217,7 @@
}
status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
- schedule([=] {
+ schedule([=]() MAIN_THREAD {
Vector<ColorMode> modes;
getDisplayColorModes(displayToken, &modes);
bool exists = std::find(std::begin(modes), std::end(modes), mode) != std::end(modes);
@@ -1226,7 +1263,7 @@
}
void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
- static_cast<void>(schedule([=] {
+ static_cast<void>(schedule([=]() MAIN_THREAD {
if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
getHwComposer().setAutoLowLatencyMode(*displayId, on);
} else {
@@ -1257,7 +1294,7 @@
}
void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
- static_cast<void>(schedule([=] {
+ static_cast<void>(schedule([=]() MAIN_THREAD {
if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
getHwComposer().setContentType(*displayId, type);
@@ -1323,7 +1360,7 @@
status_t SurfaceFlinger::setDisplayContentSamplingEnabled(const sp<IBinder>& displayToken,
bool enable, uint8_t componentMask,
uint64_t maxFrames) {
- return schedule([=]() -> status_t {
+ return schedule([=]() MAIN_THREAD -> status_t {
if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
componentMask,
@@ -1392,22 +1429,14 @@
return mScheduler->injectVSync(when, calculateExpectedPresentTime(when)) ? NO_ERROR : BAD_VALUE;
}
-status_t SurfaceFlinger::getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) const
- NO_THREAD_SAFETY_ANALYSIS {
- // Try to acquire a lock for 1s, fail gracefully
- const status_t err = mStateLock.timedLock(s2ns(1));
- const bool locked = (err == NO_ERROR);
- if (!locked) {
- ALOGE("LayerDebugInfo: SurfaceFlinger unresponsive (%s [%d]) - exit", strerror(-err), err);
- return TIMED_OUT;
- }
-
- const auto display = getDefaultDisplayDeviceLocked();
+status_t SurfaceFlinger::getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) {
outLayers->clear();
- mCurrentState.traverseInZOrder(
- [&](Layer* layer) { outLayers->push_back(layer->getLayerDebugInfo(display.get())); });
-
- mStateLock.unlock();
+ schedule([=] {
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+ mDrawingState.traverseInZOrder([&](Layer* layer) {
+ outLayers->push_back(layer->getLayerDebugInfo(display.get()));
+ });
+ }).wait();
return NO_ERROR;
}
@@ -1464,7 +1493,7 @@
return BAD_VALUE;
}
- return promise::chain(schedule([=] {
+ return promise::chain(schedule([=]() MAIN_THREAD {
if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
return getHwComposer().setDisplayBrightness(*displayId, brightness);
} else {
@@ -1577,7 +1606,7 @@
}
void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hal::HWDisplayId hwcDisplayId,
- hal::Connection connection) NO_THREAD_SAFETY_ANALYSIS {
+ hal::Connection connection) {
ALOGV("%s(%d, %" PRIu64 ", %s)", __FUNCTION__, sequenceId, hwcDisplayId,
connection == hal::Connection::CONNECTED ? "connected" : "disconnected");
@@ -1630,8 +1659,7 @@
// Enable / Disable HWVsync from the main thread to avoid race conditions with
// display power state.
- static_cast<void>(
- schedule([=]() NO_THREAD_SAFETY_ANALYSIS { setPrimaryVsyncEnabledInternal(enabled); }));
+ static_cast<void>(schedule([=]() MAIN_THREAD { setPrimaryVsyncEnabledInternal(enabled); }));
}
void SurfaceFlinger::setPrimaryVsyncEnabledInternal(bool enabled) {
@@ -1647,7 +1675,6 @@
}
}
-// Note: it is assumed the caller holds |mStateLock| when this is called
void SurfaceFlinger::resetDisplayState() {
mScheduler->disableHardwareVsync(true);
// Clear the drawing state so that the logic inside of
@@ -1740,7 +1767,7 @@
setTransactionFlags(eDisplayTransactionNeeded);
}
-sp<Fence> SurfaceFlinger::previousFrameFence() NO_THREAD_SAFETY_ANALYSIS {
+sp<Fence> SurfaceFlinger::previousFrameFence() {
// We are storing the last 2 present fences. If sf's phase offset is to be
// woken up before the actual vsync but targeting the next vsync, we need to check
// fence N-2
@@ -1748,7 +1775,7 @@
: mPreviousPresentFences[1];
}
-bool SurfaceFlinger::previousFramePending(int graceTimeMs) NO_THREAD_SAFETY_ANALYSIS {
+bool SurfaceFlinger::previousFramePending(int graceTimeMs) {
ATRACE_CALL();
const sp<Fence>& fence = previousFrameFence();
@@ -1762,7 +1789,7 @@
return status == -ETIME;
}
-nsecs_t SurfaceFlinger::previousFramePresentTime() NO_THREAD_SAFETY_ANALYSIS {
+nsecs_t SurfaceFlinger::previousFramePresentTime() {
const sp<Fence>& fence = previousFrameFence();
if (fence == Fence::NO_FENCE) {
@@ -1780,8 +1807,7 @@
return mVSyncModulator->getOffsets().sf > 0 ? presentTime : presentTime + stats.vsyncPeriod;
}
-void SurfaceFlinger::onMessageReceived(int32_t what,
- nsecs_t expectedVSyncTime) NO_THREAD_SAFETY_ANALYSIS {
+void SurfaceFlinger::onMessageReceived(int32_t what, nsecs_t expectedVSyncTime) {
ATRACE_CALL();
switch (what) {
case MessageQueue::INVALIDATE: {
@@ -1795,7 +1821,7 @@
}
}
-void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) NO_THREAD_SAFETY_ANALYSIS {
+void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) {
ATRACE_CALL();
const nsecs_t frameStart = systemTime();
@@ -1868,7 +1894,7 @@
// We received the present fence from the HWC, so we assume it successfully updated
// the config, hence we update SF.
mSetActiveConfigPending = false;
- setActiveConfigInternal();
+ ON_MAIN_THREAD(setActiveConfigInternal());
}
if (framePending && mPropagateBackpressure) {
@@ -1885,25 +1911,19 @@
// ...but if it's larger than 1s then we missed the trace cutoff.
static constexpr nsecs_t kMaxJankyDuration =
std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+ nsecs_t jankDurationToUpload = -1;
// If we're in a user build then don't push any atoms
if (!mIsUserBuild && mMissedFrameJankCount > 0) {
- const auto displayDevice = getDefaultDisplayDeviceLocked();
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
// Only report jank when the display is on, as displays in DOZE
// power mode may operate at a different frame rate than is
// reported in their config, which causes noticeable (but less
// severe) jank.
- if (displayDevice && displayDevice->getPowerMode() == hal::PowerMode::ON) {
+ if (display && display->getPowerMode() == hal::PowerMode::ON) {
const nsecs_t currentTime = systemTime();
const nsecs_t jankDuration = currentTime - mMissedFrameJankStart;
if (jankDuration > kMinJankyDuration && jankDuration < kMaxJankyDuration) {
- ATRACE_NAME("Jank detected");
- ALOGD("Detected janky event. Missed frames: %d", mMissedFrameJankCount);
- const int32_t jankyDurationMillis = jankDuration / (1000 * 1000);
- {
- ATRACE_NAME("Pushing to statsd");
- android::util::stats_write(android::util::DISPLAY_JANK_REPORTED,
- jankyDurationMillis, mMissedFrameJankCount);
- }
+ jankDurationToUpload = jankDuration;
}
// We either reported a jank event or we missed the trace
@@ -1949,13 +1969,14 @@
mScheduler->chooseRefreshRateForContent();
}
- performSetActiveConfig();
+ ON_MAIN_THREAD(performSetActiveConfig());
updateCursorAsync();
updateInputFlinger();
refreshNeeded |= mRepaintEverything;
if (refreshNeeded && CC_LIKELY(mBootStage != BootStage::BOOTLOADER)) {
+ mLastJankDuration = jankDurationToUpload;
// Signal a refresh if a transaction modified the window state,
// a new buffer was latched, or if HWC has requested a full
// repaint
@@ -1976,8 +1997,9 @@
bool flushedATransaction = flushTransactionQueues();
- bool runHandleTransaction = transactionFlags &&
- ((transactionFlags != eTransactionFlushNeeded) || flushedATransaction);
+ bool runHandleTransaction =
+ (transactionFlags && (transactionFlags != eTransactionFlushNeeded)) ||
+ flushedATransaction;
if (runHandleTransaction) {
handleTransaction(eTransactionMask);
@@ -1998,8 +2020,9 @@
mRefreshPending = false;
compositionengine::CompositionRefreshArgs refreshArgs;
- refreshArgs.outputs.reserve(mDisplays.size());
- for (const auto& [_, display] : mDisplays) {
+ const auto& displays = ON_MAIN_THREAD(mDisplays);
+ refreshArgs.outputs.reserve(displays.size());
+ for (const auto& [_, display] : displays) {
refreshArgs.outputs.push_back(display->getCompositionDisplay());
}
mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) {
@@ -2054,21 +2077,18 @@
const bool prevFrameHadDeviceComposition = mHadDeviceComposition;
- mHadClientComposition =
- std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
- auto& displayDevice = tokenDisplayPair.second;
- return displayDevice->getCompositionDisplay()->getState().usesClientComposition &&
- !displayDevice->getCompositionDisplay()->getState().reusedClientComposition;
- });
- mHadDeviceComposition =
- std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
- auto& displayDevice = tokenDisplayPair.second;
- return displayDevice->getCompositionDisplay()->getState().usesDeviceComposition;
- });
+ mHadClientComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
+ const auto& state = pair.second->getCompositionDisplay()->getState();
+ return state.usesClientComposition && !state.reusedClientComposition;
+ });
+ mHadDeviceComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
+ const auto& state = pair.second->getCompositionDisplay()->getState();
+ return state.usesDeviceComposition;
+ });
mReusedClientComposition =
- std::any_of(mDisplays.cbegin(), mDisplays.cend(), [](const auto& tokenDisplayPair) {
- auto& displayDevice = tokenDisplayPair.second;
- return displayDevice->getCompositionDisplay()->getState().reusedClientComposition;
+ std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
+ const auto& state = pair.second->getCompositionDisplay()->getState();
+ return state.reusedClientComposition;
});
// Only report a strategy change if we move in and out of composition with hw overlays
@@ -2174,14 +2194,14 @@
for (auto& layer : mLayersWithQueuedFrames) {
layer->releasePendingBuffer(dequeueReadyTime);
}
- // |mStateLock| not needed as we are on the main thread
- const auto displayDevice = getDefaultDisplayDeviceLocked();
+
+ const auto* display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked()).get();
getBE().mGlCompositionDoneTimeline.updateSignalTimes();
std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
- if (displayDevice && displayDevice->getCompositionDisplay()->getState().usesClientComposition) {
+ if (display && display->getCompositionDisplay()->getState().usesClientComposition) {
glCompositionDoneFenceTime =
- std::make_shared<FenceTime>(displayDevice->getCompositionDisplay()
+ std::make_shared<FenceTime>(display->getCompositionDisplay()
->getRenderSurface()
->getClientTargetAcquireFence());
getBE().mGlCompositionDoneTimeline.push(glCompositionDoneFenceTime);
@@ -2191,9 +2211,8 @@
getBE().mDisplayTimeline.updateSignalTimes();
mPreviousPresentFences[1] = mPreviousPresentFences[0];
- mPreviousPresentFences[0] = displayDevice
- ? getHwComposer().getPresentFence(*displayDevice->getId())
- : Fence::NO_FENCE;
+ mPreviousPresentFences[0] =
+ display ? getHwComposer().getPresentFence(*display->getId()) : Fence::NO_FENCE;
auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFences[0]);
getBE().mDisplayTimeline.push(presentFenceTime);
@@ -2212,9 +2231,8 @@
}
mDrawingState.traverse([&](Layer* layer) {
- const bool frameLatched =
- layer->onPostComposition(displayDevice.get(), glCompositionDoneFenceTime,
- presentFenceTime, compositorTiming);
+ const bool frameLatched = layer->onPostComposition(display, glCompositionDoneFenceTime,
+ presentFenceTime, compositorTiming);
if (frameLatched) {
recordBufferingStats(layer->getName(), layer->getOccupancyHistory(false));
}
@@ -2223,14 +2241,15 @@
mTransactionCompletedThread.addPresentFence(mPreviousPresentFences[0]);
mTransactionCompletedThread.sendCallbacks();
- if (displayDevice && displayDevice->isPrimary() &&
- displayDevice->getPowerMode() == hal::PowerMode::ON && presentFenceTime->isValid()) {
+ if (display && display->isPrimary() && display->getPowerMode() == hal::PowerMode::ON &&
+ presentFenceTime->isValid()) {
mScheduler->addPresentFence(presentFenceTime);
}
+ const bool isDisplayConnected = display && getHwComposer().isConnected(*display->getId());
+
if (!hasSyncFramework) {
- if (displayDevice && getHwComposer().isConnected(*displayDevice->getId()) &&
- displayDevice->isPoweredOn()) {
+ if (isDisplayConnected && display->isPoweredOn()) {
mScheduler->enableHardwareVsync();
}
}
@@ -2241,11 +2260,10 @@
if (presentFenceTime->isValid()) {
mAnimFrameTracker.setActualPresentFence(
std::move(presentFenceTime));
- } else if (displayDevice && getHwComposer().isConnected(*displayDevice->getId())) {
+ } else if (isDisplayConnected) {
// The HWC doesn't support present fences, so use the refresh
// timestamp instead.
- const nsecs_t presentTime =
- getHwComposer().getRefreshTimestamp(*displayDevice->getId());
+ const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(*display->getId());
mAnimFrameTracker.setActualPresentTime(presentTime);
}
mAnimFrameTracker.advanceFrame();
@@ -2266,8 +2284,15 @@
const size_t appConnections = mScheduler->getEventThreadConnectionCount(mAppConnectionHandle);
mTimeStats->recordDisplayEventConnectionCount(sfConnections + appConnections);
- if (displayDevice && getHwComposer().isConnected(*displayDevice->getId()) &&
- !displayDevice->isPoweredOn()) {
+ if (mLastJankDuration > 0) {
+ ATRACE_NAME("Jank detected");
+ const int32_t jankyDurationMillis = mLastJankDuration / (1000 * 1000);
+ android::util::stats_write(android::util::DISPLAY_JANK_REPORTED, jankyDurationMillis,
+ mMissedFrameJankCount);
+ mLastJankDuration = -1;
+ }
+
+ if (isDisplayConnected && !display->isPoweredOn()) {
return;
}
@@ -2323,7 +2348,7 @@
}
void SurfaceFlinger::computeLayerBounds() {
- for (const auto& pair : mDisplays) {
+ for (const auto& pair : ON_MAIN_THREAD(mDisplays)) {
const auto& displayDevice = pair.second;
const auto display = displayDevice->getCompositionDisplay();
for (const auto& layer : mDrawingState.layersSortedByZ) {
@@ -2338,10 +2363,8 @@
}
}
-void SurfaceFlinger::postFrame()
-{
- // |mStateLock| not needed as we are on the main thread
- const auto display = getDefaultDisplayDeviceLocked();
+void SurfaceFlinger::postFrame() {
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
if (display && getHwComposer().isConnected(*display->getId())) {
uint32_t flipCount = display->getPageFlipCount();
if (flipCount % LOG_FRAME_STATS_PERIOD == 0) {
@@ -2640,7 +2663,8 @@
}
processDisplayAdded(displayToken, currentState);
if (currentState.physical) {
- initializeDisplays();
+ const auto display = getDisplayDeviceLocked(displayToken);
+ setPowerModeInternal(display, hal::PowerMode::ON);
}
return;
}
@@ -2772,7 +2796,7 @@
sp<const DisplayDevice> hintDisplay;
uint32_t currentlayerStack = 0;
bool first = true;
- mCurrentState.traverse([&](Layer* layer) {
+ mCurrentState.traverse([&](Layer* layer) REQUIRES(mStateLock) {
// NOTE: we rely on the fact that layers are sorted by
// layerStack first (so we don't have to traverse the list
// of displays for every layer).
@@ -2868,7 +2892,7 @@
std::vector<InputWindowInfo> inputHandles;
mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
- if (layer->hasInput()) {
+ if (layer->needsInputInfo()) {
// When calculating the screen bounds we ignore the transparent region since it may
// result in an unwanted offset.
inputHandles.push_back(layer->fillInputInfo());
@@ -2885,10 +2909,9 @@
mPendingInputWindowCommands.clear();
}
-void SurfaceFlinger::updateCursorAsync()
-{
+void SurfaceFlinger::updateCursorAsync() {
compositionengine::CompositionRefreshArgs refreshArgs;
- for (const auto& [_, display] : mDisplays) {
+ for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
if (display->getId()) {
refreshArgs.outputs.push_back(display->getCompositionDisplay());
}
@@ -2898,7 +2921,7 @@
}
void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate,
- Scheduler::ConfigEvent event) NO_THREAD_SAFETY_ANALYSIS {
+ 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
@@ -2956,8 +2979,8 @@
// anyway since there are no connected apps at this point.
const nsecs_t vsyncPeriod =
mRefreshRateConfigs->getRefreshRateFromConfigId(currentConfig).getVsyncPeriod();
- mScheduler->onConfigChanged(mAppConnectionHandle, primaryDisplayId.value, currentConfig,
- vsyncPeriod);
+ mScheduler->onPrimaryDisplayConfigChanged(mAppConnectionHandle, primaryDisplayId.value,
+ currentConfig, vsyncPeriod);
}
void SurfaceFlinger::commitTransaction()
@@ -3024,7 +3047,7 @@
}
void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
- for (const auto& [token, displayDevice] : mDisplays) {
+ for (const auto& [token, displayDevice] : ON_MAIN_THREAD(mDisplays)) {
auto display = displayDevice->getCompositionDisplay();
if (display->belongsInOutput(layer->getLayerStack(), layer->getPrimaryDisplayOnly())) {
display->editState().dirtyRegion.orSelf(dirty);
@@ -3214,6 +3237,10 @@
return old;
}
+uint32_t SurfaceFlinger::setTransactionFlagsNoWake(uint32_t flags) {
+ return mTransactionFlags.fetch_or(flags);
+}
+
bool SurfaceFlinger::flushTransactionQueues() {
// to prevent onHandleDestroyed from being called while the lock is held,
// we must keep a copy of the transactions (specifically the composer
@@ -3373,6 +3400,12 @@
for (const ComposerState& state : states) {
clientStateFlags |= setClientStateLocked(state, desiredPresentTime, postTime, privileged,
listenerCallbacksWithSurfaces);
+ if ((flags & eAnimation) && state.state.surface) {
+ if (const auto layer = fromHandleLocked(state.state.surface).promote(); layer) {
+ mScheduler->recordLayerHistory(layer.get(), desiredPresentTime,
+ LayerHistory::LayerUpdateType::AnimationTX);
+ }
+ }
}
for (const auto& listenerCallback : listenerCallbacksWithSurfaces) {
@@ -3657,7 +3690,7 @@
if (layer->setCornerRadius(s.cornerRadius))
flags |= eTraversalNeeded;
}
- if (what & layer_state_t::eBackgroundBlurRadiusChanged && !mDisableBlurs) {
+ if (what & layer_state_t::eBackgroundBlurRadiusChanged && !mDisableBlurs && mSupportsBlur) {
if (layer->setBackgroundBlurRadius(s.backgroundBlurRadius)) flags |= eTraversalNeeded;
}
if (what & layer_state_t::eLayerStackChanged) {
@@ -4125,7 +4158,7 @@
void SurfaceFlinger::initializeDisplays() {
// Async since we may be called from the main thread.
- static_cast<void>(schedule([this]() NO_THREAD_SAFETY_ANALYSIS { onInitializeDisplays(); }));
+ static_cast<void>(schedule([this]() MAIN_THREAD { onInitializeDisplays(); }));
}
void SurfaceFlinger::setVsyncEnabledInHWC(DisplayId displayId, hal::Vsync enabled) {
@@ -4216,7 +4249,7 @@
}
void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
- schedule([=]() NO_THREAD_SAFETY_ANALYSIS {
+ schedule([=]() MAIN_THREAD {
const auto display = getDisplayDeviceLocked(displayToken);
if (!display) {
ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
@@ -4229,8 +4262,7 @@
}).wait();
}
-status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args,
- bool asProto) NO_THREAD_SAFETY_ANALYSIS {
+status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) {
std::string result;
IPCThreadState* ipc = IPCThreadState::self();
@@ -4242,18 +4274,6 @@
StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n",
pid, uid);
} else {
- // Try to get the main lock, but give up after one second
- // (this would indicate SF is stuck, but we want to be able to
- // print something in dumpsys).
- status_t err = mStateLock.timedLock(s2ns(1));
- bool locked = (err == NO_ERROR);
- if (!locked) {
- StringAppendF(&result,
- "SurfaceFlinger appears to be unresponsive (%s [%d]), dumping anyways "
- "(no locks held)\n",
- strerror(-err), err);
- }
-
static const std::unordered_map<std::string, Dumper> dumpers = {
{"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
{"--dispsync"s,
@@ -4271,18 +4291,23 @@
const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
- const auto it = dumpers.find(flag);
- if (it != dumpers.end()) {
- (it->second)(args, asProto, result);
- } else if (!asProto) {
- dumpAllLocked(args, result);
+ bool dumpLayers = true;
+ {
+ TimedLock lock(mStateLock, s2ns(1), __FUNCTION__);
+ if (!lock.locked()) {
+ StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
+ strerror(-lock.status), lock.status);
+ }
+
+ if (const auto it = dumpers.find(flag); it != dumpers.end()) {
+ (it->second)(args, asProto, result);
+ dumpLayers = false;
+ } else if (!asProto) {
+ dumpAllLocked(args, result);
+ }
}
- if (locked) {
- mStateLock.unlock();
- }
-
- if (it == dumpers.end()) {
+ if (dumpLayers) {
const LayersProto layersProto = dumpProtoFromMainThread();
if (asProto) {
result.append(layersProto.SerializeAsString());
@@ -4556,7 +4581,7 @@
LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
// If context is SurfaceTracing thread, mTracingLock blocks display transactions on main thread.
- const auto display = getDefaultDisplayDeviceLocked();
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
LayersProto layersProto;
for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
@@ -5392,7 +5417,7 @@
return NO_ERROR;
}
-const sp<DisplayDevice> SurfaceFlinger::getDisplayByIdOrLayerStack(uint64_t displayOrLayerStack) {
+sp<DisplayDevice> SurfaceFlinger::getDisplayByIdOrLayerStack(uint64_t displayOrLayerStack) {
const sp<IBinder> displayToken = getPhysicalDisplayTokenLocked(DisplayId{displayOrLayerStack});
if (displayToken) {
return getDisplayDeviceLocked(displayToken);
@@ -5402,7 +5427,7 @@
return getDisplayByLayerStack(displayOrLayerStack);
}
-const sp<DisplayDevice> SurfaceFlinger::getDisplayByLayerStack(uint64_t layerStack) {
+sp<DisplayDevice> SurfaceFlinger::getDisplayByLayerStack(uint64_t layerStack) {
for (const auto& [token, display] : mDisplays) {
if (display->getLayerStack() == layerStack) {
return display;
@@ -5908,8 +5933,8 @@
const nsecs_t vsyncPeriod = getHwComposer()
.getConfigs(*displayId)[policy->defaultConfig.value()]
->getVsyncPeriod();
- mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
- policy->defaultConfig, vsyncPeriod);
+ mScheduler->onNonPrimaryDisplayConfigChanged(mAppConnectionHandle, display->getId()->value,
+ policy->defaultConfig, vsyncPeriod);
return NO_ERROR;
}
@@ -5941,8 +5966,8 @@
const nsecs_t vsyncPeriod =
mRefreshRateConfigs->getRefreshRateFromConfigId(display->getActiveConfig())
.getVsyncPeriod();
- mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
- display->getActiveConfig(), vsyncPeriod);
+ mScheduler->onPrimaryDisplayConfigChanged(mAppConnectionHandle, display->getId()->value,
+ display->getActiveConfig(), vsyncPeriod);
auto configId = mScheduler->getPreferredConfigId();
auto& preferredRefreshRate = configId
@@ -5978,7 +6003,7 @@
}
auto future = schedule([=]() -> status_t {
- const auto display = getDisplayDeviceLocked(displayToken);
+ const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
if (!display) {
ALOGE("Attempt to set desired display configs for invalid display token %p",
displayToken.get());
@@ -6133,7 +6158,7 @@
return BAD_VALUE;
}
- static_cast<void>(schedule([=]() NO_THREAD_SAFETY_ANALYSIS {
+ static_cast<void>(schedule([=] {
Mutex::Autolock lock(mStateLock);
if (authenticateSurfaceTextureLocked(surface)) {
sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();
@@ -6162,8 +6187,7 @@
sp<IBinder> token;
if (mFrameRateFlexibilityTokenCount == 0) {
- // |mStateLock| not needed as we are on the main thread
- const auto display = getDefaultDisplayDeviceLocked();
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
// This is a little racy, but not in a way that hurts anything. As we grab the
// defaultConfig from the display manager policy, we could be setting a new display
@@ -6213,8 +6237,7 @@
mFrameRateFlexibilityTokenCount--;
ALOGD("Frame rate flexibility token released. count=%d", mFrameRateFlexibilityTokenCount);
if (mFrameRateFlexibilityTokenCount == 0) {
- // |mStateLock| not needed as we are on the main thread
- const auto display = getDefaultDisplayDeviceLocked();
+ const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
constexpr bool kOverridePolicy = true;
status_t result = setDesiredDisplayConfigSpecsInternal(display, {}, kOverridePolicy);
LOG_ALWAYS_FATAL_IF(result < 0, "Failed releasing frame rate flexibility token");
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 481f350..5c81a54 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -289,12 +289,6 @@
// The CompositionEngine encapsulates all composition related interfaces and actions.
compositionengine::CompositionEngine& getCompositionEngine() const;
- // returns the default Display
- sp<const DisplayDevice> getDefaultDisplayDevice() {
- Mutex::Autolock _l(mStateLock);
- return getDefaultDisplayDeviceLocked();
- }
-
// Obtains a name from the texture pool, or, if the pool is empty, posts a
// synchronous message to the main thread to obtain one on the fly
uint32_t getNewTexture();
@@ -307,7 +301,7 @@
void setPrimaryVsyncEnabled(bool enabled);
// main thread function to enable/disable h/w composer event
- void setPrimaryVsyncEnabledInternal(bool enabled);
+ void setPrimaryVsyncEnabledInternal(bool enabled) REQUIRES(mStateLock);
void setVsyncEnabledInHWC(DisplayId displayId, hal::Vsync enabled);
// called on the main thread by MessageQueue when an internal message
@@ -472,7 +466,7 @@
HdrCapabilities* outCapabilities) const override;
status_t enableVSyncInjections(bool enable) override;
status_t injectVSync(nsecs_t when) override;
- status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) const override;
+ status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) override;
status_t getColorManagement(bool* outGetColorManagement) const override;
status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
ui::Dataspace* outWideColorGamutDataspace,
@@ -634,6 +628,12 @@
uint32_t peekTransactionFlags();
// Can only be called from the main thread or with mStateLock held
uint32_t setTransactionFlags(uint32_t flags);
+ // Set the transaction flags, but don't trigger a wakeup! We use this cases where
+ // there are still pending transactions but we know they won't be ready until a frame
+ // arrives from a different layer. So we need to ensure we performTransaction from invalidate
+ // but there is no need to try and wake up immediately to do it. Rather we rely on
+ // onFrameAvailable to wake us up.
+ uint32_t setTransactionFlagsNoWake(uint32_t flags);
uint32_t setTransactionFlags(uint32_t flags, Scheduler::TransactionStart transactionStart);
void commitTransaction() REQUIRES(mStateLock);
void commitOffscreenLayers();
@@ -722,8 +722,8 @@
status_t captureScreenCommon(RenderArea& renderArea, TraverseLayersFunction traverseLayers,
const sp<GraphicBuffer>& buffer, bool useIdentityTransform,
bool regionSampling, bool& outCapturedSecureLayers);
- const sp<DisplayDevice> getDisplayByIdOrLayerStack(uint64_t displayOrLayerStack);
- const sp<DisplayDevice> getDisplayByLayerStack(uint64_t layerStack);
+ sp<DisplayDevice> getDisplayByIdOrLayerStack(uint64_t displayOrLayerStack) REQUIRES(mStateLock);
+ sp<DisplayDevice> getDisplayByLayerStack(uint64_t layerStack) REQUIRES(mStateLock);
status_t captureScreenImplLocked(const RenderArea& renderArea,
TraverseLayersFunction traverseLayers,
const sp<GraphicBuffer>& buffer, bool useIdentityTransform,
@@ -751,29 +751,31 @@
// called when starting, or restarting after system_server death
void initializeDisplays();
- // NOTE: can only be called from the main thread or with mStateLock held
- sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const {
+ sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
+ REQUIRES(mStateLock) {
return const_cast<SurfaceFlinger*>(this)->getDisplayDeviceLocked(displayToken);
}
- // NOTE: can only be called from the main thread or with mStateLock held
- sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) {
+ sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) REQUIRES(mStateLock) {
const auto it = mDisplays.find(displayToken);
return it == mDisplays.end() ? nullptr : it->second;
}
- sp<const DisplayDevice> getDefaultDisplayDeviceLocked() const {
+ sp<const DisplayDevice> getDefaultDisplayDeviceLocked() const REQUIRES(mStateLock) {
return const_cast<SurfaceFlinger*>(this)->getDefaultDisplayDeviceLocked();
}
- sp<DisplayDevice> getDefaultDisplayDeviceLocked() {
+ sp<DisplayDevice> getDefaultDisplayDeviceLocked() REQUIRES(mStateLock) {
if (const auto token = getInternalDisplayTokenLocked()) {
return getDisplayDeviceLocked(token);
}
return nullptr;
}
- std::optional<DeviceProductInfo> getDeviceProductInfoLocked(DisplayId) const;
+ sp<const DisplayDevice> getDefaultDisplayDevice() EXCLUDES(mStateLock) {
+ Mutex::Autolock lock(mStateLock);
+ return getDefaultDisplayDeviceLocked();
+ }
// mark a region of a layer stack dirty. this updates the dirty
// region of all screens presenting this layer stack.
@@ -827,10 +829,11 @@
std::shared_ptr<compositionengine::Display> compositionDisplay,
const DisplayDeviceState& state,
const sp<compositionengine::DisplaySurface>& displaySurface,
- const sp<IGraphicBufferProducer>& producer);
+ const sp<IGraphicBufferProducer>& producer) REQUIRES(mStateLock);
void processDisplayChangesLocked() REQUIRES(mStateLock);
- void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState& state);
- void processDisplayRemoved(const wp<IBinder>& displayToken);
+ void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState&)
+ REQUIRES(mStateLock);
+ void processDisplayRemoved(const wp<IBinder>& displayToken) REQUIRES(mStateLock);
void processDisplayChanged(const wp<IBinder>& displayToken,
const DisplayDeviceState& currentState,
const DisplayDeviceState& drawingState) REQUIRES(mStateLock);
@@ -873,12 +876,13 @@
/*
* Display identification
*/
- sp<IBinder> getPhysicalDisplayTokenLocked(DisplayId displayId) const {
+ sp<IBinder> getPhysicalDisplayTokenLocked(DisplayId displayId) const REQUIRES(mStateLock) {
const auto it = mPhysicalDisplayTokens.find(displayId);
return it != mPhysicalDisplayTokens.end() ? it->second : nullptr;
}
- std::optional<DisplayId> getPhysicalDisplayIdLocked(const sp<IBinder>& displayToken) const {
+ std::optional<DisplayId> getPhysicalDisplayIdLocked(const sp<IBinder>& displayToken) const
+ REQUIRES(mStateLock) {
for (const auto& [id, token] : mPhysicalDisplayTokens) {
if (token == displayToken) {
return id;
@@ -888,12 +892,12 @@
}
// TODO(b/74619554): Remove special cases for primary display.
- sp<IBinder> getInternalDisplayTokenLocked() const {
+ sp<IBinder> getInternalDisplayTokenLocked() const REQUIRES(mStateLock) {
const auto displayId = getInternalDisplayIdLocked();
return displayId ? getPhysicalDisplayTokenLocked(*displayId) : nullptr;
}
- std::optional<DisplayId> getInternalDisplayIdLocked() const {
+ std::optional<DisplayId> getInternalDisplayIdLocked() const REQUIRES(mStateLock) {
const auto hwcDisplayId = getHwComposer().getInternalHwcDisplayId();
return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
}
@@ -945,9 +949,9 @@
void recordBufferingStats(const std::string& layerName,
std::vector<OccupancyTracker::Segment>&& history);
void dumpBufferingStats(std::string& result) const;
- void dumpDisplayIdentificationData(std::string& result) const;
+ void dumpDisplayIdentificationData(std::string& result) const REQUIRES(mStateLock);
void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
- void dumpWideColorInfo(std::string& result) const;
+ void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
void dumpOffscreenLayersProto(LayersProto& layersProto,
uint32_t traceFlags = SurfaceTracing::TRACE_ALL) const;
@@ -974,7 +978,7 @@
/* ------------------------------------------------------------------------
* VrFlinger
*/
- void resetDisplayState();
+ void resetDisplayState() REQUIRES(mStateLock);
// Check to see if we should handoff to vr flinger.
void updateVrFlinger();
@@ -1057,16 +1061,14 @@
hal::HWDisplayId hwcDisplayId;
hal::Connection connection = hal::Connection::INVALID;
};
- // protected by mStateLock
- std::vector<HotplugEvent> mPendingHotplugEvents;
+ std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mStateLock);
// this may only be written from the main thread with mStateLock held
// it may be read from other threads with mStateLock held
- std::map<wp<IBinder>, sp<DisplayDevice>> mDisplays;
- std::unordered_map<DisplayId, sp<IBinder>> mPhysicalDisplayTokens;
+ std::map<wp<IBinder>, sp<DisplayDevice>> mDisplays GUARDED_BY(mStateLock);
+ std::unordered_map<DisplayId, sp<IBinder>> mPhysicalDisplayTokens GUARDED_BY(mStateLock);
- // protected by mStateLock
- std::unordered_map<BBinder*, wp<Layer>> mLayersByLocalBinderToken;
+ std::unordered_map<BBinder*, wp<Layer>> mLayersByLocalBinderToken GUARDED_BY(mStateLock);
// don't use a lock for these, we don't care
int mDebugRegion = 0;
@@ -1287,6 +1289,8 @@
// janky frames there are.
nsecs_t mMissedFrameJankStart = 0;
int32_t mMissedFrameJankCount = 0;
+ // Positive if jank should be uploaded in postComposition
+ nsecs_t mLastJankDuration = -1;
int mFrameRateFlexibilityTokenCount = 0;
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index c90b1b8..894ee6d 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -87,7 +87,9 @@
StringAppendF(&result, "badDesiredPresentFrames = %d\n", badDesiredPresentFrames);
const auto iter = deltas.find("present2present");
if (iter != deltas.end()) {
- StringAppendF(&result, "averageFPS = %.3f\n", 1000.0 / iter->second.averageTime());
+ const float averageTime = iter->second.averageTime();
+ const float averageFPS = averageTime < 1.0f ? 0.0f : 1000.0f / averageTime;
+ StringAppendF(&result, "averageFPS = %.3f\n", averageFPS);
}
for (const auto& ele : deltas) {
StringAppendF(&result, "%s histogram is as below:\n", ele.first.c_str());
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 94ebe89..fe2af80 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -22,6 +22,7 @@
"Credentials_test.cpp",
"DereferenceSurfaceControl_test.cpp",
"DisplayConfigs_test.cpp",
+ "EffectLayer_test.cpp",
"InvalidHandles_test.cpp",
"LayerCallback_test.cpp",
"LayerRenderTypeTransaction_test.cpp",
diff --git a/services/surfaceflinger/tests/EffectLayer_test.cpp b/services/surfaceflinger/tests/EffectLayer_test.cpp
new file mode 100644
index 0000000..3dca391
--- /dev/null
+++ b/services/surfaceflinger/tests/EffectLayer_test.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#include "LayerTransactionTest.h"
+
+namespace android {
+
+class EffectLayerTest : public LayerTransactionTest {
+protected:
+ virtual void SetUp() {
+ LayerTransactionTest::SetUp();
+ ASSERT_EQ(NO_ERROR, mClient->initCheck());
+
+ const auto display = SurfaceComposerClient::getInternalDisplayToken();
+ ASSERT_FALSE(display == nullptr);
+
+ mParentLayer = createColorLayer("Parent layer", Color::RED);
+ asTransaction([&](Transaction& t) {
+ t.setDisplayLayerStack(display, 0);
+ t.setLayer(mParentLayer, INT32_MAX - 2).show(mParentLayer);
+ t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+ });
+ }
+
+ virtual void TearDown() {
+ LayerTransactionTest::TearDown();
+ mParentLayer = 0;
+ }
+
+ sp<SurfaceControl> mParentLayer;
+};
+
+TEST_F(EffectLayerTest, DefaultEffectLayerHasSolidBlackFill) {
+ sp<SurfaceControl> effectLayer =
+ mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceEffect,
+ mParentLayer.get());
+
+ EXPECT_NE(nullptr, effectLayer.get()) << "failed to create SurfaceControl";
+ asTransaction([&](Transaction& t) {
+ t.setCrop_legacy(effectLayer, Rect(0, 0, 400, 400));
+ t.show(effectLayer);
+ });
+
+ {
+ SCOPED_TRACE("Default effect Layer has solid black fill");
+ auto shot = screenshot();
+ shot->expectColor(Rect(0, 0, 400, 400), Color::BLACK);
+ }
+}
+
+TEST_F(EffectLayerTest, EffectLayerWithNoFill) {
+ sp<SurfaceControl> effectLayer =
+ mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceEffect |
+ ISurfaceComposerClient::eNoColorFill,
+ mParentLayer.get());
+
+ EXPECT_NE(nullptr, effectLayer.get()) << "failed to create SurfaceControl";
+ asTransaction([&](Transaction& t) {
+ t.setCrop_legacy(effectLayer, Rect(0, 0, 400, 400));
+ t.show(effectLayer);
+ });
+
+ {
+ SCOPED_TRACE("Effect layer with nofill option is transparent");
+ auto shot = screenshot();
+ shot->expectColor(Rect(0, 0, 400, 400), Color::RED);
+ }
+}
+
+TEST_F(EffectLayerTest, EffectLayerCanSetColor) {
+ sp<SurfaceControl> effectLayer =
+ mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceEffect |
+ ISurfaceComposerClient::eNoColorFill,
+ mParentLayer.get());
+
+ EXPECT_NE(nullptr, effectLayer.get()) << "failed to create SurfaceControl";
+ asTransaction([&](Transaction& t) {
+ t.setCrop_legacy(effectLayer, Rect(0, 0, 400, 400));
+ t.setColor(effectLayer,
+ half3{Color::GREEN.r / 255.0f, Color::GREEN.g / 255.0f,
+ Color::GREEN.b / 255.0f});
+ t.show(effectLayer);
+ });
+
+ {
+ SCOPED_TRACE("Effect Layer can set color");
+ auto shot = screenshot();
+ shot->expectColor(Rect(0, 0, 400, 400), Color::GREEN);
+ }
+}
+
+} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index d666d7e..7d4314f 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -220,6 +220,51 @@
shot->expectColor(Rect(size - testArea, 0, right, testArea), Color::BLACK);
shot->expectColor(Rect(0, bottom - testArea, testArea, bottom), Color::BLACK);
shot->expectColor(Rect(size - testArea, bottom - testArea, right, bottom), Color::BLACK);
+ // Solid center
+ shot->expectColor(Rect(size / 2 - testArea / 2, size / 2 - testArea / 2,
+ size / 2 + testArea / 2, size / 2 + testArea / 2),
+ Color::RED);
+ }
+}
+
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusRotated) {
+ sp<SurfaceControl> parent;
+ sp<SurfaceControl> child;
+ const uint8_t size = 64;
+ const uint8_t testArea = 4;
+ const float cornerRadius = 20.0f;
+ ASSERT_NO_FATAL_FAILURE(parent = createLayer("parent", size, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(parent, Color::RED, size, size));
+ ASSERT_NO_FATAL_FAILURE(child = createLayer("child", size, size));
+ ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::GREEN, size, size));
+
+ auto transaction = Transaction()
+ .setCornerRadius(parent, cornerRadius)
+ .setCrop_legacy(parent, Rect(0, 0, size, size))
+ .reparent(child, parent->getHandle())
+ .setPosition(child, 0, size)
+ // Rotate by half PI
+ .setMatrix(child, 0.0f, -1.0f, 1.0f, 0.0f);
+ if (mLayerType == ISurfaceComposerClient::eFXSurfaceBufferQueue) {
+ transaction.setCrop_legacy(parent, Rect(0, 0, size, size));
+ } else {
+ transaction.setFrame(parent, Rect(0, 0, size, size));
+ }
+ transaction.apply();
+
+ {
+ const uint8_t bottom = size - 1;
+ const uint8_t right = size - 1;
+ auto shot = getScreenCapture();
+ // Edges are transparent
+ shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+ shot->expectColor(Rect(size - testArea, 0, right, testArea), Color::BLACK);
+ shot->expectColor(Rect(0, bottom - testArea, testArea, bottom - testArea), Color::BLACK);
+ shot->expectColor(Rect(right - testArea, bottom - testArea, right, bottom), Color::BLACK);
+ // Solid center
+ shot->expectColor(Rect(size / 2 - testArea / 2, size / 2 - testArea / 2,
+ size / 2 + testArea / 2, size / 2 + testArea / 2),
+ Color::GREEN);
}
}
diff --git a/services/surfaceflinger/tests/RelativeZ_test.cpp b/services/surfaceflinger/tests/RelativeZ_test.cpp
index 1180cac..3e0b3c6 100644
--- a/services/surfaceflinger/tests/RelativeZ_test.cpp
+++ b/services/surfaceflinger/tests/RelativeZ_test.cpp
@@ -207,6 +207,71 @@
sc->checkPixel(1, 1, Color::BLUE.r, Color::BLUE.g, Color::BLUE.b);
}
}
+
+// Preserve the relative z order when a layer is reparented to a layer that's already offscreen
+TEST_F(RelativeZTest, LayerWithRelativeReparentedToOffscreen) {
+ std::unique_ptr<ScreenCapture> sc;
+
+ Color testLayerColor = {255, 100, 0, 255};
+
+ // Background layer (RED)
+ // Foregroud layer (GREEN)
+ // child level 1a (testLayerColor) (relative to child level 2b)
+ // child level 1b (WHITE)
+ // child level 2a (BLUE)
+ // child level 2b (BLACK)
+ sp<SurfaceControl> childLevel1a =
+ createColorLayer("child level 1a", testLayerColor, mForegroundLayer.get());
+ sp<SurfaceControl> childLevel1b =
+ createColorLayer("child level 1b", Color::WHITE, mForegroundLayer.get());
+ sp<SurfaceControl> childLevel2a =
+ createColorLayer("child level 2a", Color::BLUE, childLevel1b.get());
+ sp<SurfaceControl> childLevel2b =
+ createColorLayer("child level 2b", Color::BLACK, childLevel1b.get());
+
+ Transaction{}
+ .setRelativeLayer(childLevel1a, childLevel2b->getHandle(), 1)
+ .show(childLevel1a)
+ .show(childLevel1b)
+ .show(childLevel2a)
+ .show(childLevel2b)
+ .apply();
+
+ {
+ // The childLevel1a should be in front of childLevel2b.
+ ScreenCapture::captureScreen(&sc);
+ sc->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), testLayerColor);
+ }
+
+ // Background layer (RED)
+ // Foregroud layer (GREEN)
+ // child level 1a (testLayerColor) (relative to child level 2b)
+ Transaction{}.reparent(childLevel1b, nullptr).apply();
+
+ // // Background layer (RED)
+ // // Foregroud layer (GREEN)
+ Transaction{}.reparent(childLevel1a, childLevel2a->getHandle()).apply();
+
+ {
+ // The childLevel1a and childLevel1b are no longer on screen
+ ScreenCapture::captureScreen(&sc);
+ sc->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), Color::GREEN);
+ }
+
+ // Background layer (RED)
+ // Foregroud layer (GREEN)
+ // child level 1b (WHITE)
+ // child level 2a (BLUE)
+ // child level 1a (testLayerColor) (relative to child level 2b)
+ // child level 2b (BLACK)
+ Transaction{}.reparent(childLevel1b, mForegroundLayer->getHandle()).apply();
+
+ {
+ // Nothing should change at this point since relative z info was preserved.
+ ScreenCapture::captureScreen(&sc);
+ sc->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), testLayerColor);
+ }
+}
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 71d17a9..cae317b 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -96,14 +96,14 @@
// no layers are returned if active layers have insufficient history.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, mTime);
+ history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
ASSERT_TRUE(history().summarize(mTime).empty());
EXPECT_EQ(1, activeLayerCount());
}
// High FPS is returned once enough history has been recorded.
for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, mTime);
+ history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1, history().summarize(mTime).size());
EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
@@ -121,7 +121,7 @@
nsecs_t time = mTime;
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -155,7 +155,7 @@
// layer1 is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer1.get(), time, time);
+ history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
}
@@ -166,12 +166,12 @@
// layer2 is frequent and has high refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
// layer1 is still active but infrequent.
- history().record(layer1.get(), time, time);
+ history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(2, history().summarize(time).size());
EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
@@ -182,7 +182,7 @@
// layer1 is no longer active.
// layer2 is frequent and has low refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -196,10 +196,10 @@
constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
if (i % RATIO == 0) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
}
- history().record(layer3.get(), time, time);
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -209,7 +209,7 @@
EXPECT_EQ(2, frequentLayerCount(time));
// layer3 becomes recently active.
- history().record(layer3.get(), time, time);
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(2, history().summarize(time).size());
EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
@@ -228,7 +228,7 @@
// layer2 still has low refresh rate.
// layer3 becomes inactive.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -246,7 +246,7 @@
// layer3 becomes active and has high refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer3.get(), time, time);
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index 431cf0f..afd2b71 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -17,6 +17,7 @@
#undef LOG_TAG
#define LOG_TAG "LayerHistoryTestV2"
+#include <Layer.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
@@ -37,6 +38,9 @@
static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
+ static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
+ static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+ LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
static constexpr float LO_FPS = 30.f;
static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
@@ -59,6 +63,13 @@
[now](const auto& pair) { return pair.second->isFrequent(now); });
}
+ auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mLayerInfos;
+ return std::count_if(infos.begin(),
+ infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+ [now](const auto& pair) { return pair.second->isAnimating(now); });
+ }
+
void setLayerInfoVote(Layer* layer,
LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
for (auto& [weak, info] : history().mLayerInfos) {
@@ -71,6 +82,26 @@
}
auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+ auto createLayer(std::string name) {
+ 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);
+ impl::LayerHistoryV2::Summary summary;
+ for (int i = 0; i < numFrames; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += framePeriod;
+
+ summary = history().summarize(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+ << "Frame rate is " << frameRate;
+ }
Hwc2::mock::Display mDisplay;
RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
@@ -105,7 +136,7 @@
// Max returned if active layers have insufficient history.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, time);
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
@@ -113,7 +144,7 @@
// Max is returned since we have enough history but there is no timestamp votes.
for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, time);
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1, history().summarize(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
@@ -130,7 +161,7 @@
nsecs_t time = systemTime();
- history().record(layer.get(), 0, time);
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
auto summary = history().summarize(time);
ASSERT_EQ(1, history().summarize(time).size());
// Layer is still considered inactive so we expect to get Min
@@ -154,7 +185,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -177,7 +208,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -204,7 +235,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -232,7 +263,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -260,7 +291,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -292,7 +323,7 @@
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -334,43 +365,48 @@
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
+ impl::LayerHistoryV2::Summary summary;
+
// layer1 is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer1.get(), time, time);
+ history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
// layer2 is frequent and has high refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
// layer1 is still active but infrequent.
- history().record(layer1.get(), time, time);
+ history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+ 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_EQ(2, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer1 is no longer active.
// layer2 is frequent and has low refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- 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);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -379,38 +415,41 @@
constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
if (i % RATIO == 0) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
}
- history().record(layer3.get(), time, time);
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(2, 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_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[1].vote);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer3 becomes recently active.
- history().record(layer3.get(), time, time);
- ASSERT_EQ(2, 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_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ 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_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer1 expires.
layer1.clear();
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ summary = history().summarize(time);
+ 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_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, layerCount());
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -418,39 +457,43 @@
// layer2 still has low refresh rate.
// layer3 becomes inactive.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time);
+ history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- 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);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer2 expires.
layer2.clear();
- EXPECT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
// layer3 becomes active and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer3.get(), time, time);
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+ history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, layerCount());
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer3 expires.
layer3.clear();
- EXPECT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(0, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
@@ -466,7 +509,7 @@
// the very first updates makes the layer frequent
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
EXPECT_EQ(1, layerCount());
@@ -477,7 +520,7 @@
}
// the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
EXPECT_EQ(1, layerCount());
@@ -491,7 +534,7 @@
// Now event if we post a quick few frame we should stay infrequent
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
EXPECT_EQ(1, layerCount());
@@ -502,7 +545,7 @@
}
// More quick frames will get us to frequent again
- history().record(layer.get(), time, time);
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
EXPECT_EQ(1, layerCount());
@@ -529,8 +572,9 @@
nsecs_t time = systemTime();
// Post a buffer to the layers to make them active
- history().record(explicitVisiblelayer.get(), time, time);
- history().record(explicitInvisiblelayer.get(), time, time);
+ history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitInvisiblelayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
EXPECT_EQ(2, layerCount());
ASSERT_EQ(1, history().summarize(time).size());
@@ -541,5 +585,164 @@
EXPECT_EQ(2, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTestV2, infrequentAnimatingLayer) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // another update with the same cadence keep in infrequent
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // an update as animation will immediately vote for Max
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ 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);
+ }
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ 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, 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);
+}
+
+TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ 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);
+}
+
+class LayerHistoryTestV2Parameterized
+ : public LayerHistoryTestV2,
+ public testing::WithParamInterface<std::chrono::nanoseconds> {};
+
+TEST_P(LayerHistoryTestV2Parameterized, HeuristicLayerWithInfrequentLayer) {
+ std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+ auto heuristicLayer = createLayer("HeuristicLayer");
+
+ EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ auto infrequentLayer = createLayer("InfrequentLayer");
+ EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ const nsecs_t startTime = systemTime();
+
+ const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+ history().record(heuristicLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(infrequentLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+
+ nsecs_t time = startTime;
+ nsecs_t lastInfrequentUpdate = startTime;
+ const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+ int infrequentLayerUpdates = 0;
+ while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+ time += heuristicUpdateDelta.count();
+ history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+
+ if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+ ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
+ totalInfrequentLayerUpdates);
+ lastInfrequentUpdate = time;
+ history().record(infrequentLayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ infrequentLayerUpdates++;
+ }
+
+ if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+ ASSERT_NE(0, history().summarize(time).size());
+ ASSERT_GE(2, history().summarize(time).size());
+
+ bool max = false;
+ bool min = false;
+ float heuristic = 0;
+ for (const auto& layer : history().summarize(time)) {
+ if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+ heuristic = layer.desiredRefreshRate;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+ max = true;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+ min = true;
+ }
+ }
+
+ if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+ EXPECT_FLOAT_EQ(24.0f, heuristic);
+ EXPECT_FALSE(max);
+ if (history().summarize(time).size() == 2) {
+ EXPECT_TRUE(min);
+ }
+ }
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestV2Parameterized,
+ ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 2b168b2..f24575e 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -43,6 +43,14 @@
RefreshRateConfigsTest();
~RefreshRateConfigsTest();
+ float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+ return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
+ }
+
+ std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+ return refreshRateConfigs.mKnownFrameRates;
+ }
+
// Test config IDs
static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
@@ -335,27 +343,22 @@
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
HWC_CONFIG_ID_72);
- // If there are not layers, there is not content detection, so return the current
- // refresh rate.
+ // If there are no layers we select the default frame rate, which is the max of the primary
+ // range.
auto layers = std::vector<LayerRequirement>{};
- EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/
- false, /*idle*/ false, &ignored));
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
- // Current refresh rate can always be changed.
- refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_60);
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/
- false, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -366,163 +369,134 @@
lr.vote = LayerVoteType::Min;
lr.name = "Min";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
lr.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
lr.name = "60Hz Heuristic";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
lr.name = "45Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
lr.name = "30Hz Heuristic";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
lr.name = "24Hz Heuristic";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.name = "";
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_72_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -532,43 +506,35 @@
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -583,28 +549,24 @@
lr2.desiredRefreshRate = 60.0f;
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected120Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 48.0f;
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 48.0f;
lr2.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -621,8 +583,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
EXPECT_EQ(mExpected120Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -631,8 +592,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "60Hz Heuristic";
EXPECT_EQ(mExpected120Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -641,8 +601,7 @@
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "60Hz ExplicitDefault";
EXPECT_EQ(mExpected120Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -651,8 +610,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -661,8 +619,7 @@
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -671,8 +628,7 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::Heuristic;
@@ -681,8 +637,7 @@
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -691,8 +646,7 @@
lr2.vote = LayerVoteType::ExplicitDefault;
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.desiredRefreshRate = 24.0f;
lr1.vote = LayerVoteType::ExplicitDefault;
@@ -701,12 +655,10 @@
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.name = "90Hz ExplicitExactOrMultiple";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -716,43 +668,35 @@
lr.vote = LayerVoteType::Min;
EXPECT_EQ(mExpected30Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 30.0f;
EXPECT_EQ(mExpected30Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60_72_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -763,71 +707,57 @@
lr.vote = LayerVoteType::Min;
lr.name = "Min";
EXPECT_EQ(mExpected30Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.vote = LayerVoteType::Max;
lr.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 90.0f;
lr.vote = LayerVoteType::Heuristic;
lr.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr.desiredRefreshRate = 60.0f;
lr.name = "60Hz Heuristic";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ true,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr.desiredRefreshRate = 45.0f;
lr.name = "45Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ true,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr.desiredRefreshRate = 30.0f;
lr.name = "30Hz Heuristic";
EXPECT_EQ(mExpected30Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ true,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr.desiredRefreshRate = 24.0f;
lr.name = "24Hz Heuristic";
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ true,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr.desiredRefreshRate = 24.0f;
lr.vote = LayerVoteType::ExplicitExactOrMultiple;
lr.name = "24Hz ExplicitExactOrMultiple";
EXPECT_EQ(mExpected72Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ true,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m30_60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -840,56 +770,48 @@
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::Max;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Min;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 24.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Max;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 15.0f;
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 30.0f;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 45.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -901,10 +823,8 @@
for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
lr.desiredRefreshRate = fps;
const auto& refreshRate =
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored);
- printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
- EXPECT_EQ(mExpected60Config, refreshRate);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
+ EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
}
}
@@ -932,7 +852,6 @@
}
TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getBestRefreshRate_Explicit) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -947,24 +866,21 @@
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 90.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.desiredRefreshRate = 90.0f;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::Heuristic;
lr1.desiredRefreshRate = 90.0f;
lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
lr2.desiredRefreshRate = 60.0f;
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, testInPolicy) {
@@ -976,7 +892,6 @@
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -988,15 +903,12 @@
for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
lr.desiredRefreshRate = fps;
const auto& refreshRate =
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored);
- printf("%.2fHz chooses %s\n", fps, refreshRate.getName().c_str());
- EXPECT_EQ(mExpected90Config, refreshRate);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
+ EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
}
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -1013,8 +925,7 @@
lr2.desiredRefreshRate = 90.0f;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1023,8 +934,7 @@
lr2.desiredRefreshRate = 90.0f;
lr2.name = "90Hz ExplicitDefault";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1032,8 +942,7 @@
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 30.0f;
@@ -1042,8 +951,7 @@
lr2.desiredRefreshRate = 90.0f;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 30.0f;
@@ -1051,12 +959,10 @@
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, /*touchActive*/ false,
- /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
@@ -1072,7 +978,7 @@
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getBestRefreshRate(layers, false, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1080,7 +986,7 @@
lr2.vote = LayerVoteType::NoVote;
lr2.name = "NoVote";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1088,7 +994,7 @@
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1096,7 +1002,7 @@
lr2.vote = LayerVoteType::Max;
lr2.name = "Max";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, false, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
// The other layer starts to provide buffers
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1106,20 +1012,20 @@
lr2.desiredRefreshRate = 90.0f;
lr2.name = "90Hz Heuristic";
EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getBestRefreshRate(layers, false, /*idle*/ false, &ignored));
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
}
TEST_F(RefreshRateConfigsTest, touchConsidered) {
- bool touchConsidered;
+ RefreshRateConfigs::GlobalSignals consideredSignals;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
- refreshRateConfigs->getBestRefreshRate({}, false, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(false, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false}, &consideredSignals);
+ EXPECT_EQ(false, consideredSignals.touch);
- refreshRateConfigs->getBestRefreshRate({}, true, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(true, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = false}, &consideredSignals);
+ EXPECT_EQ(true, consideredSignals.touch);
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
LayerRequirement{.weight = 1.0f}};
@@ -1132,8 +1038,9 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60.0f;
lr2.name = "60Hz Heuristic";
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(true, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
+ &consideredSignals);
+ EXPECT_EQ(true, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.desiredRefreshRate = 60.0f;
@@ -1141,8 +1048,9 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60.0f;
lr2.name = "60Hz Heuristic";
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(false, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
+ &consideredSignals);
+ EXPECT_EQ(false, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
lr1.desiredRefreshRate = 60.0f;
@@ -1150,8 +1058,9 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60.0f;
lr2.name = "60Hz Heuristic";
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(true, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
+ &consideredSignals);
+ EXPECT_EQ(true, consideredSignals.touch);
lr1.vote = LayerVoteType::ExplicitDefault;
lr1.desiredRefreshRate = 60.0f;
@@ -1159,12 +1068,12 @@
lr2.vote = LayerVoteType::Heuristic;
lr2.desiredRefreshRate = 60.0f;
lr2.name = "60Hz Heuristic";
- refreshRateConfigs->getBestRefreshRate(layers, true, /*idle*/ false, &touchConsidered);
- EXPECT_EQ(false, touchConsidered);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
+ &consideredSignals);
+ EXPECT_EQ(false, consideredSignals.touch);
}
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
- bool ignored;
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90_72_120Device, /*currentConfigId=*/
HWC_CONFIG_ID_60);
@@ -1200,12 +1109,119 @@
lr.name = ss.str();
const auto& refreshRate =
- refreshRateConfigs->getBestRefreshRate(layers, false, /*idle*/ false, &ignored);
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
EXPECT_FLOAT_EQ(refreshRate.getFps(), test.second)
<< "Expecting " << test.first << "fps => " << test.second << "Hz";
}
}
+TEST_F(RefreshRateConfigsTest,
+ getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_90);
+
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+ {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+ 0);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ auto& lr = layers[0];
+
+ RefreshRateConfigs::GlobalSignals consideredSignals;
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(mExpected60Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = true},
+ &consideredSignals));
+ EXPECT_EQ(false, consideredSignals.touch);
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz ExplicitDefault";
+ EXPECT_EQ(mExpected60Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = true},
+ &consideredSignals));
+ EXPECT_EQ(false, consideredSignals.touch);
+}
+
+TEST_F(RefreshRateConfigsTest,
+ getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+ {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 90.f}}),
+ 0);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.desiredRefreshRate = 90.0f;
+ lr.name = "90Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = true}));
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 90.0f;
+ lr.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = true}));
+}
+
+TEST_F(RefreshRateConfigsTest,
+ getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitLayers) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_90);
+
+ ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+ {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+ 0);
+
+ RefreshRateConfigs::GlobalSignals consideredSignals;
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false},
+ &consideredSignals));
+ EXPECT_EQ(false, consideredSignals.touch);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(mExpected60Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz ExplicitDefault";
+ EXPECT_EQ(mExpected60Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+
+ lr.vote = LayerVoteType::Heuristic;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz Heuristic";
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+
+ lr.vote = LayerVoteType::Max;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz Max";
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+
+ lr.vote = LayerVoteType::Min;
+ lr.desiredRefreshRate = 60.0f;
+ lr.name = "60Hz Min";
+ EXPECT_EQ(mExpected90Config,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+}
+
TEST_F(RefreshRateConfigsTest, groupSwitching) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
@@ -1217,10 +1233,8 @@
layer.desiredRefreshRate = 90.0f;
layer.name = "90Hz ExplicitDefault";
- bool touchConsidered;
ASSERT_EQ(HWC_CONFIG_ID_60,
- refreshRateConfigs
- ->getBestRefreshRate(layers, false, /*idle*/ false, &touchConsidered)
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
RefreshRateConfigs::Policy policy;
@@ -1228,14 +1242,13 @@
policy.allowGroupSwitching = true;
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
ASSERT_EQ(HWC_CONFIG_ID_90,
- refreshRateConfigs
- ->getBestRefreshRate(layers, false, /*idle*/ false, &touchConsidered)
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
}
TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m60_90Device,
+ std::make_unique<RefreshRateConfigs>(m30_60_90Device,
/*currentConfigId=*/HWC_CONFIG_ID_60);
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1247,23 +1260,18 @@
bool touchActive = false) -> HwcConfigIndexType {
layers[0].vote = voteType;
layers[0].desiredRefreshRate = fps;
- bool touchConsidered;
- return refreshRateConfigs
- ->getBestRefreshRate(layers, touchActive, /*idle*/ false, &touchConsidered)
+ return refreshRateConfigs->getBestRefreshRate(layers, {.touch = touchActive, .idle = false})
.getConfigId();
};
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
- {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 90.f}}),
+ {HWC_CONFIG_ID_60, {30.f, 60.f}, {30.f, 90.f}}),
0);
- bool touchConsidered;
EXPECT_EQ(HWC_CONFIG_ID_60,
- refreshRateConfigs
- ->getBestRefreshRate({}, /*touchActive=*/false, /*idle*/ false,
- &touchConsidered)
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false})
.getConfigId());
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_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));
@@ -1296,13 +1304,19 @@
auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
layers[0].name = "Test layer";
- auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> HwcConfigIndexType {
+ const auto getIdleFrameRate = [&](LayerVoteType voteType,
+ bool touchActive) -> HwcConfigIndexType {
layers[0].vote = voteType;
layers[0].desiredRefreshRate = 90.f;
- bool touchConsidered;
- return refreshRateConfigs
- ->getBestRefreshRate(layers, touchActive, /*idle=*/true, &touchConsidered)
- .getConfigId();
+ RefreshRateConfigs::GlobalSignals consideredSignals;
+ const auto configId =
+ refreshRateConfigs
+ ->getBestRefreshRate(layers, {.touch = touchActive, .idle = true},
+ &consideredSignals)
+ .getConfigId();
+ // Refresh rate will be chosen by either touch state or idle state
+ EXPECT_EQ(!touchActive, consideredSignals.idle);
+ return configId;
};
ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
@@ -1310,38 +1324,102 @@
0);
// Idle should be lower priority than touch boost.
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::NoVote, true));
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Min, true));
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Max, true));
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Heuristic, true));
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::ExplicitDefault, true));
- EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, true));
+ EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90,
+ getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/true));
+ EXPECT_EQ(HWC_CONFIG_ID_90,
+ getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/true));
// With no layers, idle should still be lower priority than touch boost.
- bool touchConsidered;
EXPECT_EQ(HWC_CONFIG_ID_90,
- refreshRateConfigs
- ->getBestRefreshRate({}, /*touchActive=*/true, /*idle=*/true,
- &touchConsidered)
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = true})
.getConfigId());
// Idle should be higher precedence than other layer frame rate considerations.
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::NoVote, false));
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Min, false));
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Max, false));
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Heuristic, false));
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::ExplicitDefault, false));
- EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, false));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/false));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/false));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/false));
+ EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/false));
+ EXPECT_EQ(HWC_CONFIG_ID_60,
+ getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/false));
+ EXPECT_EQ(HWC_CONFIG_ID_60,
+ getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/false));
// Idle should be applied rather than the current config when there are no layers.
EXPECT_EQ(HWC_CONFIG_ID_60,
- refreshRateConfigs
- ->getBestRefreshRate({}, /*touchActive=*/false, /*idle=*/true,
- &touchConsidered)
+ refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = true})
.getConfigId());
}
+TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+ for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
+ const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
+ float expectedFrameRate;
+ if (fps < 26.91f) {
+ expectedFrameRate = 24.0f;
+ } else if (fps < 37.51f) {
+ expectedFrameRate = 30.0f;
+ } else if (fps < 52.51f) {
+ expectedFrameRate = 45.0f;
+ } else if (fps < 66.01f) {
+ expectedFrameRate = 60.0f;
+ } else if (fps < 81.01f) {
+ expectedFrameRate = 72.0f;
+ } else {
+ expectedFrameRate = 90.0f;
+ }
+ EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+ << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
+ }
+}
+
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
+ auto refreshRateConfigs =
+ std::make_unique<RefreshRateConfigs>(m60_90Device,
+ /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+ struct ExpectedRate {
+ float 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},
+ };
+ /* 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; });
+ EXPECT_TRUE(equal);
+
+ auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::Heuristic;
+ for (const auto& expectedRate : knownFrameRatesExpectations) {
+ layer.desiredRefreshRate = expectedRate.rate;
+ const auto& refreshRate =
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
+ EXPECT_EQ(expectedRate.expected, refreshRate);
+ }
+}
+
} // namespace
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 806f95c..b7067a6 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -31,7 +31,7 @@
TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
: Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}
@@ -43,7 +43,7 @@
: Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
useContentDetectionV2, true) {
if (mUseContentDetectionV2) {
- mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+ mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
} else {
mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 82e68dd..4652da0 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -289,14 +289,14 @@
return mFlinger->destroyDisplay(displayToken);
}
- auto resetDisplayState() { return mFlinger->resetDisplayState(); }
+ auto resetDisplayState() NO_THREAD_SAFETY_ANALYSIS { return mFlinger->resetDisplayState(); }
auto setupNewDisplayDeviceInternal(
const wp<IBinder>& displayToken,
std::shared_ptr<compositionengine::Display> compositionDisplay,
const DisplayDeviceState& state,
const sp<compositionengine::DisplaySurface>& dispSurface,
- const sp<IGraphicBufferProducer>& producer) {
+ const sp<IGraphicBufferProducer>& producer) NO_THREAD_SAFETY_ANALYSIS {
return mFlinger->setupNewDisplayDeviceInternal(displayToken, compositionDisplay, state,
dispSurface, producer);
}
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 7a1c7c6..63a34af 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -833,6 +833,15 @@
ASSERT_EQ(0, globalProto.stats_size());
}
+TEST_F(TimeStatsTest, noInfInAverageFPS) {
+ EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 1000000);
+
+ const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+ EXPECT_THAT(result, HasSubstr("averageFPS = 0.000"));
+}
+
namespace {
std::string buildExpectedHistogramBytestring(const std::vector<int32_t>& times,
const std::vector<int32_t>& frameCounts) {
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index 1899bed..793cb8b 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -89,15 +89,18 @@
void advanceBy(nsecs_t advancement) {
mCurrentTime += advancement;
- if (mCurrentTime >= mNextCallbackTime && mCallback) {
+ if (mCurrentTime >= (mNextCallbackTime + mLag) && mCallback) {
mCallback();
}
};
+ void setLag(nsecs_t lag) { mLag = lag; }
+
private:
std::function<void()> mCallback;
nsecs_t mNextCallbackTime = 0;
nsecs_t mCurrentTime = 0;
+ nsecs_t mLag = 0;
};
class CountingCallback {
@@ -658,6 +661,46 @@
cb1.cancel();
}
+// b/154303580
+TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent) {
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmIn(_, 600)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmIn(_, 1200)).InSequence(seq);
+ CountingCallback cb1(mDispatch);
+ CountingCallback cb2(mDispatch);
+
+ EXPECT_EQ(mDispatch.schedule(cb1, 400, 1000), ScheduleResult::Scheduled);
+
+ mMockClock.setLag(100);
+ mMockClock.advanceBy(620);
+
+ EXPECT_EQ(mDispatch.schedule(cb2, 100, 2000), ScheduleResult::Scheduled);
+ mMockClock.advanceBy(80);
+
+ EXPECT_THAT(cb1.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb2.mCalls.size(), Eq(0));
+}
+
+// b/154303580.
+// If the same callback tries to reschedule itself after it's too late, timer opts to apply the
+// update later, as opposed to blocking the calling thread.
+TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminentSameCallback) {
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmIn(_, 600)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmIn(_, 930)).InSequence(seq);
+ CountingCallback cb(mDispatch);
+
+ EXPECT_EQ(mDispatch.schedule(cb, 400, 1000), ScheduleResult::Scheduled);
+
+ mMockClock.setLag(100);
+ mMockClock.advanceBy(620);
+
+ EXPECT_EQ(mDispatch.schedule(cb, 370, 2000), ScheduleResult::Scheduled);
+ mMockClock.advanceBy(80);
+
+ EXPECT_THAT(cb.mCalls.size(), Eq(1));
+}
+
class VSyncDispatchTimerQueueEntryTest : public testing::Test {
protected:
nsecs_t const mPeriod = 1000;
@@ -817,6 +860,19 @@
EXPECT_THAT(entry.schedule(1200, 500, mStubTracker, 0), Eq(ScheduleResult::Scheduled));
}
+TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) {
+ static constexpr auto effectualOffset = 200;
+ VSyncDispatchTimerQueueEntry entry(
+ "test", [](auto, auto) {}, mVsyncMoveThreshold);
+ EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
+ entry.addPendingWorkloadUpdate(100, 400);
+ entry.addPendingWorkloadUpdate(effectualOffset, 700);
+ EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
+ entry.update(mStubTracker, 0);
+ EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
+ EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset));
+}
+
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 3f14d65..a972562 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -157,7 +157,8 @@
mMockClock(std::make_shared<NiceMock<MockClock>>()),
mReactor(std::make_unique<ClockWrapper>(mMockClock),
std::make_unique<VSyncDispatchWrapper>(mMockDispatch),
- std::make_unique<VSyncTrackerWrapper>(mMockTracker), kPendingLimit) {
+ std::make_unique<VSyncTrackerWrapper>(mMockTracker), kPendingLimit,
+ false /* supportKernelIdleTimer */) {
ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow));
ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period));
}
@@ -663,6 +664,43 @@
EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
}
+TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
+ // Create a reactor which supports the kernel idle timer
+ auto idleReactor = VSyncReactor(std::make_unique<ClockWrapper>(mMockClock),
+ std::make_unique<VSyncDispatchWrapper>(mMockDispatch),
+ std::make_unique<VSyncTrackerWrapper>(mMockTracker),
+ kPendingLimit, true /* supportKernelIdleTimer */);
+
+ bool periodFlushed = true;
+ EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(5);
+ idleReactor.setIgnorePresentFences(true);
+
+ // First, set the same period, which should only be confirmed when we receive two
+ // matching callbacks
+ idleReactor.setPeriod(10000);
+ EXPECT_TRUE(idleReactor.addResyncSample(0, 0, &periodFlushed));
+ EXPECT_FALSE(periodFlushed);
+ // Correct period but incorrect timestamp delta
+ EXPECT_TRUE(idleReactor.addResyncSample(0, 10000, &periodFlushed));
+ EXPECT_FALSE(periodFlushed);
+ // Correct period and correct timestamp delta
+ EXPECT_FALSE(idleReactor.addResyncSample(10000, 10000, &periodFlushed));
+ EXPECT_TRUE(periodFlushed);
+
+ // Then, set a new period, which should be confirmed as soon as we receive a callback
+ // reporting the new period
+ nsecs_t const newPeriod = 5000;
+ idleReactor.setPeriod(newPeriod);
+ // Incorrect timestamp delta and period
+ EXPECT_TRUE(idleReactor.addResyncSample(20000, 10000, &periodFlushed));
+ EXPECT_FALSE(periodFlushed);
+ // Incorrect timestamp delta but correct period
+ EXPECT_FALSE(idleReactor.addResyncSample(20000, 5000, &periodFlushed));
+ EXPECT_TRUE(periodFlushed);
+
+ EXPECT_TRUE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+}
+
using VSyncReactorDeathTest = VSyncReactorTest;
TEST_F(VSyncReactorDeathTest, invalidRemoval) {
mReactor.addEventListener(mName, mPhase, &outerCb, lastCallbackTime);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 2a31078..c2c5072 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -140,6 +140,7 @@
const std::vector<uint8_t>&));
MOCK_METHOD1(getLayerGenericMetadataKeys,
V2_4::Error(std::vector<IComposerClient::LayerGenericMetadataKey>*));
+ MOCK_METHOD2(getClientTargetProperty, Error(Display, IComposerClient::ClientTargetProperty*));
};
} // namespace mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplay.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplay.h
index dade9fc..fe99e77 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplay.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplay.h
@@ -93,6 +93,7 @@
MOCK_METHOD1(setAutoLowLatencyMode, hal::Error(bool on));
MOCK_CONST_METHOD1(getSupportedContentTypes, hal::Error(std::vector<hal::ContentType>*));
MOCK_METHOD1(setContentType, hal::Error(hal::ContentType));
+ MOCK_METHOD1(getClientTargetProperty, hal::Error(hal::ClientTargetProperty*));
MOCK_CONST_METHOD1(getConnectionType, hal::Error(android::DisplayConnectionType*));
MOCK_CONST_METHOD0(isVsyncPeriodSwitchSupported, bool());
};
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 119f580..078d8e07 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -24,8 +24,9 @@
class MockLayer : public Layer {
public:
- explicit MockLayer(SurfaceFlinger* flinger)
- : Layer(LayerCreationArgs(flinger, nullptr, "TestLayer", 800, 600, 0, {})) {}
+ MockLayer(SurfaceFlinger* flinger, std::string name)
+ : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 800, 600, 0, {})) {}
+ explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
MOCK_CONST_METHOD0(getType, const char*());
MOCK_METHOD0(getFrameSelectionPriority, int32_t());
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
new file mode 100644
index 0000000..b4342bd
--- /dev/null
+++ b/services/vibratorservice/Android.bp
@@ -0,0 +1,52 @@
+// 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_shared {
+ name: "libvibratorservice",
+
+ srcs: [
+ "VibratorHalWrapper.cpp",
+ ],
+
+ aidl: {
+ local_include_dirs: ["include"],
+ include_dirs: [
+ "hardware/interfaces/vibrator/aidl/android/hardware/vibrator",
+ ],
+ export_aidl_headers: true
+ },
+
+ shared_libs: [
+ "libbinder",
+ "libhidlbase",
+ "liblog",
+ "libutils",
+ "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator@1.0",
+ "android.hardware.vibrator@1.1",
+ "android.hardware.vibrator@1.2",
+ "android.hardware.vibrator@1.3",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+
+ local_include_dirs: ["include"],
+
+ export_include_dirs: ["include"],
+}
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
new file mode 100644
index 0000000..db27bd9
--- /dev/null
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -0,0 +1,425 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapper"
+
+#include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/hardware/vibrator/BnVibratorCallback.h>
+#include <android/hardware/vibrator/IVibrator.h>
+#include <hardware/vibrator.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+
+using std::chrono::milliseconds;
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
+namespace Aidl = android::hardware::vibrator;
+
+namespace android {
+
+namespace vibrator {
+
+// -------------------------------------------------------------------------------------------------
+
+template <class T>
+HalResult<T> loadCached(const std::function<HalResult<T>()>& loadFn, std::optional<T>& cache) {
+ if (cache.has_value()) {
+ return HalResult<T>::ok(cache.value());
+ }
+ HalResult<T> ret = loadFn();
+ if (ret.isOk()) {
+ cache.emplace(ret.value());
+ }
+ return ret;
+}
+
+template <class T>
+bool isStaticCastValid(Effect effect) {
+ T castEffect = static_cast<T>(effect);
+ auto iter = hardware::hidl_enum_range<T>();
+ return castEffect >= *iter.begin() && castEffect <= *std::prev(iter.end());
+}
+
+template <class I, class T>
+using perform_fn = hardware::Return<void> (I::*)(T, V1_0::EffectStrength,
+ V1_0::IVibrator::perform_cb);
+
+template <class I, class T>
+HalResult<milliseconds> perform(perform_fn<I, T> performFn, sp<I> handle, T effect,
+ EffectStrength strength) {
+ V1_0::Status status;
+ int32_t lengthMs;
+ V1_0::IVibrator::perform_cb effectCallback = [&status, &lengthMs](V1_0::Status retStatus,
+ uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ };
+
+ V1_0::EffectStrength effectStrength = static_cast<V1_0::EffectStrength>(strength);
+ auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
+
+ return HalResult<milliseconds>::fromReturn(result, status, milliseconds(lengthMs));
+}
+
+// -------------------------------------------------------------------------------------------------
+
+template <typename T>
+HalResult<T> HalResult<T>::ok(T value) {
+ return HalResult(value);
+}
+
+template <typename T>
+HalResult<T> HalResult<T>::failed() {
+ return HalResult(/* unsupported= */ false);
+}
+
+template <typename T>
+HalResult<T> HalResult<T>::unsupported() {
+ return HalResult(/* unsupported= */ true);
+}
+
+template <typename T>
+HalResult<T> HalResult<T>::fromStatus(binder::Status status, T data) {
+ if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<T>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<T>::ok(data);
+ }
+ return HalResult<T>::failed();
+}
+
+template <typename T>
+HalResult<T> HalResult<T>::fromStatus(V1_0::Status status, T data) {
+ switch (status) {
+ case V1_0::Status::OK:
+ return HalResult<T>::ok(data);
+ case V1_0::Status::UNSUPPORTED_OPERATION:
+ return HalResult<T>::unsupported();
+ default:
+ return HalResult<T>::failed();
+ }
+}
+
+template <typename T>
+template <typename R>
+HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, T data) {
+ return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed();
+}
+
+template <typename T>
+template <typename R>
+HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, V1_0::Status status, T data) {
+ return ret.isOk() ? HalResult<T>::fromStatus(status, data) : HalResult<T>::failed();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HalResult<void>::ok() {
+ return HalResult();
+}
+
+HalResult<void> HalResult<void>::failed() {
+ return HalResult(/* failed= */ true);
+}
+
+HalResult<void> HalResult<void>::unsupported() {
+ return HalResult(/* failed= */ false, /* unsupported= */ true);
+}
+
+HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
+ if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<void>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<void>::ok();
+ }
+ return HalResult<void>::failed();
+}
+
+HalResult<void> HalResult<void>::fromStatus(V1_0::Status status) {
+ switch (status) {
+ case V1_0::Status::OK:
+ return HalResult<void>::ok();
+ case V1_0::Status::UNSUPPORTED_OPERATION:
+ return HalResult<void>::unsupported();
+ default:
+ return HalResult<void>::failed();
+ }
+}
+
+template <typename R>
+HalResult<void> HalResult<void>::fromReturn(hardware::Return<R>& ret) {
+ return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class HalCallbackWrapper : public Aidl::BnVibratorCallback {
+public:
+ HalCallbackWrapper(const std::function<void()>& completionCallback)
+ : mCompletionCallback(completionCallback) {}
+
+ binder::Status onComplete() override {
+ mCompletionCallback();
+ return binder::Status::ok();
+ }
+
+private:
+ const std::function<void()> mCompletionCallback;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> AidlHalWrapper::ping() {
+ return IInterface::asBinder(mHandle)->pingBinder() ? HalResult<void>::ok()
+ : HalResult<void>::failed();
+}
+
+HalResult<void> AidlHalWrapper::on(milliseconds timeout,
+ const std::function<void()>& completionCallback) {
+ auto cb = new HalCallbackWrapper(completionCallback);
+ return HalResult<void>::fromStatus(mHandle->on(timeout.count(), cb));
+}
+
+HalResult<void> AidlHalWrapper::off() {
+ return HalResult<void>::fromStatus(mHandle->off());
+}
+
+HalResult<void> AidlHalWrapper::setAmplitude(int32_t amplitude) {
+ float convertedAmplitude = static_cast<float>(amplitude) / std::numeric_limits<uint8_t>::max();
+ return HalResult<void>::fromStatus(mHandle->setAmplitude(convertedAmplitude));
+}
+
+HalResult<void> AidlHalWrapper::setExternalControl(bool enabled) {
+ return HalResult<void>::fromStatus(mHandle->setExternalControl(enabled));
+}
+
+HalResult<void> AidlHalWrapper::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
+ return HalResult<void>::fromStatus(mHandle->alwaysOnEnable(id, effect, strength));
+}
+
+HalResult<void> AidlHalWrapper::alwaysOnDisable(int32_t id) {
+ return HalResult<void>::fromStatus(mHandle->alwaysOnDisable(id));
+}
+
+HalResult<Capabilities> AidlHalWrapper::getCapabilities() {
+ std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
+ static auto loadFn = [this]() {
+ int32_t capabilities = 0;
+ auto result = mHandle->getCapabilities(&capabilities);
+ return HalResult<Capabilities>::fromStatus(result, static_cast<Capabilities>(capabilities));
+ };
+ return loadCached<Capabilities>(loadFn, mCapabilities);
+}
+
+HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffects() {
+ std::lock_guard<std::mutex> lock(mSupportedEffectsMutex);
+ static auto loadFn = [this]() {
+ std::vector<Effect> supportedEffects;
+ auto result = mHandle->getSupportedEffects(&supportedEffects);
+ return HalResult<std::vector<Effect>>::fromStatus(result, supportedEffects);
+ };
+ return loadCached<std::vector<Effect>>(loadFn, mSupportedEffects);
+}
+
+HalResult<milliseconds> AidlHalWrapper::performEffect(
+ Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
+ int32_t lengthMs;
+ auto cb = new HalCallbackWrapper(completionCallback);
+ auto result = mHandle->perform(effect, strength, cb, &lengthMs);
+ return HalResult<milliseconds>::fromStatus(result, milliseconds(lengthMs));
+}
+
+HalResult<void> AidlHalWrapper::performComposedEffect(
+ const std::vector<CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) {
+ auto cb = new HalCallbackWrapper(completionCallback);
+ return HalResult<void>::fromStatus(mHandle->compose(primitiveEffects, cb));
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_0::ping() {
+ auto result = mHandleV1_0->ping();
+ return HalResult<void>::fromReturn(result);
+}
+
+HalResult<void> HidlHalWrapperV1_0::on(milliseconds timeout, const std::function<void()>&) {
+ auto result = mHandleV1_0->on(timeout.count());
+ auto status = result.withDefault(V1_0::Status::UNKNOWN_ERROR);
+ return HalResult<void>::fromStatus(status);
+}
+
+HalResult<void> HidlHalWrapperV1_0::off() {
+ auto result = mHandleV1_0->off();
+ return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+}
+
+HalResult<void> HidlHalWrapperV1_0::setAmplitude(int32_t amplitude) {
+ auto result = mHandleV1_0->setAmplitude(static_cast<uint8_t>(amplitude));
+ return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+}
+
+HalResult<void> HidlHalWrapperV1_0::setExternalControl(bool) {
+ ALOGV("Skipped setExternalControl because Vibrator HAL does not support it");
+ return HalResult<void>::unsupported();
+}
+
+HalResult<void> HidlHalWrapperV1_0::alwaysOnEnable(int32_t, Effect, EffectStrength) {
+ ALOGV("Skipped alwaysOnEnable because Vibrator HAL AIDL is not available");
+ return HalResult<void>::unsupported();
+}
+
+HalResult<void> HidlHalWrapperV1_0::alwaysOnDisable(int32_t) {
+ ALOGV("Skipped alwaysOnDisable because Vibrator HAL AIDL is not available");
+ return HalResult<void>::unsupported();
+}
+
+HalResult<Capabilities> HidlHalWrapperV1_0::getCapabilities() {
+ std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
+ return loadCached<Capabilities>(std::bind(&HidlHalWrapperV1_0::getCapabilitiesInternal, this),
+ mCapabilities);
+}
+
+HalResult<std::vector<Effect>> HidlHalWrapperV1_0::getSupportedEffects() {
+ ALOGV("Skipped getSupportedEffects because Vibrator HAL AIDL is not available");
+ return HalResult<std::vector<Effect>>::unsupported();
+}
+
+HalResult<milliseconds> HidlHalWrapperV1_0::performEffect(Effect effect, EffectStrength strength,
+ const std::function<void()>&) {
+ if (isStaticCastValid<V1_0::Effect>(effect)) {
+ V1_0::Effect e = static_cast<V1_0::Effect>(effect);
+ return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+ }
+
+ ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
+ Aidl::toString(effect).c_str());
+ return HalResult<milliseconds>::unsupported();
+}
+
+HalResult<void> HidlHalWrapperV1_0::performComposedEffect(const std::vector<CompositeEffect>&,
+ const std::function<void()>&) {
+ ALOGV("Skipped composed effect because Vibrator HAL AIDL is not available");
+ return HalResult<void>::unsupported();
+}
+
+HalResult<Capabilities> HidlHalWrapperV1_0::getCapabilitiesInternal() {
+ hardware::Return<bool> result = mHandleV1_0->supportsAmplitudeControl();
+ Capabilities capabilities =
+ result.withDefault(false) ? Capabilities::AMPLITUDE_CONTROL : Capabilities::NONE;
+ return HalResult<Capabilities>::fromReturn(result, capabilities);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<milliseconds> HidlHalWrapperV1_1::performEffect(Effect effect, EffectStrength strength,
+ const std::function<void()>&) {
+ if (isStaticCastValid<V1_0::Effect>(effect)) {
+ V1_0::Effect e = static_cast<V1_0::Effect>(effect);
+ return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+ }
+ if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
+ V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
+ return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+ }
+
+ ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
+ Aidl::toString(effect).c_str());
+ return HalResult<milliseconds>::unsupported();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<milliseconds> HidlHalWrapperV1_2::performEffect(Effect effect, EffectStrength strength,
+ const std::function<void()>&) {
+ if (isStaticCastValid<V1_0::Effect>(effect)) {
+ V1_0::Effect e = static_cast<V1_0::Effect>(effect);
+ return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+ }
+ if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
+ V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
+ return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+ }
+ if (isStaticCastValid<V1_2::Effect>(effect)) {
+ V1_2::Effect e = static_cast<V1_2::Effect>(effect);
+ return perform(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength);
+ }
+
+ ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
+ Aidl::toString(effect).c_str());
+ return HalResult<milliseconds>::unsupported();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_3::setExternalControl(bool enabled) {
+ auto result = mHandleV1_3->setExternalControl(static_cast<uint32_t>(enabled));
+ return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+}
+
+HalResult<milliseconds> HidlHalWrapperV1_3::performEffect(Effect effect, EffectStrength strength,
+ const std::function<void()>&) {
+ if (isStaticCastValid<V1_0::Effect>(effect)) {
+ V1_0::Effect e = static_cast<V1_0::Effect>(effect);
+ return perform(&V1_0::IVibrator::perform, mHandleV1_0, e, strength);
+ }
+ if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
+ V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
+ return perform(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength);
+ }
+ if (isStaticCastValid<V1_2::Effect>(effect)) {
+ V1_2::Effect e = static_cast<V1_2::Effect>(effect);
+ return perform(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength);
+ }
+ if (isStaticCastValid<V1_3::Effect>(effect)) {
+ V1_3::Effect e = static_cast<V1_3::Effect>(effect);
+ return perform(&V1_3::IVibrator::perform_1_3, mHandleV1_3, e, strength);
+ }
+
+ ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
+ Aidl::toString(effect).c_str());
+ return HalResult<milliseconds>::unsupported();
+}
+
+HalResult<Capabilities> HidlHalWrapperV1_3::getCapabilitiesInternal() {
+ HalResult<Capabilities> parentResult = HidlHalWrapperV1_2::getCapabilitiesInternal();
+ if (!parentResult.isOk()) {
+ // Loading for previous HAL versions already failed, so propagate failure.
+ return parentResult;
+ }
+
+ Capabilities capabilities = parentResult.value();
+ auto result = mHandleV1_3->supportsExternalControl();
+ capabilities |= result.withDefault(false) ? Capabilities::EXTERNAL_CONTROL : Capabilities::NONE;
+ return HalResult<Capabilities>::fromReturn(result, capabilities);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+}; // namespace vibrator
+
+}; // namespace android
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
new file mode 100644
index 0000000..1a1f64b
--- /dev/null
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -0,0 +1,285 @@
+/*
+ * 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 ANDROID_OS_VIBRATORHALWRAPPER_H
+#define ANDROID_OS_VIBRATORHALWRAPPER_H
+
+#include <android-base/thread_annotations.h>
+#include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/hardware/vibrator/IVibrator.h>
+
+namespace android {
+
+namespace vibrator {
+
+// -------------------------------------------------------------------------------------------------
+
+// Result of a call to the Vibrator HAL wrapper, holding data if successful.
+template <typename T>
+class HalResult {
+public:
+ static HalResult<T> ok(T value);
+ static HalResult<T> failed();
+ static HalResult<T> unsupported();
+
+ static HalResult<T> fromStatus(binder::Status status, T data);
+ static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data);
+
+ template <typename R>
+ static HalResult<T> fromReturn(hardware::Return<R>& ret, T data);
+
+ template <typename R>
+ static HalResult<T> fromReturn(hardware::Return<R>& ret,
+ hardware::vibrator::V1_0::Status status, T data);
+
+ // This will throw std::bad_optional_access if this result is not ok.
+ T value() const { return mValue.value(); }
+ bool isOk() const { return !mUnsupported && mValue.has_value(); }
+ bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
+ bool isUnsupported() const { return mUnsupported; }
+
+private:
+ std::optional<T> mValue;
+ bool mUnsupported;
+
+ explicit HalResult(T value) : mValue(std::make_optional(value)), mUnsupported(false) {}
+ explicit HalResult(bool unsupported) : mValue(), mUnsupported(unsupported) {}
+};
+
+// Empty result of a call to the Vibrator HAL wrapper.
+template <>
+class HalResult<void> {
+public:
+ static HalResult<void> ok();
+ static HalResult<void> failed();
+ static HalResult<void> unsupported();
+
+ static HalResult<void> fromStatus(binder::Status status);
+ static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status);
+
+ template <typename R>
+ static HalResult<void> fromReturn(hardware::Return<R>& ret);
+
+ bool isOk() const { return !mUnsupported && !mFailed; }
+ bool isFailed() const { return !mUnsupported && mFailed; }
+ bool isUnsupported() const { return mUnsupported; }
+
+private:
+ bool mFailed;
+ bool mUnsupported;
+
+ explicit HalResult(bool failed = false, bool unsupported = false)
+ : mFailed(failed), mUnsupported(unsupported) {}
+};
+
+// -------------------------------------------------------------------------------------------------
+
+// Vibrator HAL capabilities.
+enum class Capabilities : int32_t {
+ NONE = 0,
+ ON_CALLBACK = hardware::vibrator::IVibrator::CAP_ON_CALLBACK,
+ PERFORM_CALLBACK = hardware::vibrator::IVibrator::CAP_PERFORM_CALLBACK,
+ AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_AMPLITUDE_CONTROL,
+ EXTERNAL_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_CONTROL,
+ EXTERNAL_AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL,
+ COMPOSE_EFFECTS = hardware::vibrator::IVibrator::CAP_COMPOSE_EFFECTS,
+ ALWAYS_ON_CONTROL = hardware::vibrator::IVibrator::CAP_ALWAYS_ON_CONTROL
+};
+
+inline Capabilities operator|(Capabilities lhs, Capabilities rhs) {
+ using underlying = typename std::underlying_type<Capabilities>::type;
+ return static_cast<Capabilities>(static_cast<underlying>(lhs) | static_cast<underlying>(rhs));
+}
+
+inline Capabilities& operator|=(Capabilities& lhs, Capabilities rhs) {
+ return lhs = lhs | rhs;
+}
+
+inline Capabilities operator&(Capabilities lhs, Capabilities rhs) {
+ using underlying = typename std::underlying_type<Capabilities>::type;
+ return static_cast<Capabilities>(static_cast<underlying>(lhs) & static_cast<underlying>(rhs));
+}
+
+inline Capabilities& operator&=(Capabilities& lhs, Capabilities rhs) {
+ return lhs = lhs & rhs;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+// Wrapper for Vibrator HAL handlers.
+class HalWrapper {
+public:
+ virtual ~HalWrapper() = default;
+
+ virtual HalResult<void> ping() = 0;
+
+ virtual HalResult<void> on(std::chrono::milliseconds timeout,
+ const std::function<void()>& completionCallback) = 0;
+ virtual HalResult<void> off() = 0;
+
+ virtual HalResult<void> setAmplitude(int32_t amplitude) = 0;
+ virtual HalResult<void> setExternalControl(bool enabled) = 0;
+
+ virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+ hardware::vibrator::EffectStrength strength) = 0;
+ virtual HalResult<void> alwaysOnDisable(int32_t id) = 0;
+
+ virtual HalResult<Capabilities> getCapabilities() = 0;
+ virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() = 0;
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) = 0;
+
+ virtual HalResult<void> performComposedEffect(
+ const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) = 0;
+};
+
+// Wrapper for the AIDL Vibrator HAL.
+class AidlHalWrapper : public HalWrapper {
+public:
+ explicit AidlHalWrapper(sp<hardware::vibrator::IVibrator> handle)
+ : mHandle(std::move(handle)) {}
+
+ virtual HalResult<void> ping() override;
+
+ virtual HalResult<void> on(std::chrono::milliseconds timeout,
+ const std::function<void()>& completionCallback) override;
+ virtual HalResult<void> off() override;
+
+ virtual HalResult<void> setAmplitude(int32_t amplitude) override;
+ virtual HalResult<void> setExternalControl(bool enabled) override;
+
+ virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+ hardware::vibrator::EffectStrength strength) override;
+ virtual HalResult<void> alwaysOnDisable(int32_t id) override;
+
+ virtual HalResult<Capabilities> getCapabilities() override;
+ virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override;
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) override;
+
+ virtual HalResult<void> performComposedEffect(
+ const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) override;
+
+private:
+ const sp<hardware::vibrator::IVibrator> mHandle;
+ std::mutex mCapabilitiesMutex;
+ std::mutex mSupportedEffectsMutex;
+ std::optional<Capabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
+ std::optional<std::vector<hardware::vibrator::Effect>> mSupportedEffects
+ GUARDED_BY(mSupportedEffectsMutex);
+};
+
+// Wrapper for the HDIL Vibrator HAL v1.0.
+class HidlHalWrapperV1_0 : public HalWrapper {
+public:
+ explicit HidlHalWrapperV1_0(sp<hardware::vibrator::V1_0::IVibrator> handle)
+ : mHandleV1_0(std::move(handle)) {}
+
+ virtual HalResult<void> ping() override;
+
+ virtual HalResult<void> on(std::chrono::milliseconds timeout,
+ const std::function<void()>& completionCallback) override;
+ virtual HalResult<void> off() override;
+
+ virtual HalResult<void> setAmplitude(int32_t amplitude) override;
+ virtual HalResult<void> setExternalControl(bool enabled) override;
+
+ virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+ hardware::vibrator::EffectStrength strength) override;
+ virtual HalResult<void> alwaysOnDisable(int32_t id) override;
+
+ virtual HalResult<Capabilities> getCapabilities() override;
+ virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override;
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) override;
+
+ virtual HalResult<void> performComposedEffect(
+ const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
+ const std::function<void()>& completionCallback) override;
+
+protected:
+ const sp<hardware::vibrator::V1_0::IVibrator> mHandleV1_0;
+ std::mutex mCapabilitiesMutex;
+ std::optional<Capabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
+
+ // Loads directly from IVibrator handle, skipping the mCapabilities cache.
+ virtual HalResult<Capabilities> getCapabilitiesInternal();
+};
+
+// Wrapper for the HDIL Vibrator HAL v1.1.
+class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
+public:
+ explicit HidlHalWrapperV1_1(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+ : HidlHalWrapperV1_0(handleV1_0),
+ mHandleV1_1(hardware::vibrator::V1_1::IVibrator::castFrom(handleV1_0)) {}
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) override;
+
+protected:
+ const sp<hardware::vibrator::V1_1::IVibrator> mHandleV1_1;
+};
+
+// Wrapper for the HDIL Vibrator HAL v1.2.
+class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
+public:
+ explicit HidlHalWrapperV1_2(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+ : HidlHalWrapperV1_1(handleV1_0),
+ mHandleV1_2(hardware::vibrator::V1_2::IVibrator::castFrom(handleV1_0)) {}
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) override;
+
+protected:
+ const sp<hardware::vibrator::V1_2::IVibrator> mHandleV1_2;
+};
+
+// Wrapper for the HDIL Vibrator HAL v1.3.
+class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
+public:
+ explicit HidlHalWrapperV1_3(sp<hardware::vibrator::V1_0::IVibrator> handleV1_0)
+ : HidlHalWrapperV1_2(handleV1_0),
+ mHandleV1_3(hardware::vibrator::V1_3::IVibrator::castFrom(handleV1_0)) {}
+
+ virtual HalResult<void> setExternalControl(bool enabled) override;
+
+ virtual HalResult<std::chrono::milliseconds> performEffect(
+ hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+ const std::function<void()>& completionCallback) override;
+
+protected:
+ const sp<hardware::vibrator::V1_3::IVibrator> mHandleV1_3;
+
+ virtual HalResult<Capabilities> getCapabilitiesInternal() override;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+}; // namespace vibrator
+
+}; // namespace android
+
+#endif // ANDROID_OS_VIBRATORHALWRAPPER_H
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
new file mode 100644
index 0000000..7c038e9
--- /dev/null
+++ b/services/vibratorservice/test/Android.bp
@@ -0,0 +1,46 @@
+// 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: "libvibratorservice_test",
+ test_suites: ["device-tests"],
+ srcs: [
+ "VibratorHalWrapperAidlTest.cpp",
+ "VibratorHalWrapperHidlV1_0Test.cpp",
+ "VibratorHalWrapperHidlV1_1Test.cpp",
+ "VibratorHalWrapperHidlV1_2Test.cpp",
+ "VibratorHalWrapperHidlV1_3Test.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libhidlbase",
+ "liblog",
+ "libvibratorservice",
+ "libutils",
+ "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator@1.0",
+ "android.hardware.vibrator@1.1",
+ "android.hardware.vibrator@1.2",
+ "android.hardware.vibrator@1.3",
+ ],
+ static_libs: [
+ "libgmock",
+ ],
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
new file mode 100644
index 0000000..6db449a
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -0,0 +1,393 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapperAidlTest"
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <thread>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+using android::binder::Status;
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::CompositePrimitive;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+using android::hardware::vibrator::IVibrator;
+using android::hardware::vibrator::IVibratorCallback;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockBinder : public BBinder {
+public:
+ MOCK_METHOD(status_t, linkToDeath,
+ (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
+ MOCK_METHOD(status_t, unlinkToDeath,
+ (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
+ wp<DeathRecipient>* outRecipient),
+ (override));
+ MOCK_METHOD(status_t, pingBinder, (), (override));
+};
+
+class MockIVibrator : public IVibrator {
+public:
+ MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
+ MOCK_METHOD(Status, off, (), (override));
+ MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
+ MOCK_METHOD(Status, perform,
+ (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
+ (override));
+ MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
+ MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
+ MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
+ MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
+ MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
+ MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
+ (override));
+ MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
+ MOCK_METHOD(Status, compose,
+ (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
+ (override));
+ MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
+ MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
+ MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
+ MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+ MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+ MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalWrapperAidlTest : public Test {
+public:
+ void SetUp() override {
+ mMockBinder = new StrictMock<MockBinder>();
+ mMockHal = new StrictMock<MockIVibrator>();
+ mWrapper = std::make_unique<vibrator::AidlHalWrapper>(mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibrator>> mMockHal = nullptr;
+ sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+ACTION(TriggerCallbackInArg1) {
+ if (arg1 != nullptr) {
+ arg1->onComplete();
+ }
+}
+
+ACTION(TriggerCallbackInArg2) {
+ if (arg2 != nullptr) {
+ arg2->onComplete();
+ }
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestPing) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), onAsBinder())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(mMockBinder.get()));
+ EXPECT_CALL(*mMockBinder.get(), pingBinder()).Times(Exactly(1));
+ }
+
+ ASSERT_TRUE(mWrapper->ping().isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestOn) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+ EXPECT_CALL(*mMockHal.get(), on(Eq(100), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(), on(Eq(1000), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(10ms, callback).isOk());
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(100ms, callback).isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(1000ms, callback).isFailed());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestOff) {
+ EXPECT_CALL(*mMockHal.get(), off())
+ .Times(Exactly(3))
+ .WillOnce(Return(Status()))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+
+ ASSERT_TRUE(mWrapper->off().isOk());
+ ASSERT_TRUE(mWrapper->off().isUnsupported());
+ ASSERT_TRUE(mWrapper->off().isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestSetAmplitude) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(FloatNear(0.1, 1e-2))).Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(FloatNear(0.2, 1e-2)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(FloatNear(0.5, 1e-2)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ ASSERT_TRUE(mWrapper->setAmplitude(std::numeric_limits<uint8_t>::max() / 10).isOk());
+ ASSERT_TRUE(mWrapper->setAmplitude(std::numeric_limits<uint8_t>::max() / 5).isUnsupported());
+ ASSERT_TRUE(mWrapper->setAmplitude(std::numeric_limits<uint8_t>::max() / 2).isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestSetExternalControl) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true))).Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(false)))
+ .Times(Exactly(2))
+ .WillOnce(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ ASSERT_TRUE(mWrapper->setExternalControl(true).isOk());
+ ASSERT_TRUE(mWrapper->setExternalControl(false).isUnsupported());
+ ASSERT_TRUE(mWrapper->setExternalControl(false).isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestAlwaysOnEnable) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(),
+ alwaysOnEnable(Eq(1), Eq(Effect::CLICK), Eq(EffectStrength::LIGHT)))
+ .Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(),
+ alwaysOnEnable(Eq(2), Eq(Effect::TICK), Eq(EffectStrength::MEDIUM)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(),
+ alwaysOnEnable(Eq(3), Eq(Effect::POP), Eq(EffectStrength::STRONG)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ auto result = mWrapper->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT);
+ ASSERT_TRUE(result.isOk());
+ result = mWrapper->alwaysOnEnable(2, Effect::TICK, EffectStrength::MEDIUM);
+ ASSERT_TRUE(result.isUnsupported());
+ result = mWrapper->alwaysOnEnable(3, Effect::POP, EffectStrength::STRONG);
+ ASSERT_TRUE(result.isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestAlwaysOnDisable) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1))).Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(2)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(3)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ ASSERT_TRUE(mWrapper->alwaysOnDisable(1).isOk());
+ ASSERT_TRUE(mWrapper->alwaysOnDisable(2).isUnsupported());
+ ASSERT_TRUE(mWrapper->alwaysOnDisable(3).isFailed());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestGetCapabilitiesDoesNotCacheFailedResult) {
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+
+ ASSERT_TRUE(mWrapper->getCapabilities().isUnsupported());
+ ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, result.value());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestGetCapabilitiesCachesResult) {
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestGetSupportedEffectsDoesNotCacheFailedResult) {
+ std::vector<Effect> supportedEffects;
+ supportedEffects.push_back(Effect::CLICK);
+ supportedEffects.push_back(Effect::TICK);
+
+ EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+
+ ASSERT_TRUE(mWrapper->getSupportedEffects().isUnsupported());
+ ASSERT_TRUE(mWrapper->getSupportedEffects().isFailed());
+
+ auto result = mWrapper->getSupportedEffects();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(supportedEffects, result.value());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestGetSupportedEffectsCachesResult) {
+ std::vector<Effect> supportedEffects;
+ supportedEffects.push_back(Effect::CLICK);
+ supportedEffects.push_back(Effect::TICK);
+
+ EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getSupportedEffects();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(supportedEffects, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestPerformEffect) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<3>(1000), TriggerCallbackInArg2(), Return(Status())));
+ EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(1000ms, result.value());
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::POP, EffectStrength::MEDIUM, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperAidlTest, TestPerformComposedEffect) {
+ std::vector<CompositeEffect> emptyEffects, singleEffect, multipleEffects;
+ singleEffect.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::CLICK, 10ms, 0.0f));
+ multipleEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::SPIN, 100ms, 0.5f));
+ multipleEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::THUD, 1000ms, 1.0f));
+
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), compose(Eq(emptyEffects), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+ EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+ EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performComposedEffect(emptyEffects, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ result = mWrapper->performComposedEffect(singleEffect, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+
+ result = mWrapper->performComposedEffect(multipleEffects, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(1, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
new file mode 100644
index 0000000..7f1016f
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
@@ -0,0 +1,318 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapperHidlV1_0Test"
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <thread>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::CompositePrimitive;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+using android::hardware::vibrator::IVibrator;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibratorV1_0 : public V1_0::IVibrator {
+public:
+ MOCK_METHOD(hardware::Return<void>, ping, (), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, on, (uint32_t timeoutMs), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, off, (), (override));
+ MOCK_METHOD(hardware::Return<bool>, supportsAmplitudeControl, (), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, setAmplitude, (uint8_t amplitude), (override));
+ MOCK_METHOD(hardware::Return<void>, perform,
+ (V1_0::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalWrapperHidlV1_0Test : public Test {
+public:
+ void SetUp() override {
+ mMockHal = new StrictMock<MockIVibratorV1_0>();
+ mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_0>(mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibratorV1_0>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestPing) {
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(2))
+ .WillOnce([]() { return hardware::Return<void>(); })
+ .WillRepeatedly([]() {
+ return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+ });
+
+ ASSERT_TRUE(mWrapper->ping().isOk());
+ ASSERT_TRUE(mWrapper->ping().isFailed());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestOn) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(1))))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](uint32_t) { return hardware::Return<V1_0::Status>(V1_0::Status::OK); });
+ EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(10))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint32_t) {
+ return hardware::Return<V1_0::Status>(V1_0::Status::UNSUPPORTED_OPERATION);
+ });
+ EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(100))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint32_t) {
+ return hardware::Return<V1_0::Status>(V1_0::Status::BAD_VALUE);
+ });
+ EXPECT_CALL(*mMockHal.get(), on(Eq(static_cast<uint32_t>(1000))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint32_t) {
+ return hardware::Return<V1_0::Status>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(1ms, callback).isOk());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(10ms, callback).isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(100ms, callback).isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->on(1000ms, callback).isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestOff) {
+ EXPECT_CALL(*mMockHal.get(), off())
+ .Times(Exactly(4))
+ .WillOnce([]() { return hardware::Return<V1_0::Status>(V1_0::Status::OK); })
+ .WillOnce([]() {
+ return hardware::Return<V1_0::Status>(V1_0::Status::UNSUPPORTED_OPERATION);
+ })
+ .WillOnce([]() { return hardware::Return<V1_0::Status>(V1_0::Status::BAD_VALUE); })
+ .WillRepeatedly([]() {
+ return hardware::Return<V1_0::Status>(hardware::Status::fromExceptionCode(-1));
+ });
+
+ ASSERT_TRUE(mWrapper->off().isOk());
+ ASSERT_TRUE(mWrapper->off().isUnsupported());
+ ASSERT_TRUE(mWrapper->off().isFailed());
+ ASSERT_TRUE(mWrapper->off().isFailed());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestSetAmplitude) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(static_cast<uint8_t>(1)))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](uint8_t) { return hardware::Return<V1_0::Status>(V1_0::Status::OK); });
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(static_cast<uint8_t>(2))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint8_t) {
+ return hardware::Return<V1_0::Status>(V1_0::Status::UNSUPPORTED_OPERATION);
+ });
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(static_cast<uint8_t>(3))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint8_t) {
+ return hardware::Return<V1_0::Status>(V1_0::Status::BAD_VALUE);
+ });
+ EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(static_cast<uint8_t>(4))))
+ .Times(Exactly(1))
+ .WillRepeatedly([](uint8_t) {
+ return hardware::Return<V1_0::Status>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ ASSERT_TRUE(mWrapper->setAmplitude(1).isOk());
+ ASSERT_TRUE(mWrapper->setAmplitude(2).isUnsupported());
+ ASSERT_TRUE(mWrapper->setAmplitude(3).isFailed());
+ ASSERT_TRUE(mWrapper->setAmplitude(4).isFailed());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestSetExternalControlUnsupported) {
+ ASSERT_TRUE(mWrapper->setExternalControl(true).isUnsupported());
+ ASSERT_TRUE(mWrapper->setExternalControl(false).isUnsupported());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestAlwaysOnEnableUnsupported) {
+ ASSERT_TRUE(mWrapper->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT).isUnsupported());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestAlwaysOnDisableUnsupported) {
+ ASSERT_TRUE(mWrapper->alwaysOnDisable(1).isUnsupported());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetCapabilitiesDoesNotCacheFailedResult) {
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(2))
+ .WillOnce([]() {
+ return hardware::Return<bool>(hardware::Status::fromExceptionCode(-1));
+ })
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+
+ ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetCapabilitiesWithoutAmplitudeControl) {
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl()).Times(Exactly(1)).WillRepeatedly([]() {
+ return hardware::Return<bool>(false);
+ });
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::NONE, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetCapabilitiesCachesResult) {
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl()).Times(Exactly(1)).WillRepeatedly([]() {
+ return hardware::Return<bool>(true);
+ });
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetSupportedEffectsUnsupported) {
+ ASSERT_TRUE(mWrapper->getSupportedEffects().isUnsupported());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformEffect) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_0::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::MEDIUM), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_0::perform_cb cb) {
+ cb(V1_0::Status::UNSUPPORTED_OPERATION, 10);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::STRONG), _))
+ .Times(Exactly(2))
+ .WillOnce([](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_0::perform_cb cb) {
+ cb(V1_0::Status::BAD_VALUE, 10);
+ return hardware::Return<void>();
+ })
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_0::perform_cb) {
+ return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::CLICK, EffectStrength::MEDIUM, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::CLICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::CLICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformEffectUnsupported) {
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ // Using TICK that is only available in v1.1
+ auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformComposedEffectUnsupported) {
+ std::vector<CompositeEffect> emptyEffects, singleEffect, multipleEffects;
+ singleEffect.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::CLICK, 10ms, 0.0f));
+ multipleEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::SPIN, 100ms, 0.5f));
+ multipleEffects.push_back(
+ vibrator::TestFactory::createCompositeEffect(CompositePrimitive::THUD, 1000ms, 1.0f));
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->performComposedEffect(singleEffect, callback).isUnsupported());
+ ASSERT_TRUE(mWrapper->performComposedEffect(multipleEffects, callback).isUnsupported());
+
+ // No callback is triggered.
+ ASSERT_EQ(0, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
new file mode 100644
index 0000000..d0531e6
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapperHidlV1_1Test"
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibratorV1_1 : public V1_1::IVibrator {
+public:
+ MOCK_METHOD(hardware::Return<V1_0::Status>, on, (uint32_t timeoutMs), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, off, (), (override));
+ MOCK_METHOD(hardware::Return<bool>, supportsAmplitudeControl, (), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, setAmplitude, (uint8_t amplitude), (override));
+ MOCK_METHOD(hardware::Return<void>, perform,
+ (V1_0::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_1,
+ (V1_1::Effect_1_1 effect, V1_0::EffectStrength strength, perform_cb cb),
+ (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalWrapperHidlV1_1Test : public Test {
+public:
+ void SetUp() override {
+ mMockHal = new StrictMock<MockIVibratorV1_1>();
+ mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_1>(mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibratorV1_1>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectV1_0) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_1::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectV1_1) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly([](V1_1::Effect_1_1, V1_0::EffectStrength,
+ MockIVibratorV1_1::perform_cb cb) {
+ cb(V1_0::Status::OK, 10);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::MEDIUM), _))
+ .Times(Exactly(1))
+ .WillRepeatedly([](V1_1::Effect_1_1, V1_0::EffectStrength,
+ MockIVibratorV1_1::perform_cb cb) {
+ cb(V1_0::Status::UNSUPPORTED_OPERATION, 0);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::STRONG), _))
+ .Times(Exactly(2))
+ .WillOnce([](V1_1::Effect_1_1, V1_0::EffectStrength,
+ MockIVibratorV1_1::perform_cb cb) {
+ cb(V1_0::Status::BAD_VALUE, 0);
+ return hardware::Return<void>();
+ })
+ .WillRepeatedly(
+ [](V1_1::Effect_1_1, V1_0::EffectStrength, MockIVibratorV1_1::perform_cb) {
+ return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(10ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TICK, EffectStrength::MEDIUM, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_1Test, TestPerformEffectUnsupported) {
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ // Using THUD that is only available in v1.2
+ auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ ASSERT_EQ(0, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
new file mode 100644
index 0000000..5d2c269
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapperHidlV1_2Test"
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+namespace V1_2 = android::hardware::vibrator::V1_2;
+
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibratorV1_2 : public V1_2::IVibrator {
+public:
+ MOCK_METHOD(hardware::Return<V1_0::Status>, on, (uint32_t timeoutMs), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, off, (), (override));
+ MOCK_METHOD(hardware::Return<bool>, supportsAmplitudeControl, (), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, setAmplitude, (uint8_t amplitude), (override));
+ MOCK_METHOD(hardware::Return<void>, perform,
+ (V1_0::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_1,
+ (V1_1::Effect_1_1 effect, V1_0::EffectStrength strength, perform_cb cb),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_2,
+ (V1_2::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalWrapperHidlV1_2Test : public Test {
+public:
+ void SetUp() override {
+ mMockHal = new StrictMock<MockIVibratorV1_2>();
+ mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_2>(mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibratorV1_2>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_0) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_1) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_1::Effect_1_1, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectV1_2) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::MEDIUM), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+ cb(V1_0::Status::UNSUPPORTED_OPERATION, 10);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::STRONG), _))
+ .Times(Exactly(2))
+ .WillOnce([](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb cb) {
+ cb(V1_0::Status::BAD_VALUE, 10);
+ return hardware::Return<void>();
+ })
+ .WillRepeatedly(
+ [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_2::perform_cb) {
+ return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::THUD, EffectStrength::MEDIUM, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::THUD, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_2Test, TestPerformEffectUnsupported) {
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ // Using TEXTURE_TICK that is only available in v1.3
+ auto result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ ASSERT_EQ(0, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
new file mode 100644
index 0000000..a799257
--- /dev/null
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
@@ -0,0 +1,377 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorHalWrapperHidlV1_3Test"
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+#include <thread>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+#include "test_utils.h"
+
+namespace V1_0 = android::hardware::vibrator::V1_0;
+namespace V1_1 = android::hardware::vibrator::V1_1;
+namespace V1_2 = android::hardware::vibrator::V1_2;
+namespace V1_3 = android::hardware::vibrator::V1_3;
+
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+using android::hardware::vibrator::IVibrator;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibratorV1_3 : public V1_3::IVibrator {
+public:
+ MOCK_METHOD(hardware::Return<V1_0::Status>, on, (uint32_t timeoutMs), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, off, (), (override));
+ MOCK_METHOD(hardware::Return<bool>, supportsAmplitudeControl, (), (override));
+ MOCK_METHOD(hardware::Return<bool>, supportsExternalControl, (), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, setAmplitude, (uint8_t amplitude), (override));
+ MOCK_METHOD(hardware::Return<V1_0::Status>, setExternalControl, (bool enabled), (override));
+ MOCK_METHOD(hardware::Return<void>, perform,
+ (V1_0::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_1,
+ (V1_1::Effect_1_1 effect, V1_0::EffectStrength strength, perform_cb cb),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_2,
+ (V1_2::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+ MOCK_METHOD(hardware::Return<void>, perform_1_3,
+ (V1_3::Effect effect, V1_0::EffectStrength strength, perform_cb cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorHalWrapperHidlV1_3Test : public Test {
+public:
+ void SetUp() override {
+ mMockHal = new StrictMock<MockIVibratorV1_3>();
+ mWrapper = std::make_unique<vibrator::HidlHalWrapperV1_3>(mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibratorV1_3>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestSetExternalControl) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true)))
+ .Times(Exactly(2))
+ .WillOnce([]() { return hardware::Return<V1_0::Status>(V1_0::Status::OK); })
+ .WillRepeatedly([]() {
+ return hardware::Return<V1_0::Status>(V1_0::Status::UNSUPPORTED_OPERATION);
+ });
+ EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(false)))
+ .Times(Exactly(2))
+ .WillOnce([]() { return hardware::Return<V1_0::Status>(V1_0::Status::BAD_VALUE); })
+ .WillRepeatedly([]() {
+ return hardware::Return<V1_0::Status>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ ASSERT_TRUE(mWrapper->setExternalControl(true).isOk());
+ ASSERT_TRUE(mWrapper->setExternalControl(true).isUnsupported());
+ ASSERT_TRUE(mWrapper->setExternalControl(false).isFailed());
+ ASSERT_TRUE(mWrapper->setExternalControl(false).isFailed());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesSuccessful) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(true);
+ });
+ }
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL | vibrator::Capabilities::EXTERNAL_CONTROL,
+ result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesOnlyAmplitudeControl) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(true);
+ });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(false);
+ });
+ }
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesOnlyExternalControl) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(false);
+ });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(true);
+ });
+ }
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::EXTERNAL_CONTROL, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesNone) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(false); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(false);
+ });
+ }
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::NONE, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesFailed) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() {
+ return hardware::Return<bool>(hardware::Status::fromExceptionCode(-1));
+ });
+
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() {
+ return hardware::Return<bool>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+ ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesCachesResult) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl()).Times(Exactly(1)).WillOnce([]() {
+ return hardware::Return<bool>(false);
+ });
+ }
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestGetCapabilitiesDoesNotCacheFailedResult) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() {
+ return hardware::Return<bool>(hardware::Status::fromExceptionCode(-1));
+ });
+
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() {
+ return hardware::Return<bool>(hardware::Status::fromExceptionCode(-1));
+ });
+
+ EXPECT_CALL(*mMockHal.get(), supportsAmplitudeControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(true); });
+ EXPECT_CALL(*mMockHal.get(), supportsExternalControl())
+ .Times(Exactly(1))
+ .WillRepeatedly([]() { return hardware::Return<bool>(false); });
+ }
+
+ // Call to supportsAmplitudeControl failed.
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isFailed());
+
+ // Call to supportsExternalControl failed.
+ result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isFailed());
+
+ // Returns successful result from third call.
+ result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+
+ // Returns cached successful result.
+ result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::Capabilities::AMPLITUDE_CONTROL, result.value());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_0) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform(Eq(V1_0::Effect::CLICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_0::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::CLICK, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_1) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_1(Eq(V1_1::Effect_1_1::TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_1::Effect_1_1, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::TICK, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_2) {
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_2(Eq(V1_2::Effect::THUD), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_2::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::OK, 100);
+ return hardware::Return<void>();
+ });
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+ auto result = mWrapper->performEffect(Effect::THUD, EffectStrength::LIGHT, callback);
+
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(100ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorHalWrapperHidlV1_3Test, TestPerformEffectV1_3) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_3(Eq(V1_3::Effect::TEXTURE_TICK), Eq(V1_0::EffectStrength::LIGHT), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_3::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::OK, 10);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_3(Eq(V1_3::Effect::TEXTURE_TICK), Eq(V1_0::EffectStrength::MEDIUM),
+ _))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ [](V1_3::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::UNSUPPORTED_OPERATION, 0);
+ return hardware::Return<void>();
+ });
+ EXPECT_CALL(*mMockHal.get(),
+ perform_1_3(Eq(V1_3::Effect::TEXTURE_TICK), Eq(V1_0::EffectStrength::STRONG),
+ _))
+ .Times(Exactly(2))
+ .WillOnce([](V1_3::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb cb) {
+ cb(V1_0::Status::BAD_VALUE, 0);
+ return hardware::Return<void>();
+ })
+ .WillRepeatedly(
+ [](V1_3::Effect, V1_0::EffectStrength, MockIVibratorV1_3::perform_cb) {
+ return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+ });
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ auto result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::LIGHT, callback);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(10ms, result.value());
+ // TODO(b/153418251): check callback will be triggered once implemented
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::MEDIUM, callback);
+ ASSERT_TRUE(result.isUnsupported());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+
+ result = mWrapper->performEffect(Effect::TEXTURE_TICK, EffectStrength::STRONG, callback);
+ ASSERT_TRUE(result.isFailed());
+ // Callback not triggered
+ ASSERT_EQ(0, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
new file mode 100644
index 0000000..fc9b364
--- /dev/null
+++ b/services/vibratorservice/test/test_utils.h
@@ -0,0 +1,55 @@
+/*
+ * 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 VIBRATORSERVICE_UNITTEST_UTIL_H_
+#define VIBRATORSERVICE_UNITTEST_UTIL_H_
+
+#include <android/hardware/vibrator/IVibrator.h>
+
+#include <vibratorservice/VibratorHalWrapper.h>
+
+namespace android {
+
+namespace vibrator {
+
+using ::android::hardware::vibrator::CompositeEffect;
+using ::android::hardware::vibrator::CompositePrimitive;
+
+class TestFactory {
+public:
+ static CompositeEffect createCompositeEffect(CompositePrimitive primitive,
+ std::chrono::milliseconds delay, float scale) {
+ CompositeEffect effect;
+ effect.primitive = primitive;
+ effect.delayMs = delay.count();
+ effect.scale = scale;
+ return effect;
+ }
+
+ static std::function<void()> createCountingCallback(int32_t* counter) {
+ return [counter]() { *counter += 1; };
+ }
+
+private:
+ TestFactory() = delete;
+ ~TestFactory() = delete;
+};
+
+} // namespace vibrator
+
+} // namespace android
+
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
\ No newline at end of file
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index 37b5368..d9a9427 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -621,6 +621,7 @@
// global functions
if (instance == VK_NULL_HANDLE) {
if (strcmp(pName, "vkCreateInstance") == 0) return reinterpret_cast<PFN_vkVoidFunction>(CreateInstance);
+ if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr);
if (strcmp(pName, "vkEnumerateInstanceVersion") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceVersion);
if (strcmp(pName, "vkEnumerateInstanceLayerProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceLayerProperties);
if (strcmp(pName, "vkEnumerateInstanceExtensionProperties") == 0) return reinterpret_cast<PFN_vkVoidFunction>(EnumerateInstanceExtensionProperties);
diff --git a/vulkan/scripts/api_generator.py b/vulkan/scripts/api_generator.py
index 7c39075..be24172 100644
--- a/vulkan/scripts/api_generator.py
+++ b/vulkan/scripts/api_generator.py
@@ -152,7 +152,9 @@
if (instance == VK_NULL_HANDLE) {\n""")
for cmd in gencom.command_list:
- if gencom.is_globally_dispatched(cmd):
+ # vkGetInstanceProcAddr(nullptr, "vkGetInstanceProcAddr") is effectively
+ # globally dispatched
+ if gencom.is_globally_dispatched(cmd) or cmd == 'vkGetInstanceProcAddr':
f.write(gencom.indent(2) +
'if (strcmp(pName, \"' + cmd +
'\") == 0) return reinterpret_cast<PFN_vkVoidFunction>(' +
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index cf370fa..ef0719d 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -22,7 +22,7 @@
import xml.etree.ElementTree as element_tree
# Extensions unsupported on Android.
-_BLACKLISTED_EXTENSIONS = [
+_BLOCKED_EXTENSIONS = [
'VK_EXT_acquire_xlib_display',
'VK_EXT_direct_mode_display',
'VK_EXT_display_control',
@@ -192,7 +192,7 @@
if cmd not in extension_dict:
return True
else:
- if extension_dict[cmd] not in _BLACKLISTED_EXTENSIONS:
+ if extension_dict[cmd] not in _BLOCKED_EXTENSIONS:
return True
return False