Merge "Add timestamp smoothening for Bluetooth mice and touchpads"
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index 7e89220..bb3faaf 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
package android.view;
-parcelable Surface cpp_header "gui/view/Surface.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index ebb7891..34ea759 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -1956,7 +1956,7 @@
join_fds(context_input_fds), swap_fd.get(), instruction_set, compiler_filter,
debuggable, boot_complete, for_restore, target_sdk_version,
enable_hidden_api_checks, generate_compact_dex, use_jitzygote_image,
- compilation_reason);
+ background_job_compile, compilation_reason);
bool cancelled = false;
pid_t pid = dexopt_status_->check_cancellation_and_fork(&cancelled);
diff --git a/cmds/installd/run_dex2oat.cpp b/cmds/installd/run_dex2oat.cpp
index 51c4589..4221a3a 100644
--- a/cmds/installd/run_dex2oat.cpp
+++ b/cmds/installd/run_dex2oat.cpp
@@ -81,6 +81,7 @@
bool enable_hidden_api_checks,
bool generate_compact_dex,
bool use_jitzygote,
+ bool background_job_compile,
const char* compilation_reason) {
PrepareBootImageFlags(use_jitzygote);
@@ -92,7 +93,8 @@
debuggable, target_sdk_version, enable_hidden_api_checks,
generate_compact_dex, compilation_reason);
- PrepareCompilerRuntimeAndPerfConfigFlags(post_bootcomplete, for_restore);
+ PrepareCompilerRuntimeAndPerfConfigFlags(post_bootcomplete, for_restore,
+ background_job_compile);
const std::string dex2oat_flags = GetProperty("dalvik.vm.dex2oat-flags", "");
std::vector<std::string> dex2oat_flags_args = SplitBySpaces(dex2oat_flags);
@@ -296,7 +298,8 @@
}
void RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete,
- bool for_restore) {
+ bool for_restore,
+ bool background_job_compile) {
// CPU set
{
std::string cpu_set_format = "--cpu-set=%s";
@@ -306,7 +309,12 @@
"dalvik.vm.restore-dex2oat-cpu-set",
"dalvik.vm.dex2oat-cpu-set",
cpu_set_format)
- : MapPropertyToArg("dalvik.vm.dex2oat-cpu-set", cpu_set_format))
+ : (background_job_compile
+ ? MapPropertyToArgWithBackup(
+ "dalvik.vm.background-dex2oat-cpu-set",
+ "dalvik.vm.dex2oat-cpu-set",
+ cpu_set_format)
+ : MapPropertyToArg("dalvik.vm.dex2oat-cpu-set", cpu_set_format)))
: MapPropertyToArg("dalvik.vm.boot-dex2oat-cpu-set", cpu_set_format);
AddArg(dex2oat_cpu_set_arg);
}
@@ -320,7 +328,12 @@
"dalvik.vm.restore-dex2oat-threads",
"dalvik.vm.dex2oat-threads",
threads_format)
- : MapPropertyToArg("dalvik.vm.dex2oat-threads", threads_format))
+ : (background_job_compile
+ ? MapPropertyToArgWithBackup(
+ "dalvik.vm.background-dex2oat-threads",
+ "dalvik.vm.dex2oat-threads",
+ threads_format)
+ : MapPropertyToArg("dalvik.vm.dex2oat-threads", threads_format)))
: MapPropertyToArg("dalvik.vm.boot-dex2oat-threads", threads_format);
AddArg(dex2oat_threads_arg);
}
diff --git a/cmds/installd/run_dex2oat.h b/cmds/installd/run_dex2oat.h
index 559244f..c13e1f1 100644
--- a/cmds/installd/run_dex2oat.h
+++ b/cmds/installd/run_dex2oat.h
@@ -51,6 +51,7 @@
bool enable_hidden_api_checks,
bool generate_compact_dex,
bool use_jitzygote,
+ bool background_job_compile,
const char* compilation_reason);
void Exec(int exit_code);
@@ -76,7 +77,9 @@
bool enable_hidden_api_checks,
bool generate_compact_dex,
const char* compilation_reason);
- void PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete, bool for_restore);
+ void PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete,
+ bool for_restore,
+ bool background_job_compile);
virtual std::string GetProperty(const std::string& key, const std::string& default_value);
virtual bool GetBoolProperty(const std::string& key, bool default_value);
diff --git a/cmds/installd/run_dex2oat_test.cpp b/cmds/installd/run_dex2oat_test.cpp
index 2a8135a..304ba7b 100644
--- a/cmds/installd/run_dex2oat_test.cpp
+++ b/cmds/installd/run_dex2oat_test.cpp
@@ -115,6 +115,7 @@
bool enable_hidden_api_checks = false;
bool generate_compact_dex = true;
bool use_jitzygote = false;
+ bool background_job_compile = false;
const char* compilation_reason = nullptr;
};
@@ -259,6 +260,7 @@
args->enable_hidden_api_checks,
args->generate_compact_dex,
args->use_jitzygote,
+ args->background_job_compile,
args->compilation_reason);
runner.Exec(/*exit_code=*/ 0);
}
@@ -375,6 +377,30 @@
VerifyExpectedFlags();
}
+TEST_F(RunDex2OatTest, CpuSetPostBootCompleteBackground) {
+ setSystemProperty("dalvik.vm.background-dex2oat-cpu-set", "1,3");
+ setSystemProperty("dalvik.vm.dex2oat-cpu-set", "1,2");
+ auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+ args->post_bootcomplete = true;
+ args->background_job_compile = true;
+ CallRunDex2Oat(std::move(args));
+
+ SetExpectedFlagUsed("--cpu-set", "=1,3");
+ VerifyExpectedFlags();
+}
+
+TEST_F(RunDex2OatTest, CpuSetPostBootCompleteBackground_Backup) {
+ setSystemProperty("dalvik.vm.background-dex2oat-cpu-set", "");
+ setSystemProperty("dalvik.vm.dex2oat-cpu-set", "1,2");
+ auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+ args->post_bootcomplete = true;
+ args->background_job_compile = true;
+ CallRunDex2Oat(std::move(args));
+
+ SetExpectedFlagUsed("--cpu-set", "=1,2");
+ VerifyExpectedFlags();
+}
+
TEST_F(RunDex2OatTest, CpuSetPostBootCompleteForRestore) {
setSystemProperty("dalvik.vm.restore-dex2oat-cpu-set", "1,2");
setSystemProperty("dalvik.vm.dex2oat-cpu-set", "2,3");
@@ -481,6 +507,30 @@
VerifyExpectedFlags();
}
+TEST_F(RunDex2OatTest, ThreadsPostBootCompleteBackground) {
+ setSystemProperty("dalvik.vm.background-dex2oat-threads", "2");
+ setSystemProperty("dalvik.vm.dex2oat-threads", "3");
+ auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+ args->post_bootcomplete = true;
+ args->background_job_compile = true;
+ CallRunDex2Oat(std::move(args));
+
+ SetExpectedFlagUsed("-j", "2");
+ VerifyExpectedFlags();
+}
+
+TEST_F(RunDex2OatTest, ThreadsPostBootCompleteBackground_Backup) {
+ setSystemProperty("dalvik.vm.background-dex2oat-threads", "");
+ setSystemProperty("dalvik.vm.dex2oat-threads", "3");
+ auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+ args->post_bootcomplete = true;
+ args->background_job_compile = true;
+ CallRunDex2Oat(std::move(args));
+
+ SetExpectedFlagUsed("-j", "3");
+ VerifyExpectedFlags();
+}
+
TEST_F(RunDex2OatTest, ThreadsPostBootCompleteForRestore) {
setSystemProperty("dalvik.vm.restore-dex2oat-threads", "4");
setSystemProperty("dalvik.vm.dex2oat-threads", "5");
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 2684f04..2ae61b9 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -612,7 +612,8 @@
}
void ServiceManager::tryStartService(const std::string& name) {
- ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service",
+ ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service. (if it's not "
+ "configured to be a lazy service, it may be stuck starting or still starting).",
name.c_str());
std::thread([=] {
diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
new file mode 100644
index 0000000..d0b594c
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132580097" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml
new file mode 100644
index 0000000..6ae248a
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+ tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.vulkan.deqp.level" version="132580097" />
+</permissions>
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 5fa47f6..eed6b33 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -88,6 +88,36 @@
typedef struct APerformanceHintSession APerformanceHintSession;
/**
+ * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes
+ * in the mode or workload.
+ */
+enum SessionHint {
+ /**
+ * This hint indicates a sudden increase in CPU workload intensity. It means
+ * that this hint session needs extra CPU resources immediately to meet the
+ * target duration for the current work cycle.
+ */
+ CPU_LOAD_UP = 0,
+ /**
+ * This hint indicates a decrease in CPU workload intensity. It means that
+ * this hint session can reduce CPU resources and still meet the target duration.
+ */
+ CPU_LOAD_DOWN = 1,
+ /*
+ * This hint indicates an upcoming CPU workload that is completely changed and
+ * unknown. It means that the hint session should reset CPU resources to a known
+ * baseline to prepare for an arbitrary load, and must wake up if inactive.
+ */
+ CPU_LOAD_RESET = 2,
+ /*
+ * This hint indicates that the most recent CPU workload is resuming after a
+ * period of inactivity. It means that the hint session should allocate similar
+ * CPU resources to what was used previously, and must wake up if inactive.
+ */
+ CPU_LOAD_RESUME = 3,
+};
+
+/**
* Acquire an instance of the performance hint manager.
*
* @return manager instance on success, nullptr on failure.
@@ -159,6 +189,17 @@
void APerformanceHint_closeSession(
APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+/**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param session The performance hint session instance to update.
+ * @param hint The hint to send to the session.
+ * @return 0 on success
+ * EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_sendHint(
+ APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__);
+
__END_DECLS
#endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index fccc0af..d6937c2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -30,11 +30,11 @@
#include <android/binder_internal_logging.h>
#include <android/binder_parcel.h>
#include <android/binder_status.h>
-
#include <assert.h>
-
#include <unistd.h>
+
#include <cstddef>
+#include <iostream>
#include <string>
namespace ndk {
@@ -270,14 +270,19 @@
std::string getDescription() const {
#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
if (__builtin_available(android 30, *)) {
-#else
- if (__ANDROID_API__ >= 30) {
#endif
+
+#if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 30
const char* cStr = AStatus_getDescription(get());
std::string ret = cStr;
AStatus_deleteDescription(cStr);
return ret;
+#endif
+
+#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
}
+#endif
+
binder_exception_t exception = getExceptionCode();
std::string desc = std::to_string(exception);
if (exception == EX_SERVICE_SPECIFIC) {
@@ -315,6 +320,11 @@
}
};
+static inline std::ostream& operator<<(std::ostream& os, const ScopedAStatus& status) {
+ return os << status.getDescription();
+ return os;
+}
+
/**
* Convenience wrapper. See AIBinder_DeathRecipient.
*/
diff --git a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
index c1f2620..caee471 100644
--- a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
@@ -41,68 +41,34 @@
if (_status != STATUS_OK) return _status; \
} while (false)
+// AParcelableHolder has been introduced in 31.
+#if __ANDROID_API__ >= 31
class AParcelableHolder {
public:
AParcelableHolder() = delete;
explicit AParcelableHolder(parcelable_stability_t stability)
: mParcel(AParcel_create()), mStability(stability) {}
-#if __ANDROID_API__ >= 31
AParcelableHolder(const AParcelableHolder& other)
: mParcel(AParcel_create()), mStability(other.mStability) {
- // AParcelableHolder has been introduced in 31.
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- AParcel_appendFrom(other.mParcel.get(), this->mParcel.get(), 0,
- AParcel_getDataSize(other.mParcel.get()));
- } else {
- syslog(LOG_ERR,
- "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
- }
+ AParcel_appendFrom(other.mParcel.get(), this->mParcel.get(), 0,
+ AParcel_getDataSize(other.mParcel.get()));
}
-#endif
AParcelableHolder(AParcelableHolder&& other) = default;
virtual ~AParcelableHolder() = default;
binder_status_t writeToParcel(AParcel* parcel) const {
RETURN_ON_FAILURE(AParcel_writeInt32(parcel, static_cast<int32_t>(this->mStability)));
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- int32_t size = AParcel_getDataSize(this->mParcel.get());
- RETURN_ON_FAILURE(AParcel_writeInt32(parcel, size));
- } else {
- return STATUS_INVALID_OPERATION;
- }
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- int32_t size = AParcel_getDataSize(this->mParcel.get());
- RETURN_ON_FAILURE(AParcel_appendFrom(this->mParcel.get(), parcel, 0, size));
- } else {
- return STATUS_INVALID_OPERATION;
- }
+ int32_t size = AParcel_getDataSize(this->mParcel.get());
+ RETURN_ON_FAILURE(AParcel_writeInt32(parcel, size));
+ size = AParcel_getDataSize(this->mParcel.get());
+ RETURN_ON_FAILURE(AParcel_appendFrom(this->mParcel.get(), parcel, 0, size));
return STATUS_OK;
}
binder_status_t readFromParcel(const AParcel* parcel) {
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- AParcel_reset(mParcel.get());
- } else {
- return STATUS_INVALID_OPERATION;
- }
+ AParcel_reset(mParcel.get());
parcelable_stability_t wireStability;
RETURN_ON_FAILURE(AParcel_readInt32(parcel, &wireStability));
@@ -123,15 +89,7 @@
return STATUS_BAD_VALUE;
}
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- status = AParcel_appendFrom(parcel, mParcel.get(), dataStartPos, dataSize);
- } else {
- status = STATUS_INVALID_OPERATION;
- }
+ status = AParcel_appendFrom(parcel, mParcel.get(), dataStartPos, dataSize);
if (status != STATUS_OK) {
return status;
}
@@ -143,15 +101,7 @@
if (this->mStability > T::_aidl_stability) {
return STATUS_BAD_VALUE;
}
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- AParcel_reset(mParcel.get());
- } else {
- return STATUS_INVALID_OPERATION;
- }
+ AParcel_reset(mParcel.get());
AParcel_writeString(mParcel.get(), T::descriptor, strlen(T::descriptor));
p.writeToParcel(mParcel.get());
return STATUS_OK;
@@ -161,17 +111,9 @@
binder_status_t getParcelable(std::optional<T>* ret) const {
const std::string parcelableDesc(T::descriptor);
AParcel_setDataPosition(mParcel.get(), 0);
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- if (AParcel_getDataSize(mParcel.get()) == 0) {
- *ret = std::nullopt;
- return STATUS_OK;
- }
- } else {
- return STATUS_INVALID_OPERATION;
+ if (AParcel_getDataSize(mParcel.get()) == 0) {
+ *ret = std::nullopt;
+ return STATUS_OK;
}
std::string parcelableDescInParcel;
binder_status_t status = AParcel_readString(mParcel.get(), &parcelableDescInParcel);
@@ -188,18 +130,7 @@
return STATUS_OK;
}
- void reset() {
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- AParcel_reset(mParcel.get());
- } else {
- syslog(LOG_ERR,
- "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
- }
- }
+ void reset() { AParcel_reset(mParcel.get()); }
inline bool operator!=(const AParcelableHolder& rhs) const { return this != &rhs; }
inline bool operator<(const AParcelableHolder& rhs) const { return this < &rhs; }
@@ -207,34 +138,23 @@
inline bool operator==(const AParcelableHolder& rhs) const { return this == &rhs; }
inline bool operator>(const AParcelableHolder& rhs) const { return this > &rhs; }
inline bool operator>=(const AParcelableHolder& rhs) const { return this >= &rhs; }
-#if __ANDROID_API__ >= 31
inline AParcelableHolder& operator=(const AParcelableHolder& rhs) {
- // AParcelableHolder has been introduced in 31.
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
- if (__builtin_available(android 31, *)) {
-#else
- if (__ANDROID_API__ >= 31) {
-#endif
- this->reset();
- if (this->mStability != rhs.mStability) {
- syslog(LOG_ERR, "AParcelableHolder stability mismatch: this %d rhs %d!",
- this->mStability, rhs.mStability);
- abort();
- }
- AParcel_appendFrom(rhs.mParcel.get(), this->mParcel.get(), 0,
- AParcel_getDataSize(rhs.mParcel.get()));
- } else {
- syslog(LOG_ERR,
- "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
+ this->reset();
+ if (this->mStability != rhs.mStability) {
+ syslog(LOG_ERR, "AParcelableHolder stability mismatch: this %d rhs %d!",
+ this->mStability, rhs.mStability);
+ abort();
}
+ AParcel_appendFrom(rhs.mParcel.get(), this->mParcel.get(), 0,
+ AParcel_getDataSize(rhs.mParcel.get()));
return *this;
}
-#endif
private:
mutable ndk::ScopedAParcel mParcel;
parcelable_stability_t mStability;
};
+#endif // __ANDROID_API__ >= 31
#undef RETURN_ON_FAILURE
} // namespace ndk
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index d7840ec..6a25db2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -136,8 +136,10 @@
template <typename _U>
static std::enable_if_t<
#ifdef HAS_NDK_INTERFACE
- std::is_base_of_v<::ndk::ICInterface, _U> ||
- std::is_same_v<::ndk::AParcelableHolder, _U>
+ std::is_base_of_v<::ndk::ICInterface, _U>
+#if __ANDROID_API__ >= 31
+ || std::is_same_v<::ndk::AParcelableHolder, _U>
+#endif
#else
std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
std::is_same_v<os::ParcelFileDescriptor, _U> ||
diff --git a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
index f3cd218..43b2cb8 100644
--- a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
+++ b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
@@ -106,7 +106,7 @@
std::string outString;
ScopedAStatus status = server->RepeatString("foo", &outString);
EXPECT_EQ(STATUS_OK, AStatus_getExceptionCode(status.get()))
- << serviceName << " " << status.getDescription();
+ << serviceName << " " << status;
EXPECT_EQ("foo", outString) << serviceName;
}
}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
new file mode 100644
index 0000000..28e0200
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
@@ -0,0 +1,25 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_fuzz {
+ name: "parcel_fuzzer_rs",
+ srcs: [
+ "parcel_fuzzer.rs",
+ ],
+ rustlibs: [
+ "libarbitrary",
+ "libnum_traits",
+ "libbinder_rs",
+ "libbinder_random_parcel_rs",
+ "binderReadParcelIface-rust",
+ ],
+
+ fuzz_config: {
+ cc: [
+ "waghpawan@google.com",
+ "smoreland@google.com",
+ ],
+ },
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
new file mode 100644
index 0000000..c5c7719
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+
+#[macro_use]
+extern crate libfuzzer_sys;
+
+mod read_utils;
+
+use crate::read_utils::READ_FUNCS;
+use binder::binder_impl::{
+ Binder, BorrowedParcel, IBinderInternal, Parcel, Stability, TransactionCode,
+};
+use binder::{
+ declare_binder_interface, BinderFeatures, Interface, Parcelable, ParcelableHolder, SpIBinder,
+ StatusCode,
+};
+use binder_random_parcel_rs::create_random_parcel;
+use libfuzzer_sys::arbitrary::Arbitrary;
+
+#[derive(Arbitrary, Debug)]
+enum ReadOperation {
+ SetDataPosition { pos: i32 },
+ GetDataSize,
+ ReadParcelableHolder { is_vintf: bool },
+ ReadBasicTypes { instructions: Vec<usize> },
+}
+
+#[derive(Arbitrary, Debug)]
+enum Operation<'a> {
+ Transact { code: u32, flag: u32, data: &'a [u8] },
+ Append { start: i32, len: i32, data1: &'a [u8], data2: &'a [u8], append_all: bool },
+ Read { read_operations: Vec<ReadOperation>, data: &'a [u8] },
+}
+
+/// Interface to fuzz transact with random parcel
+pub trait BinderTransactTest: Interface {}
+
+declare_binder_interface! {
+ BinderTransactTest["Binder_Transact_Test"] {
+ native: BnBinderTransactTest(on_transact),
+ proxy: BpBinderTransactTest,
+ }
+}
+
+impl BinderTransactTest for Binder<BnBinderTransactTest> {}
+
+impl BinderTransactTest for BpBinderTransactTest {}
+
+impl BinderTransactTest for () {}
+
+fn on_transact(
+ _service: &dyn BinderTransactTest,
+ _code: TransactionCode,
+ _parcel: &BorrowedParcel<'_>,
+ _reply: &mut BorrowedParcel<'_>,
+) -> Result<(), StatusCode> {
+ Err(StatusCode::UNKNOWN_ERROR)
+}
+
+fn do_transact(code: u32, data: &[u8], flag: u32) {
+ let p: Parcel = create_random_parcel(data);
+ let spibinder: Option<SpIBinder> =
+ Some(BnBinderTransactTest::new_binder((), BinderFeatures::default()).as_binder());
+ let _reply = spibinder.submit_transact(code, p, flag);
+}
+
+fn do_append_fuzz(start: i32, len: i32, data1: &[u8], data2: &[u8], append_all: bool) {
+ let mut p1 = create_random_parcel(data1);
+ let p2 = create_random_parcel(data2);
+
+ // Fuzz both append methods
+ if append_all {
+ match p1.append_all_from(&p2) {
+ Ok(result) => result,
+ Err(e) => {
+ println!("Error occurred while appending a parcel using append_all_from: {:?}", e)
+ }
+ }
+ } else {
+ match p1.append_from(&p2, start, len) {
+ Ok(result) => result,
+ Err(e) => {
+ println!("Error occurred while appending a parcel using append_from: {:?}", e)
+ }
+ }
+ };
+}
+
+fn do_read_fuzz(read_operations: Vec<ReadOperation>, data: &[u8]) {
+ let parcel = create_random_parcel(data);
+
+ for operation in read_operations {
+ match operation {
+ ReadOperation::SetDataPosition { pos } => {
+ unsafe {
+ // Safety: Safe if pos is less than current size of the parcel.
+ // It relies on C++ code for bound checks
+ match parcel.set_data_position(pos) {
+ Ok(result) => result,
+ Err(e) => println!("error occurred while setting data position: {:?}", e),
+ }
+ }
+ }
+
+ ReadOperation::GetDataSize => {
+ let data_size = parcel.get_data_size();
+ println!("data size from parcel: {:?}", data_size);
+ }
+
+ ReadOperation::ReadParcelableHolder { is_vintf } => {
+ let stability = if is_vintf { Stability::Vintf } else { Stability::Local };
+ let mut holder: ParcelableHolder = ParcelableHolder::new(stability);
+ match holder.read_from_parcel(parcel.borrowed_ref()) {
+ Ok(result) => result,
+ Err(err) => {
+ println!("error occurred while reading from parcel: {:?}", err)
+ }
+ }
+ }
+
+ ReadOperation::ReadBasicTypes { instructions } => {
+ for instruction in instructions.iter() {
+ let read_index = instruction % READ_FUNCS.len();
+ READ_FUNCS[read_index](parcel.borrowed_ref());
+ }
+ }
+ }
+ }
+}
+
+fuzz_target!(|operation: Operation| {
+ match operation {
+ Operation::Transact { code, flag, data } => {
+ do_transact(code, data, flag);
+ }
+
+ Operation::Append { start, len, data1, data2, append_all } => {
+ do_append_fuzz(start, len, data1, data2, append_all);
+ }
+
+ Operation::Read { read_operations, data } => {
+ do_read_fuzz(read_operations, data);
+ }
+ }
+});
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
index 6fe4fcd..43a3094 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
@@ -14,6 +14,8 @@
"--size_t-is-usize",
"--allowlist-function",
"createRandomParcel",
+ "--allowlist-function",
+ "fuzzRustService",
],
shared_libs: [
"libc++",
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
new file mode 100644
index 0000000..43e407c
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aidl_interface {
+ name: "testServiceInterface",
+ srcs: ["ITestService.aidl"],
+ unstable: true,
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_fuzz {
+ name: "example_service_fuzzer",
+ srcs: [
+ "service_fuzzer.rs",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ "libbinder_random_parcel_rs",
+ "testServiceInterface-rust",
+ ],
+ fuzz_config: {
+ cc: [
+ "waghpawan@google.com",
+ "smoreland@google.com",
+ ],
+ },
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl
new file mode 100644
index 0000000..8ce6558
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+interface ITestService {
+ boolean repeatData(boolean token);
+}
\ No newline at end of file
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
new file mode 100644
index 0000000..a427f28
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+#[macro_use]
+extern crate libfuzzer_sys;
+
+use binder::{self, BinderFeatures, Interface};
+use binder_random_parcel_rs::fuzz_service;
+use testServiceInterface::aidl::ITestService::{self, BnTestService};
+
+struct TestService;
+
+impl Interface for TestService {}
+
+impl ITestService::ITestService for TestService {
+ fn repeatData(&self, token: bool) -> binder::Result<bool> {
+ Ok(token)
+ }
+}
+
+fuzz_target!(|data: &[u8]| {
+ let service = BnTestService::new_binder(TestService, BinderFeatures::default());
+ fuzz_service(&mut service.as_binder(), data);
+});
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
index ee3b6f8..1bbd674 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
@@ -16,7 +16,8 @@
use binder::binder_impl::Parcel;
use binder::unstable_api::{AParcel, AsNative};
-use binder_random_parcel_bindgen::createRandomParcel;
+use binder::SpIBinder;
+use binder_random_parcel_bindgen::{createRandomParcel, fuzzRustService};
use std::os::raw::c_void;
/// This API creates a random parcel to be used by fuzzers
@@ -31,3 +32,13 @@
}
parcel
}
+
+/// This API automatically fuzzes provided service
+pub fn fuzz_service(binder: &mut SpIBinder, fuzzer_data: &[u8]) {
+ let ptr = binder.as_native_mut() as *mut c_void;
+ unsafe {
+ // Safety: `SpIBinder::as_native_mut` and `slice::as_ptr` always
+ // return valid pointers.
+ fuzzRustService(ptr, fuzzer_data.as_ptr(), fuzzer_data.len());
+ }
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
index 167a64e..831bd56 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
@@ -19,4 +19,7 @@
extern "C" {
// This API is used by rust to fill random parcel.
void createRandomParcel(void* aParcel, const uint8_t* data, size_t len);
+
+ // This API is used by fuzzers to automatically fuzz aidl services
+ void fuzzRustService(void* binder, const uint8_t* data, size_t len);
}
\ No newline at end of file
diff --git a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
new file mode 100644
index 0000000..d2bfde1
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use binder::binder_impl::BorrowedParcel;
+use binder::{ParcelFileDescriptor, Parcelable, SpIBinder};
+use binderReadParcelIface::aidl::EmptyParcelable::EmptyParcelable;
+use binderReadParcelIface::aidl::GenericDataParcelable::GenericDataParcelable;
+use binderReadParcelIface::aidl::SingleDataParcelable::SingleDataParcelable;
+
+macro_rules! read_parcel_interface {
+ ($data_type:ty) => {
+ |parcel: &BorrowedParcel<'_>| {
+ let _res = parcel.read::<$data_type>();
+ }
+ };
+}
+
+#[derive(Debug, Default)]
+pub struct SomeParcelable {
+ pub data: i32,
+}
+
+impl binder::Parcelable for SomeParcelable {
+ fn write_to_parcel(
+ &self,
+ parcel: &mut binder::binder_impl::BorrowedParcel,
+ ) -> std::result::Result<(), binder::StatusCode> {
+ parcel.sized_write(|subparcel| subparcel.write(&self.data))
+ }
+
+ fn read_from_parcel(
+ &mut self,
+ parcel: &binder::binder_impl::BorrowedParcel,
+ ) -> std::result::Result<(), binder::StatusCode> {
+ parcel.sized_read(|subparcel| match subparcel.read() {
+ Ok(result) => {
+ self.data = result;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ })
+ }
+}
+
+binder::impl_deserialize_for_parcelable!(SomeParcelable);
+
+pub const READ_FUNCS: &[fn(&BorrowedParcel<'_>)] = &[
+ //read basic types
+ read_parcel_interface!(bool),
+ read_parcel_interface!(i8),
+ read_parcel_interface!(i32),
+ read_parcel_interface!(i64),
+ read_parcel_interface!(f32),
+ read_parcel_interface!(f64),
+ read_parcel_interface!(u16),
+ read_parcel_interface!(u32),
+ read_parcel_interface!(u64),
+ read_parcel_interface!(String),
+ //read vec of basic types
+ read_parcel_interface!(Vec<i8>),
+ read_parcel_interface!(Vec<i32>),
+ read_parcel_interface!(Vec<i64>),
+ read_parcel_interface!(Vec<f32>),
+ read_parcel_interface!(Vec<f64>),
+ read_parcel_interface!(Vec<u16>),
+ read_parcel_interface!(Vec<u32>),
+ read_parcel_interface!(Vec<u64>),
+ read_parcel_interface!(Vec<String>),
+ read_parcel_interface!(Option<Vec<i8>>),
+ read_parcel_interface!(Option<Vec<i32>>),
+ read_parcel_interface!(Option<Vec<i64>>),
+ read_parcel_interface!(Option<Vec<f32>>),
+ read_parcel_interface!(Option<Vec<f64>>),
+ read_parcel_interface!(Option<Vec<u16>>),
+ read_parcel_interface!(Option<Vec<u32>>),
+ read_parcel_interface!(Option<Vec<u64>>),
+ read_parcel_interface!(Option<Vec<String>>),
+ read_parcel_interface!(ParcelFileDescriptor),
+ read_parcel_interface!(Vec<Option<ParcelFileDescriptor>>),
+ read_parcel_interface!(Option<Vec<ParcelFileDescriptor>>),
+ read_parcel_interface!(Option<Vec<Option<ParcelFileDescriptor>>>),
+ read_parcel_interface!(SpIBinder),
+ read_parcel_interface!(Vec<Option<SpIBinder>>),
+ read_parcel_interface!(Option<Vec<SpIBinder>>),
+ read_parcel_interface!(Option<Vec<Option<SpIBinder>>>),
+ read_parcel_interface!(SomeParcelable),
+ read_parcel_interface!(Vec<Option<SomeParcelable>>),
+ read_parcel_interface!(Option<Vec<SomeParcelable>>),
+ read_parcel_interface!(Option<Vec<Option<SomeParcelable>>>),
+ // Fuzz read_from_parcel for AIDL generated parcelables
+ |parcel| {
+ let mut empty_parcelable: EmptyParcelable = EmptyParcelable::default();
+ match empty_parcelable.read_from_parcel(parcel) {
+ Ok(result) => result,
+ Err(e) => {
+ println!("EmptyParcelable: error occurred while reading from a parcel: {:?}", e)
+ }
+ }
+ },
+ |parcel| {
+ let mut single_parcelable: SingleDataParcelable = SingleDataParcelable::default();
+ match single_parcelable.read_from_parcel(parcel) {
+ Ok(result) => result,
+ Err(e) => println!(
+ "SingleDataParcelable: error occurred while reading from a parcel: {:?}",
+ e
+ ),
+ }
+ },
+ |parcel| {
+ let mut generic_parcelable: GenericDataParcelable = GenericDataParcelable::default();
+ match generic_parcelable.read_from_parcel(parcel) {
+ Ok(result) => result,
+ Err(e) => println!(
+ "GenericDataParcelable: error occurred while reading from a parcel: {:?}",
+ e
+ ),
+ }
+ },
+];
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 92d132f..03e4a23 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -232,6 +232,7 @@
srcs: [
"binderRpcTest.cpp",
"binderRpcTestCommon.cpp",
+ "binderRpcUniversalTests.cpp",
],
test_suites: ["general-tests"],
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 7294305..004dea3 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -15,7 +15,6 @@
*/
#include <android-base/stringprintf.h>
-#include <gtest/gtest.h>
#include <chrono>
#include <cstdlib>
@@ -29,6 +28,7 @@
#include <sys/socket.h>
#include "binderRpcTestCommon.h"
+#include "binderRpcTestFixture.h"
using namespace std::chrono_literals;
using namespace std::placeholders;
@@ -44,37 +44,6 @@
constexpr bool kEnableSharedLibs = true;
#endif
-static_assert(RPC_WIRE_PROTOCOL_VERSION + 1 == RPC_WIRE_PROTOCOL_VERSION_NEXT ||
- RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
-
-TEST(BinderRpcParcel, EntireParcelFormatted) {
- Parcel p;
- p.writeInt32(3);
-
- EXPECT_DEATH(p.markForBinder(sp<BBinder>::make()), "format must be set before data is written");
-}
-
-TEST(BinderRpc, CannotUseNextWireVersion) {
- auto session = RpcSession::make();
- EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT));
- EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 1));
- EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 2));
- EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
-}
-
-TEST(BinderRpc, CanUseExperimentalWireVersion) {
- auto session = RpcSession::make();
- EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
-}
-
-using android::binder::Status;
-
-#define EXPECT_OK(status) \
- do { \
- Status stat = (status); \
- EXPECT_TRUE(stat.isOk()) << stat; \
- } while (false)
-
static std::string WaitStatusToString(int wstatus) {
if (WIFEXITED(wstatus)) {
return base::StringPrintf("exit status %d", WEXITSTATUS(wstatus));
@@ -92,7 +61,15 @@
class Process {
public:
- Process(Process&&) = default;
+ Process(Process&& other)
+ : mCustomExitStatusCheck(std::move(other.mCustomExitStatusCheck)),
+ mReadEnd(std::move(other.mReadEnd)),
+ mWriteEnd(std::move(other.mWriteEnd)) {
+ // The default move constructor doesn't clear mPid after moving it,
+ // which we need to do because the destructor checks for mPid!=0
+ mPid = other.mPid;
+ other.mPid = 0;
+ }
Process(const std::function<void(android::base::borrowed_fd /* writeEnd */,
android::base::borrowed_fd /* readEnd */)>& f) {
android::base::unique_fd childWriteEnd;
@@ -152,21 +129,17 @@
return vsockPort++;
}
-struct ProcessSession {
+// Destructors need to be defined, even if pure virtual
+ProcessSession::~ProcessSession() {}
+
+class LinuxProcessSession : public ProcessSession {
+public:
// reference to process hosting a socket server
Process host;
- struct SessionInfo {
- sp<RpcSession> session;
- sp<IBinder> root;
- };
-
- // client session objects associated with other process
- // each one represents a separate session
- std::vector<SessionInfo> sessions;
-
- ProcessSession(ProcessSession&&) = default;
- ~ProcessSession() {
+ LinuxProcessSession(LinuxProcessSession&&) = default;
+ LinuxProcessSession(Process&& host) : host(std::move(host)) {}
+ ~LinuxProcessSession() override {
for (auto& session : sessions) {
session.root = nullptr;
}
@@ -197,46 +170,12 @@
}
}
}
-};
-// Process session where the process hosts IBinderRpcTest, the server used
-// for most testing here
-struct BinderRpcTestProcessSession {
- ProcessSession proc;
-
- // pre-fetched root object (for first session)
- sp<IBinder> rootBinder;
-
- // pre-casted root object (for first session)
- sp<IBinderRpcTest> rootIface;
-
- // whether session should be invalidated by end of run
- bool expectAlreadyShutdown = false;
-
- BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
- ~BinderRpcTestProcessSession() {
- if (!expectAlreadyShutdown) {
- EXPECT_NE(nullptr, rootIface);
- if (rootIface == nullptr) return;
-
- std::vector<int32_t> remoteCounts;
- // calling over any sessions counts across all sessions
- EXPECT_OK(rootIface->countBinders(&remoteCounts));
- EXPECT_EQ(remoteCounts.size(), proc.sessions.size());
- for (auto remoteCount : remoteCounts) {
- EXPECT_EQ(remoteCount, 1);
- }
-
- // even though it is on another thread, shutdown races with
- // the transaction reply being written
- if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
- EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
- }
- }
-
- rootIface = nullptr;
- rootBinder = nullptr;
+ void setCustomExitStatusCheck(std::function<void(int wstatus)> f) override {
+ host.setCustomExitStatusCheck(std::move(f));
}
+
+ void terminate() override { host.terminate(); }
};
static base::unique_fd connectTo(const RpcSocketAddress& addr) {
@@ -273,552 +212,130 @@
return std::move(sockClient);
}
-using RunServiceFn = void (*)(android::base::borrowed_fd writeEnd,
- android::base::borrowed_fd readEnd);
+std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+ auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
+ auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
+ std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
+ if (singleThreaded) {
+ ret += "_single_threaded";
+ }
+ if (noKernel) {
+ ret += "_no_kernel";
+ }
+ return ret;
+}
-class BinderRpc : public ::testing::TestWithParam<
- std::tuple<SocketType, RpcSecurity, uint32_t, uint32_t, bool, bool>> {
-public:
- SocketType socketType() const { return std::get<0>(GetParam()); }
- RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); }
- uint32_t clientVersion() const { return std::get<2>(GetParam()); }
- uint32_t serverVersion() const { return std::get<3>(GetParam()); }
- bool serverSingleThreaded() const { return std::get<4>(GetParam()); }
- bool noKernel() const { return std::get<5>(GetParam()); }
+// This creates a new process serving an interface on a certain number of
+// threads.
+std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
+ const BinderRpcOptions& options) {
+ CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
- bool clientOrServerSingleThreaded() const {
- return !kEnableRpcThreads || serverSingleThreaded();
+ SocketType socketType = std::get<0>(GetParam());
+ RpcSecurity rpcSecurity = std::get<1>(GetParam());
+ uint32_t clientVersion = std::get<2>(GetParam());
+ uint32_t serverVersion = std::get<3>(GetParam());
+ bool singleThreaded = std::get<4>(GetParam());
+ bool noKernel = std::get<5>(GetParam());
+
+ std::string path = android::base::GetExecutableDirectory();
+ auto servicePath = android::base::StringPrintf("%s/binder_rpc_test_service%s%s", path.c_str(),
+ singleThreaded ? "_single_threaded" : "",
+ noKernel ? "_no_kernel" : "");
+
+ base::unique_fd bootstrapClientFd, bootstrapServerFd;
+ // Do not set O_CLOEXEC, bootstrapServerFd needs to survive fork/exec.
+ // This is because we cannot pass ParcelFileDescriptor over a pipe.
+ if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &bootstrapServerFd)) {
+ int savedErrno = errno;
+ LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
}
- // Whether the test params support sending FDs in parcels.
- bool supportsFdTransport() const {
- return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS &&
- (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX ||
- socketType() == SocketType::UNIX_BOOTSTRAP);
+ auto ret = std::make_unique<LinuxProcessSession>(
+ Process([=](android::base::borrowed_fd writeEnd, android::base::borrowed_fd readEnd) {
+ auto writeFd = std::to_string(writeEnd.get());
+ auto readFd = std::to_string(readEnd.get());
+ execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(), readFd.c_str(),
+ NULL);
+ }));
+
+ BinderRpcTestServerConfig serverConfig;
+ serverConfig.numThreads = options.numThreads;
+ serverConfig.socketType = static_cast<int32_t>(socketType);
+ serverConfig.rpcSecurity = static_cast<int32_t>(rpcSecurity);
+ serverConfig.serverVersion = serverVersion;
+ serverConfig.vsockPort = allocateVsockPort();
+ serverConfig.addr = allocateSocketAddress();
+ serverConfig.unixBootstrapFd = bootstrapServerFd.get();
+ for (auto mode : options.serverSupportedFileDescriptorTransportModes) {
+ serverConfig.serverSupportedFileDescriptorTransportModes.push_back(
+ static_cast<int32_t>(mode));
+ }
+ writeToFd(ret->host.writeEnd(), serverConfig);
+
+ std::vector<sp<RpcSession>> sessions;
+ auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
+ for (size_t i = 0; i < options.numSessions; i++) {
+ sessions.emplace_back(RpcSession::make(newFactory(rpcSecurity, certVerifier)));
}
- void SetUp() override {
- if (socketType() == SocketType::UNIX_BOOTSTRAP && rpcSecurity() == RpcSecurity::TLS) {
- GTEST_SKIP() << "Unix bootstrap not supported over a TLS transport";
- }
+ auto serverInfo = readFromFd<BinderRpcTestServerInfo>(ret->host.readEnd());
+ BinderRpcTestClientInfo clientInfo;
+ for (const auto& session : sessions) {
+ auto& parcelableCert = clientInfo.certs.emplace_back();
+ parcelableCert.data = session->getCertificate(RpcCertificateFormat::PEM);
+ }
+ writeToFd(ret->host.writeEnd(), clientInfo);
+
+ CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
+ if (socketType == SocketType::INET) {
+ CHECK_NE(0, serverInfo.port);
}
- static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
- auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
- auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
- std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
- if (singleThreaded) {
- ret += "_single_threaded";
- }
- if (noKernel) {
- ret += "_no_kernel";
- }
- return ret;
+ if (rpcSecurity == RpcSecurity::TLS) {
+ const auto& serverCert = serverInfo.cert.data;
+ CHECK_EQ(OK,
+ certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM, serverCert));
}
- // This creates a new process serving an interface on a certain number of
- // threads.
- ProcessSession createRpcTestSocketServerProcessEtc(const BinderRpcOptions& options) {
- CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
+ status_t status;
- SocketType socketType = std::get<0>(GetParam());
- RpcSecurity rpcSecurity = std::get<1>(GetParam());
- uint32_t clientVersion = std::get<2>(GetParam());
- uint32_t serverVersion = std::get<3>(GetParam());
- bool singleThreaded = std::get<4>(GetParam());
- bool noKernel = std::get<5>(GetParam());
+ for (const auto& session : sessions) {
+ CHECK(session->setProtocolVersion(clientVersion));
+ session->setMaxIncomingThreads(options.numIncomingConnections);
+ session->setMaxOutgoingThreads(options.numOutgoingConnections);
+ session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
- std::string path = android::base::GetExecutableDirectory();
- auto servicePath =
- android::base::StringPrintf("%s/binder_rpc_test_service%s%s", path.c_str(),
- singleThreaded ? "_single_threaded" : "",
- noKernel ? "_no_kernel" : "");
-
- base::unique_fd bootstrapClientFd, bootstrapServerFd;
- // Do not set O_CLOEXEC, bootstrapServerFd needs to survive fork/exec.
- // This is because we cannot pass ParcelFileDescriptor over a pipe.
- if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &bootstrapServerFd)) {
- int savedErrno = errno;
- LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
- }
-
- auto ret = ProcessSession{
- .host = Process([=](android::base::borrowed_fd writeEnd,
- android::base::borrowed_fd readEnd) {
- auto writeFd = std::to_string(writeEnd.get());
- auto readFd = std::to_string(readEnd.get());
- execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(), readFd.c_str(),
- NULL);
- }),
- };
-
- BinderRpcTestServerConfig serverConfig;
- serverConfig.numThreads = options.numThreads;
- serverConfig.socketType = static_cast<int32_t>(socketType);
- serverConfig.rpcSecurity = static_cast<int32_t>(rpcSecurity);
- serverConfig.serverVersion = serverVersion;
- serverConfig.vsockPort = allocateVsockPort();
- serverConfig.addr = allocateSocketAddress();
- serverConfig.unixBootstrapFd = bootstrapServerFd.get();
- for (auto mode : options.serverSupportedFileDescriptorTransportModes) {
- serverConfig.serverSupportedFileDescriptorTransportModes.push_back(
- static_cast<int32_t>(mode));
- }
- writeToFd(ret.host.writeEnd(), serverConfig);
-
- std::vector<sp<RpcSession>> sessions;
- auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
- for (size_t i = 0; i < options.numSessions; i++) {
- sessions.emplace_back(RpcSession::make(newFactory(rpcSecurity, certVerifier)));
- }
-
- auto serverInfo = readFromFd<BinderRpcTestServerInfo>(ret.host.readEnd());
- BinderRpcTestClientInfo clientInfo;
- for (const auto& session : sessions) {
- auto& parcelableCert = clientInfo.certs.emplace_back();
- parcelableCert.data = session->getCertificate(RpcCertificateFormat::PEM);
- }
- writeToFd(ret.host.writeEnd(), clientInfo);
-
- CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
- if (socketType == SocketType::INET) {
- CHECK_NE(0, serverInfo.port);
- }
-
- if (rpcSecurity == RpcSecurity::TLS) {
- const auto& serverCert = serverInfo.cert.data;
- CHECK_EQ(OK,
- certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
- serverCert));
- }
-
- status_t status;
-
- for (const auto& session : sessions) {
- CHECK(session->setProtocolVersion(clientVersion));
- session->setMaxIncomingThreads(options.numIncomingConnections);
- session->setMaxOutgoingThreads(options.numOutgoingConnections);
- session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
-
- switch (socketType) {
- case SocketType::PRECONNECTED:
- status = session->setupPreconnectedClient({}, [=]() {
- return connectTo(UnixSocketAddress(serverConfig.addr.c_str()));
- });
- break;
- case SocketType::UNIX:
- status = session->setupUnixDomainClient(serverConfig.addr.c_str());
- break;
- case SocketType::UNIX_BOOTSTRAP:
- status = session->setupUnixDomainSocketBootstrapClient(
- base::unique_fd(dup(bootstrapClientFd.get())));
- break;
- case SocketType::VSOCK:
- status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
- break;
- case SocketType::INET:
- status = session->setupInetClient("127.0.0.1", serverInfo.port);
- break;
- default:
- LOG_ALWAYS_FATAL("Unknown socket type");
- }
- if (options.allowConnectFailure && status != OK) {
- ret.sessions.clear();
+ switch (socketType) {
+ case SocketType::PRECONNECTED:
+ status = session->setupPreconnectedClient({}, [=]() {
+ return connectTo(UnixSocketAddress(serverConfig.addr.c_str()));
+ });
break;
- }
- CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
- ret.sessions.push_back({session, session->getRootObject()});
+ case SocketType::UNIX:
+ status = session->setupUnixDomainClient(serverConfig.addr.c_str());
+ break;
+ case SocketType::UNIX_BOOTSTRAP:
+ status = session->setupUnixDomainSocketBootstrapClient(
+ base::unique_fd(dup(bootstrapClientFd.get())));
+ break;
+ case SocketType::VSOCK:
+ status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
+ break;
+ case SocketType::INET:
+ status = session->setupInetClient("127.0.0.1", serverInfo.port);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unknown socket type");
}
- return ret;
+ if (options.allowConnectFailure && status != OK) {
+ ret->sessions.clear();
+ break;
+ }
+ CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
+ ret->sessions.push_back({session, session->getRootObject()});
}
-
- BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
- BinderRpcTestProcessSession ret{
- .proc = createRpcTestSocketServerProcessEtc(options),
- };
-
- ret.rootBinder = ret.proc.sessions.empty() ? nullptr : ret.proc.sessions.at(0).root;
- ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
-
- return ret;
- }
-
- void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
- size_t sleepMs = 500);
-};
-
-TEST_P(BinderRpc, Ping) {
- auto proc = createRpcTestSocketServerProcess({});
- ASSERT_NE(proc.rootBinder, nullptr);
- EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-}
-
-TEST_P(BinderRpc, GetInterfaceDescriptor) {
- auto proc = createRpcTestSocketServerProcess({});
- ASSERT_NE(proc.rootBinder, nullptr);
- EXPECT_EQ(IBinderRpcTest::descriptor, proc.rootBinder->getInterfaceDescriptor());
-}
-
-TEST_P(BinderRpc, MultipleSessions) {
- if (serverSingleThreaded()) {
- // Tests with multiple sessions require a multi-threaded service,
- // but work fine on a single-threaded client
- GTEST_SKIP() << "This test requires a multi-threaded service";
- }
-
- auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 5});
- for (auto session : proc.proc.sessions) {
- ASSERT_NE(nullptr, session.root);
- EXPECT_EQ(OK, session.root->pingBinder());
- }
-}
-
-TEST_P(BinderRpc, SeparateRootObject) {
- if (serverSingleThreaded()) {
- GTEST_SKIP() << "This test requires a multi-threaded service";
- }
-
- SocketType type = std::get<0>(GetParam());
- if (type == SocketType::PRECONNECTED || type == SocketType::UNIX ||
- type == SocketType::UNIX_BOOTSTRAP) {
- // we can't get port numbers for unix sockets
- return;
- }
-
- auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
-
- int port1 = 0;
- EXPECT_OK(proc.rootIface->getClientPort(&port1));
-
- sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc.sessions.at(1).root);
- int port2;
- EXPECT_OK(rootIface2->getClientPort(&port2));
-
- // we should have a different IBinderRpcTest object created for each
- // session, because we use setPerSessionRootObject
- EXPECT_NE(port1, port2);
-}
-
-TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
- auto proc = createRpcTestSocketServerProcess({});
- Parcel data;
- Parcel reply;
- EXPECT_EQ(BAD_TYPE, proc.rootBinder->transact(IBinder::PING_TRANSACTION, data, &reply, 0));
-}
-
-TEST_P(BinderRpc, AppendSeparateFormats) {
- auto proc1 = createRpcTestSocketServerProcess({});
- auto proc2 = createRpcTestSocketServerProcess({});
-
- Parcel pRaw;
-
- Parcel p1;
- p1.markForBinder(proc1.rootBinder);
- p1.writeInt32(3);
-
- EXPECT_EQ(BAD_TYPE, p1.appendFrom(&pRaw, 0, pRaw.dataSize()));
- EXPECT_EQ(BAD_TYPE, pRaw.appendFrom(&p1, 0, p1.dataSize()));
-
- Parcel p2;
- p2.markForBinder(proc2.rootBinder);
- p2.writeInt32(7);
-
- EXPECT_EQ(BAD_TYPE, p1.appendFrom(&p2, 0, p2.dataSize()));
- EXPECT_EQ(BAD_TYPE, p2.appendFrom(&p1, 0, p1.dataSize()));
-}
-
-TEST_P(BinderRpc, UnknownTransaction) {
- auto proc = createRpcTestSocketServerProcess({});
- Parcel data;
- data.markForBinder(proc.rootBinder);
- Parcel reply;
- EXPECT_EQ(UNKNOWN_TRANSACTION, proc.rootBinder->transact(1337, data, &reply, 0));
-}
-
-TEST_P(BinderRpc, SendSomethingOneway) {
- auto proc = createRpcTestSocketServerProcess({});
- EXPECT_OK(proc.rootIface->sendString("asdf"));
-}
-
-TEST_P(BinderRpc, SendAndGetResultBack) {
- auto proc = createRpcTestSocketServerProcess({});
- std::string doubled;
- EXPECT_OK(proc.rootIface->doubleString("cool ", &doubled));
- EXPECT_EQ("cool cool ", doubled);
-}
-
-TEST_P(BinderRpc, SendAndGetResultBackBig) {
- auto proc = createRpcTestSocketServerProcess({});
- std::string single = std::string(1024, 'a');
- std::string doubled;
- EXPECT_OK(proc.rootIface->doubleString(single, &doubled));
- EXPECT_EQ(single + single, doubled);
-}
-
-TEST_P(BinderRpc, InvalidNullBinderReturn) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> outBinder;
- EXPECT_EQ(proc.rootIface->getNullBinder(&outBinder).transactionError(), UNEXPECTED_NULL);
-}
-
-TEST_P(BinderRpc, CallMeBack) {
- auto proc = createRpcTestSocketServerProcess({});
-
- int32_t pingResult;
- EXPECT_OK(proc.rootIface->pingMe(new MyBinderRpcSession("foo"), &pingResult));
- EXPECT_EQ(OK, pingResult);
-
- EXPECT_EQ(0, MyBinderRpcSession::gNum);
-}
-
-TEST_P(BinderRpc, RepeatBinder) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> inBinder = new MyBinderRpcSession("foo");
- sp<IBinder> outBinder;
- EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
- EXPECT_EQ(inBinder, outBinder);
-
- wp<IBinder> weak = inBinder;
- inBinder = nullptr;
- outBinder = nullptr;
-
- // Force reading a reply, to process any pending dec refs from the other
- // process (the other process will process dec refs there before processing
- // the ping here).
- EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-
- EXPECT_EQ(nullptr, weak.promote());
-
- EXPECT_EQ(0, MyBinderRpcSession::gNum);
-}
-
-TEST_P(BinderRpc, RepeatTheirBinder) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinderRpcSession> session;
- EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
-
- sp<IBinder> inBinder = IInterface::asBinder(session);
- sp<IBinder> outBinder;
- EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
- EXPECT_EQ(inBinder, outBinder);
-
- wp<IBinder> weak = inBinder;
- session = nullptr;
- inBinder = nullptr;
- outBinder = nullptr;
-
- // Force reading a reply, to process any pending dec refs from the other
- // process (the other process will process dec refs there before processing
- // the ping here).
- EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-
- EXPECT_EQ(nullptr, weak.promote());
-}
-
-TEST_P(BinderRpc, RepeatBinderNull) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> outBinder;
- EXPECT_OK(proc.rootIface->repeatBinder(nullptr, &outBinder));
- EXPECT_EQ(nullptr, outBinder);
-}
-
-TEST_P(BinderRpc, HoldBinder) {
- auto proc = createRpcTestSocketServerProcess({});
-
- IBinder* ptr = nullptr;
- {
- sp<IBinder> binder = new BBinder();
- ptr = binder.get();
- EXPECT_OK(proc.rootIface->holdBinder(binder));
- }
-
- sp<IBinder> held;
- EXPECT_OK(proc.rootIface->getHeldBinder(&held));
-
- EXPECT_EQ(held.get(), ptr);
-
- // stop holding binder, because we test to make sure references are cleaned
- // up
- EXPECT_OK(proc.rootIface->holdBinder(nullptr));
- // and flush ref counts
- EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-}
-
-// START TESTS FOR LIMITATIONS OF SOCKET BINDER
-// These are behavioral differences form regular binder, where certain usecases
-// aren't supported.
-
-TEST_P(BinderRpc, CannotMixBindersBetweenUnrelatedSocketSessions) {
- auto proc1 = createRpcTestSocketServerProcess({});
- auto proc2 = createRpcTestSocketServerProcess({});
-
- sp<IBinder> outBinder;
- EXPECT_EQ(INVALID_OPERATION,
- proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
-}
-
-TEST_P(BinderRpc, CannotMixBindersBetweenTwoSessionsToTheSameServer) {
- if (serverSingleThreaded()) {
- GTEST_SKIP() << "This test requires a multi-threaded service";
- }
-
- auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 2});
-
- sp<IBinder> outBinder;
- EXPECT_EQ(INVALID_OPERATION,
- proc.rootIface->repeatBinder(proc.proc.sessions.at(1).root, &outBinder)
- .transactionError());
-}
-
-TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
- if (!kEnableKernelIpc || noKernel()) {
- GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
- "at build time.";
- }
-
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
- sp<IBinder> outBinder;
- EXPECT_EQ(INVALID_OPERATION,
- proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
-}
-
-TEST_P(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
- if (!kEnableKernelIpc || noKernel()) {
- GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
- "at build time.";
- }
-
- auto proc = createRpcTestSocketServerProcess({});
-
- // for historical reasons, IServiceManager interface only returns the
- // exception code
- EXPECT_EQ(binder::Status::EX_TRANSACTION_FAILED,
- defaultServiceManager()->addService(String16("not_suspicious"), proc.rootBinder));
-}
-
-// END TESTS FOR LIMITATIONS OF SOCKET BINDER
-
-TEST_P(BinderRpc, RepeatRootObject) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> outBinder;
- EXPECT_OK(proc.rootIface->repeatBinder(proc.rootBinder, &outBinder));
- EXPECT_EQ(proc.rootBinder, outBinder);
-}
-
-TEST_P(BinderRpc, NestedTransactions) {
- auto proc = createRpcTestSocketServerProcess({
- // Enable FD support because it uses more stack space and so represents
- // something closer to a worst case scenario.
- .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
- .serverSupportedFileDescriptorTransportModes =
- {RpcSession::FileDescriptorTransportMode::UNIX},
- });
-
- auto nastyNester = sp<MyBinderRpcTest>::make();
- EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10));
-
- wp<IBinder> weak = nastyNester;
- nastyNester = nullptr;
- EXPECT_EQ(nullptr, weak.promote());
-}
-
-TEST_P(BinderRpc, SameBinderEquality) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> a;
- EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
-
- sp<IBinder> b;
- EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
-
- EXPECT_EQ(a, b);
-}
-
-TEST_P(BinderRpc, SameBinderEqualityWeak) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinder> a;
- EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
- wp<IBinder> weak = a;
- a = nullptr;
-
- sp<IBinder> b;
- EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
-
- // this is the wrong behavior, since BpBinder
- // doesn't implement onIncStrongAttempted
- // but make sure there is no crash
- EXPECT_EQ(nullptr, weak.promote());
-
- GTEST_SKIP() << "Weak binders aren't currently re-promotable for RPC binder.";
-
- // In order to fix this:
- // - need to have incStrongAttempted reflected across IPC boundary (wait for
- // response to promote - round trip...)
- // - sendOnLastWeakRef, to delete entries out of RpcState table
- EXPECT_EQ(b, weak.promote());
-}
-
-#define expectSessions(expected, iface) \
- do { \
- int session; \
- EXPECT_OK((iface)->getNumOpenSessions(&session)); \
- EXPECT_EQ(expected, session); \
- } while (false)
-
-TEST_P(BinderRpc, SingleSession) {
- auto proc = createRpcTestSocketServerProcess({});
-
- sp<IBinderRpcSession> session;
- EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
- std::string out;
- EXPECT_OK(session->getName(&out));
- EXPECT_EQ("aoeu", out);
-
- expectSessions(1, proc.rootIface);
- session = nullptr;
- expectSessions(0, proc.rootIface);
-}
-
-TEST_P(BinderRpc, ManySessions) {
- auto proc = createRpcTestSocketServerProcess({});
-
- std::vector<sp<IBinderRpcSession>> sessions;
-
- for (size_t i = 0; i < 15; i++) {
- expectSessions(i, proc.rootIface);
- sp<IBinderRpcSession> session;
- EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
- sessions.push_back(session);
- }
- expectSessions(sessions.size(), proc.rootIface);
- for (size_t i = 0; i < sessions.size(); i++) {
- std::string out;
- EXPECT_OK(sessions.at(i)->getName(&out));
- EXPECT_EQ(std::to_string(i), out);
- }
- expectSessions(sessions.size(), proc.rootIface);
-
- while (!sessions.empty()) {
- sessions.pop_back();
- expectSessions(sessions.size(), proc.rootIface);
- }
- expectSessions(0, proc.rootIface);
-}
-
-size_t epochMillis() {
- using std::chrono::duration_cast;
- using std::chrono::milliseconds;
- using std::chrono::seconds;
- using std::chrono::system_clock;
- return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+ return ret;
}
TEST_P(BinderRpc, ThreadPoolGreaterThanEqualRequested) {
@@ -857,8 +374,8 @@
for (auto& t : ts) t.join();
}
-void BinderRpc::testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
- size_t sleepMs) {
+static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
+ size_t sleepMs = 500) {
size_t epochMsBefore = epochMillis();
std::vector<std::thread> ts;
@@ -958,20 +475,6 @@
saturateThreadPool(kNumServerThreads, proc.rootIface);
}
-TEST_P(BinderRpc, OnewayCallDoesNotWait) {
- constexpr size_t kReallyLongTimeMs = 100;
- constexpr size_t kSleepMs = kReallyLongTimeMs * 5;
-
- auto proc = createRpcTestSocketServerProcess({});
-
- size_t epochMsBefore = epochMillis();
-
- EXPECT_OK(proc.rootIface->sleepMsAsync(kSleepMs));
-
- size_t epochMsAfter = epochMillis();
- EXPECT_LT(epochMsAfter, epochMsBefore + kReallyLongTimeMs);
-}
-
TEST_P(BinderRpc, OnewayCallQueueingWithFds) {
if (!supportsFdTransport()) {
GTEST_SKIP() << "Would fail trivially (which is tested elsewhere)";
@@ -1057,7 +560,7 @@
// Build up oneway calls on the second session to make sure it terminates
// and shuts down. The first session should be unaffected (proc destructor
// checks the first session).
- auto iface = interface_cast<IBinderRpcTest>(proc.proc.sessions.at(1).root);
+ auto iface = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
std::vector<std::thread> threads;
for (size_t i = 0; i < kNumClients; i++) {
@@ -1085,66 +588,7 @@
// any pending commands). We need to erase this session from the record
// here, so that the destructor for our session won't check that this
// session is valid, but we still want it to test the other session.
- proc.proc.sessions.erase(proc.proc.sessions.begin() + 1);
-}
-
-TEST_P(BinderRpc, Callbacks) {
- const static std::string kTestString = "good afternoon!";
-
- for (bool callIsOneway : {true, false}) {
- for (bool callbackIsOneway : {true, false}) {
- for (bool delayed : {true, false}) {
- if (clientOrServerSingleThreaded() &&
- (callIsOneway || callbackIsOneway || delayed)) {
- // we have no incoming connections to receive the callback
- continue;
- }
-
- size_t numIncomingConnections = clientOrServerSingleThreaded() ? 0 : 1;
- auto proc = createRpcTestSocketServerProcess(
- {.numThreads = 1,
- .numSessions = 1,
- .numIncomingConnections = numIncomingConnections});
- auto cb = sp<MyBinderRpcCallback>::make();
-
- if (callIsOneway) {
- EXPECT_OK(proc.rootIface->doCallbackAsync(cb, callbackIsOneway, delayed,
- kTestString));
- } else {
- EXPECT_OK(
- proc.rootIface->doCallback(cb, callbackIsOneway, delayed, kTestString));
- }
-
- // if both transactions are synchronous and the response is sent back on the
- // same thread, everything should have happened in a nested call. Otherwise,
- // the callback will be processed on another thread.
- if (callIsOneway || callbackIsOneway || delayed) {
- using std::literals::chrono_literals::operator""s;
- RpcMutexUniqueLock _l(cb->mMutex);
- cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
- }
-
- EXPECT_EQ(cb->mValues.size(), 1)
- << "callIsOneway: " << callIsOneway
- << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
- if (cb->mValues.empty()) continue;
- EXPECT_EQ(cb->mValues.at(0), kTestString)
- << "callIsOneway: " << callIsOneway
- << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
-
- // since we are severing the connection, we need to go ahead and
- // tell the server to shutdown and exit so that waitpid won't hang
- if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
- EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
- }
-
- // since this session has an incoming connection w/ a threadpool, we
- // need to manually shut it down
- EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
- proc.expectAlreadyShutdown = true;
- }
- }
- }
+ proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
}
TEST_P(BinderRpc, SingleDeathRecipient) {
@@ -1177,7 +621,7 @@
ASSERT_TRUE(dr->mCv.wait_for(lock, 100ms, [&]() { return dr->dead; }));
// need to wait for the session to shutdown so we don't "Leak session"
- EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+ EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
proc.expectAlreadyShutdown = true;
}
@@ -1205,7 +649,7 @@
// Explicitly calling shutDownAndWait will cause the death recipients
// to be called.
- EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+ EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
std::unique_lock<std::mutex> lock(dr->mMtx);
if (!dr->dead) {
@@ -1213,8 +657,8 @@
}
EXPECT_TRUE(dr->dead) << "Failed to receive the death notification.";
- proc.proc.host.terminate();
- proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+ proc.proc->terminate();
+ proc.proc->setCustomExitStatusCheck([](int wstatus) {
EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
<< "server process failed incorrectly: " << WaitStatusToString(wstatus);
});
@@ -1259,18 +703,10 @@
}
// need to wait for the session to shutdown so we don't "Leak session"
- EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+ EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
proc.expectAlreadyShutdown = true;
}
-TEST_P(BinderRpc, OnewayCallbackWithNoThread) {
- auto proc = createRpcTestSocketServerProcess({});
- auto cb = sp<MyBinderRpcCallback>::make();
-
- Status status = proc.rootIface->doCallback(cb, true /*oneway*/, false /*delayed*/, "anything");
- EXPECT_EQ(WOULD_BLOCK, status.transactionError());
-}
-
TEST_P(BinderRpc, Die) {
for (bool doDeathCleanup : {true, false}) {
auto proc = createRpcTestSocketServerProcess({});
@@ -1286,7 +722,7 @@
EXPECT_EQ(DEAD_OBJECT, proc.rootIface->die(doDeathCleanup).transactionError())
<< "Do death cleanup: " << doDeathCleanup;
- proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+ proc.proc->setCustomExitStatusCheck([](int wstatus) {
EXPECT_TRUE(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 1)
<< "server process failed incorrectly: " << WaitStatusToString(wstatus);
});
@@ -1316,7 +752,7 @@
// second time! we catch the error :)
EXPECT_EQ(DEAD_OBJECT, proc.rootIface->useKernelBinderCallingId().transactionError());
- proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+ proc.proc->setCustomExitStatusCheck([](int wstatus) {
EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)
<< "server process failed incorrectly: " << WaitStatusToString(wstatus);
});
@@ -1330,9 +766,9 @@
{RpcSession::FileDescriptorTransportMode::UNIX},
.allowConnectFailure = true,
});
- EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed";
- proc.proc.host.terminate();
- proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+ EXPECT_TRUE(proc.proc->sessions.empty()) << "session connections should have failed";
+ proc.proc->terminate();
+ proc.proc->setCustomExitStatusCheck([](int wstatus) {
EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
<< "server process failed incorrectly: " << WaitStatusToString(wstatus);
});
@@ -1346,9 +782,9 @@
{RpcSession::FileDescriptorTransportMode::NONE},
.allowConnectFailure = true,
});
- EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed";
- proc.proc.host.terminate();
- proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+ EXPECT_TRUE(proc.proc->sessions.empty()) << "session connections should have failed";
+ proc.proc->terminate();
+ proc.proc->setCustomExitStatusCheck([](int wstatus) {
EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
<< "server process failed incorrectly: " << WaitStatusToString(wstatus);
});
@@ -1516,16 +952,6 @@
ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
}
-TEST_P(BinderRpc, AidlDelegatorTest) {
- auto proc = createRpcTestSocketServerProcess({});
- auto myDelegator = sp<IBinderRpcTestDelegator>::make(proc.rootIface);
- ASSERT_NE(nullptr, myDelegator);
-
- std::string doubled;
- EXPECT_OK(myDelegator->doubleString("cool ", &doubled));
- EXPECT_EQ("cool cool ", doubled);
-}
-
static bool testSupportVsockLoopback() {
// We don't need to enable TLS to know if vsock is supported.
unsigned int vsockPort = allocateVsockPort();
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index 823bbf6..dc7d264 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -91,6 +91,14 @@
}
}
+static inline size_t epochMillis() {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ using std::chrono::seconds;
+ using std::chrono::system_clock;
+ return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+}
+
struct BinderRpcOptions {
size_t numThreads = 1;
size_t numSessions = 1;
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
new file mode 100644
index 0000000..721fbfe
--- /dev/null
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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 <gtest/gtest.h>
+
+#include "binderRpcTestCommon.h"
+
+#define EXPECT_OK(status) \
+ do { \
+ android::binder::Status stat = (status); \
+ EXPECT_TRUE(stat.isOk()) << stat; \
+ } while (false)
+
+namespace android {
+
+// Abstract base class with a virtual destructor that handles the
+// ownership of a process session for BinderRpcTestSession below
+class ProcessSession {
+public:
+ struct SessionInfo {
+ sp<RpcSession> session;
+ sp<IBinder> root;
+ };
+
+ // client session objects associated with other process
+ // each one represents a separate session
+ std::vector<SessionInfo> sessions;
+
+ virtual ~ProcessSession() = 0;
+
+ // If the process exits with a status, run the given callback on that value.
+ virtual void setCustomExitStatusCheck(std::function<void(int wstatus)> f) = 0;
+
+ // Kill the process. Avoid if possible. Shutdown gracefully via an RPC instead.
+ virtual void terminate() = 0;
+};
+
+// Process session where the process hosts IBinderRpcTest, the server used
+// for most testing here
+struct BinderRpcTestProcessSession {
+ std::unique_ptr<ProcessSession> proc;
+
+ // pre-fetched root object (for first session)
+ sp<IBinder> rootBinder;
+
+ // pre-casted root object (for first session)
+ sp<IBinderRpcTest> rootIface;
+
+ // whether session should be invalidated by end of run
+ bool expectAlreadyShutdown = false;
+
+ BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
+ ~BinderRpcTestProcessSession() {
+ if (!expectAlreadyShutdown) {
+ EXPECT_NE(nullptr, rootIface);
+ if (rootIface == nullptr) return;
+
+ std::vector<int32_t> remoteCounts;
+ // calling over any sessions counts across all sessions
+ EXPECT_OK(rootIface->countBinders(&remoteCounts));
+ EXPECT_EQ(remoteCounts.size(), proc->sessions.size());
+ for (auto remoteCount : remoteCounts) {
+ EXPECT_EQ(remoteCount, 1);
+ }
+
+ // even though it is on another thread, shutdown races with
+ // the transaction reply being written
+ if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
+ EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+ }
+ }
+
+ rootIface = nullptr;
+ rootBinder = nullptr;
+ }
+};
+
+class BinderRpc : public ::testing::TestWithParam<
+ std::tuple<SocketType, RpcSecurity, uint32_t, uint32_t, bool, bool>> {
+public:
+ SocketType socketType() const { return std::get<0>(GetParam()); }
+ RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); }
+ uint32_t clientVersion() const { return std::get<2>(GetParam()); }
+ uint32_t serverVersion() const { return std::get<3>(GetParam()); }
+ bool serverSingleThreaded() const { return std::get<4>(GetParam()); }
+ bool noKernel() const { return std::get<5>(GetParam()); }
+
+ bool clientOrServerSingleThreaded() const {
+ return !kEnableRpcThreads || serverSingleThreaded();
+ }
+
+ // Whether the test params support sending FDs in parcels.
+ bool supportsFdTransport() const {
+ return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS &&
+ (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX ||
+ socketType() == SocketType::UNIX_BOOTSTRAP);
+ }
+
+ void SetUp() override {
+ if (socketType() == SocketType::UNIX_BOOTSTRAP && rpcSecurity() == RpcSecurity::TLS) {
+ GTEST_SKIP() << "Unix bootstrap not supported over a TLS transport";
+ }
+ }
+
+ BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
+ BinderRpcTestProcessSession ret{
+ .proc = createRpcTestSocketServerProcessEtc(options),
+ };
+
+ ret.rootBinder = ret.proc->sessions.empty() ? nullptr : ret.proc->sessions.at(0).root;
+ ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
+
+ return ret;
+ }
+
+ static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info);
+
+protected:
+ std::unique_ptr<ProcessSession> createRpcTestSocketServerProcessEtc(
+ const BinderRpcOptions& options);
+};
+
+} // namespace android
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
new file mode 100644
index 0000000..1e8d93d
--- /dev/null
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2022 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 <chrono>
+#include <cstdlib>
+#include <type_traits>
+
+#include "binderRpcTestCommon.h"
+#include "binderRpcTestFixture.h"
+
+using namespace std::chrono_literals;
+using namespace std::placeholders;
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
+namespace android {
+
+static_assert(RPC_WIRE_PROTOCOL_VERSION + 1 == RPC_WIRE_PROTOCOL_VERSION_NEXT ||
+ RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+
+TEST(BinderRpcParcel, EntireParcelFormatted) {
+ Parcel p;
+ p.writeInt32(3);
+
+ EXPECT_DEATH_IF_SUPPORTED(p.markForBinder(sp<BBinder>::make()),
+ "format must be set before data is written");
+}
+
+TEST(BinderRpc, CannotUseNextWireVersion) {
+ auto session = RpcSession::make();
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 1));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 2));
+ EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
+}
+
+TEST(BinderRpc, CanUseExperimentalWireVersion) {
+ auto session = RpcSession::make();
+ EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
+}
+
+TEST_P(BinderRpc, Ping) {
+ auto proc = createRpcTestSocketServerProcess({});
+ ASSERT_NE(proc.rootBinder, nullptr);
+ EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+}
+
+TEST_P(BinderRpc, GetInterfaceDescriptor) {
+ auto proc = createRpcTestSocketServerProcess({});
+ ASSERT_NE(proc.rootBinder, nullptr);
+ EXPECT_EQ(IBinderRpcTest::descriptor, proc.rootBinder->getInterfaceDescriptor());
+}
+
+TEST_P(BinderRpc, MultipleSessions) {
+ if (serverSingleThreaded()) {
+ // Tests with multiple sessions require a multi-threaded service,
+ // but work fine on a single-threaded client
+ GTEST_SKIP() << "This test requires a multi-threaded service";
+ }
+
+ auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 5});
+ for (auto session : proc.proc->sessions) {
+ ASSERT_NE(nullptr, session.root);
+ EXPECT_EQ(OK, session.root->pingBinder());
+ }
+}
+
+TEST_P(BinderRpc, SeparateRootObject) {
+ if (serverSingleThreaded()) {
+ GTEST_SKIP() << "This test requires a multi-threaded service";
+ }
+
+ SocketType type = std::get<0>(GetParam());
+ if (type == SocketType::PRECONNECTED || type == SocketType::UNIX ||
+ type == SocketType::UNIX_BOOTSTRAP) {
+ // we can't get port numbers for unix sockets
+ return;
+ }
+
+ auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
+
+ int port1 = 0;
+ EXPECT_OK(proc.rootIface->getClientPort(&port1));
+
+ sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
+ int port2;
+ EXPECT_OK(rootIface2->getClientPort(&port2));
+
+ // we should have a different IBinderRpcTest object created for each
+ // session, because we use setPerSessionRootObject
+ EXPECT_NE(port1, port2);
+}
+
+TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
+ auto proc = createRpcTestSocketServerProcess({});
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(BAD_TYPE, proc.rootBinder->transact(IBinder::PING_TRANSACTION, data, &reply, 0));
+}
+
+TEST_P(BinderRpc, AppendSeparateFormats) {
+ auto proc1 = createRpcTestSocketServerProcess({});
+ auto proc2 = createRpcTestSocketServerProcess({});
+
+ Parcel pRaw;
+
+ Parcel p1;
+ p1.markForBinder(proc1.rootBinder);
+ p1.writeInt32(3);
+
+ EXPECT_EQ(BAD_TYPE, p1.appendFrom(&pRaw, 0, pRaw.dataSize()));
+ EXPECT_EQ(BAD_TYPE, pRaw.appendFrom(&p1, 0, p1.dataSize()));
+
+ Parcel p2;
+ p2.markForBinder(proc2.rootBinder);
+ p2.writeInt32(7);
+
+ EXPECT_EQ(BAD_TYPE, p1.appendFrom(&p2, 0, p2.dataSize()));
+ EXPECT_EQ(BAD_TYPE, p2.appendFrom(&p1, 0, p1.dataSize()));
+}
+
+TEST_P(BinderRpc, UnknownTransaction) {
+ auto proc = createRpcTestSocketServerProcess({});
+ Parcel data;
+ data.markForBinder(proc.rootBinder);
+ Parcel reply;
+ EXPECT_EQ(UNKNOWN_TRANSACTION, proc.rootBinder->transact(1337, data, &reply, 0));
+}
+
+TEST_P(BinderRpc, SendSomethingOneway) {
+ auto proc = createRpcTestSocketServerProcess({});
+ EXPECT_OK(proc.rootIface->sendString("asdf"));
+}
+
+TEST_P(BinderRpc, SendAndGetResultBack) {
+ auto proc = createRpcTestSocketServerProcess({});
+ std::string doubled;
+ EXPECT_OK(proc.rootIface->doubleString("cool ", &doubled));
+ EXPECT_EQ("cool cool ", doubled);
+}
+
+TEST_P(BinderRpc, SendAndGetResultBackBig) {
+ auto proc = createRpcTestSocketServerProcess({});
+ std::string single = std::string(1024, 'a');
+ std::string doubled;
+ EXPECT_OK(proc.rootIface->doubleString(single, &doubled));
+ EXPECT_EQ(single + single, doubled);
+}
+
+TEST_P(BinderRpc, InvalidNullBinderReturn) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> outBinder;
+ EXPECT_EQ(proc.rootIface->getNullBinder(&outBinder).transactionError(), UNEXPECTED_NULL);
+}
+
+TEST_P(BinderRpc, CallMeBack) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ int32_t pingResult;
+ EXPECT_OK(proc.rootIface->pingMe(new MyBinderRpcSession("foo"), &pingResult));
+ EXPECT_EQ(OK, pingResult);
+
+ EXPECT_EQ(0, MyBinderRpcSession::gNum);
+}
+
+TEST_P(BinderRpc, RepeatBinder) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> inBinder = new MyBinderRpcSession("foo");
+ sp<IBinder> outBinder;
+ EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
+ EXPECT_EQ(inBinder, outBinder);
+
+ wp<IBinder> weak = inBinder;
+ inBinder = nullptr;
+ outBinder = nullptr;
+
+ // Force reading a reply, to process any pending dec refs from the other
+ // process (the other process will process dec refs there before processing
+ // the ping here).
+ EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+ EXPECT_EQ(nullptr, weak.promote());
+
+ EXPECT_EQ(0, MyBinderRpcSession::gNum);
+}
+
+TEST_P(BinderRpc, RepeatTheirBinder) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinderRpcSession> session;
+ EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
+
+ sp<IBinder> inBinder = IInterface::asBinder(session);
+ sp<IBinder> outBinder;
+ EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
+ EXPECT_EQ(inBinder, outBinder);
+
+ wp<IBinder> weak = inBinder;
+ session = nullptr;
+ inBinder = nullptr;
+ outBinder = nullptr;
+
+ // Force reading a reply, to process any pending dec refs from the other
+ // process (the other process will process dec refs there before processing
+ // the ping here).
+ EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+ EXPECT_EQ(nullptr, weak.promote());
+}
+
+TEST_P(BinderRpc, RepeatBinderNull) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> outBinder;
+ EXPECT_OK(proc.rootIface->repeatBinder(nullptr, &outBinder));
+ EXPECT_EQ(nullptr, outBinder);
+}
+
+TEST_P(BinderRpc, HoldBinder) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ IBinder* ptr = nullptr;
+ {
+ sp<IBinder> binder = new BBinder();
+ ptr = binder.get();
+ EXPECT_OK(proc.rootIface->holdBinder(binder));
+ }
+
+ sp<IBinder> held;
+ EXPECT_OK(proc.rootIface->getHeldBinder(&held));
+
+ EXPECT_EQ(held.get(), ptr);
+
+ // stop holding binder, because we test to make sure references are cleaned
+ // up
+ EXPECT_OK(proc.rootIface->holdBinder(nullptr));
+ // and flush ref counts
+ EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+}
+
+// START TESTS FOR LIMITATIONS OF SOCKET BINDER
+// These are behavioral differences form regular binder, where certain usecases
+// aren't supported.
+
+TEST_P(BinderRpc, CannotMixBindersBetweenUnrelatedSocketSessions) {
+ auto proc1 = createRpcTestSocketServerProcess({});
+ auto proc2 = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> outBinder;
+ EXPECT_EQ(INVALID_OPERATION,
+ proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
+}
+
+TEST_P(BinderRpc, CannotMixBindersBetweenTwoSessionsToTheSameServer) {
+ if (serverSingleThreaded()) {
+ GTEST_SKIP() << "This test requires a multi-threaded service";
+ }
+
+ auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 2});
+
+ sp<IBinder> outBinder;
+ EXPECT_EQ(INVALID_OPERATION,
+ proc.rootIface->repeatBinder(proc.proc->sessions.at(1).root, &outBinder)
+ .transactionError());
+}
+
+TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
+ if (!kEnableKernelIpc || noKernel()) {
+ GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
+ "at build time.";
+ }
+
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
+ sp<IBinder> outBinder;
+ EXPECT_EQ(INVALID_OPERATION,
+ proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
+}
+
+TEST_P(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
+ if (!kEnableKernelIpc || noKernel()) {
+ GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
+ "at build time.";
+ }
+
+ auto proc = createRpcTestSocketServerProcess({});
+
+ // for historical reasons, IServiceManager interface only returns the
+ // exception code
+ EXPECT_EQ(binder::Status::EX_TRANSACTION_FAILED,
+ defaultServiceManager()->addService(String16("not_suspicious"), proc.rootBinder));
+}
+
+// END TESTS FOR LIMITATIONS OF SOCKET BINDER
+
+TEST_P(BinderRpc, RepeatRootObject) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> outBinder;
+ EXPECT_OK(proc.rootIface->repeatBinder(proc.rootBinder, &outBinder));
+ EXPECT_EQ(proc.rootBinder, outBinder);
+}
+
+TEST_P(BinderRpc, NestedTransactions) {
+ auto proc = createRpcTestSocketServerProcess({
+ // Enable FD support because it uses more stack space and so represents
+ // something closer to a worst case scenario.
+ .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
+ .serverSupportedFileDescriptorTransportModes =
+ {RpcSession::FileDescriptorTransportMode::UNIX},
+ });
+
+ auto nastyNester = sp<MyBinderRpcTest>::make();
+ EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10));
+
+ wp<IBinder> weak = nastyNester;
+ nastyNester = nullptr;
+ EXPECT_EQ(nullptr, weak.promote());
+}
+
+TEST_P(BinderRpc, SameBinderEquality) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> a;
+ EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+
+ sp<IBinder> b;
+ EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
+
+ EXPECT_EQ(a, b);
+}
+
+TEST_P(BinderRpc, SameBinderEqualityWeak) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinder> a;
+ EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+ wp<IBinder> weak = a;
+ a = nullptr;
+
+ sp<IBinder> b;
+ EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
+
+ // this is the wrong behavior, since BpBinder
+ // doesn't implement onIncStrongAttempted
+ // but make sure there is no crash
+ EXPECT_EQ(nullptr, weak.promote());
+
+ GTEST_SKIP() << "Weak binders aren't currently re-promotable for RPC binder.";
+
+ // In order to fix this:
+ // - need to have incStrongAttempted reflected across IPC boundary (wait for
+ // response to promote - round trip...)
+ // - sendOnLastWeakRef, to delete entries out of RpcState table
+ EXPECT_EQ(b, weak.promote());
+}
+
+#define expectSessions(expected, iface) \
+ do { \
+ int session; \
+ EXPECT_OK((iface)->getNumOpenSessions(&session)); \
+ EXPECT_EQ(expected, session); \
+ } while (false)
+
+TEST_P(BinderRpc, SingleSession) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ sp<IBinderRpcSession> session;
+ EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
+ std::string out;
+ EXPECT_OK(session->getName(&out));
+ EXPECT_EQ("aoeu", out);
+
+ expectSessions(1, proc.rootIface);
+ session = nullptr;
+ expectSessions(0, proc.rootIface);
+}
+
+TEST_P(BinderRpc, ManySessions) {
+ auto proc = createRpcTestSocketServerProcess({});
+
+ std::vector<sp<IBinderRpcSession>> sessions;
+
+ for (size_t i = 0; i < 15; i++) {
+ expectSessions(i, proc.rootIface);
+ sp<IBinderRpcSession> session;
+ EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
+ sessions.push_back(session);
+ }
+ expectSessions(sessions.size(), proc.rootIface);
+ for (size_t i = 0; i < sessions.size(); i++) {
+ std::string out;
+ EXPECT_OK(sessions.at(i)->getName(&out));
+ EXPECT_EQ(std::to_string(i), out);
+ }
+ expectSessions(sessions.size(), proc.rootIface);
+
+ while (!sessions.empty()) {
+ sessions.pop_back();
+ expectSessions(sessions.size(), proc.rootIface);
+ }
+ expectSessions(0, proc.rootIface);
+}
+
+TEST_P(BinderRpc, OnewayCallDoesNotWait) {
+ constexpr size_t kReallyLongTimeMs = 100;
+ constexpr size_t kSleepMs = kReallyLongTimeMs * 5;
+
+ auto proc = createRpcTestSocketServerProcess({});
+
+ size_t epochMsBefore = epochMillis();
+
+ EXPECT_OK(proc.rootIface->sleepMsAsync(kSleepMs));
+
+ size_t epochMsAfter = epochMillis();
+ EXPECT_LT(epochMsAfter, epochMsBefore + kReallyLongTimeMs);
+}
+
+TEST_P(BinderRpc, Callbacks) {
+ const static std::string kTestString = "good afternoon!";
+
+ for (bool callIsOneway : {true, false}) {
+ for (bool callbackIsOneway : {true, false}) {
+ for (bool delayed : {true, false}) {
+ if (clientOrServerSingleThreaded() &&
+ (callIsOneway || callbackIsOneway || delayed)) {
+ // we have no incoming connections to receive the callback
+ continue;
+ }
+
+ size_t numIncomingConnections = clientOrServerSingleThreaded() ? 0 : 1;
+ auto proc = createRpcTestSocketServerProcess(
+ {.numThreads = 1,
+ .numSessions = 1,
+ .numIncomingConnections = numIncomingConnections});
+ auto cb = sp<MyBinderRpcCallback>::make();
+
+ if (callIsOneway) {
+ EXPECT_OK(proc.rootIface->doCallbackAsync(cb, callbackIsOneway, delayed,
+ kTestString));
+ } else {
+ EXPECT_OK(
+ proc.rootIface->doCallback(cb, callbackIsOneway, delayed, kTestString));
+ }
+
+ // if both transactions are synchronous and the response is sent back on the
+ // same thread, everything should have happened in a nested call. Otherwise,
+ // the callback will be processed on another thread.
+ if (callIsOneway || callbackIsOneway || delayed) {
+ using std::literals::chrono_literals::operator""s;
+ RpcMutexUniqueLock _l(cb->mMutex);
+ cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
+ }
+
+ EXPECT_EQ(cb->mValues.size(), 1)
+ << "callIsOneway: " << callIsOneway
+ << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
+ if (cb->mValues.empty()) continue;
+ EXPECT_EQ(cb->mValues.at(0), kTestString)
+ << "callIsOneway: " << callIsOneway
+ << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
+
+ // since we are severing the connection, we need to go ahead and
+ // tell the server to shutdown and exit so that waitpid won't hang
+ if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
+ EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+ }
+
+ // since this session has an incoming connection w/ a threadpool, we
+ // need to manually shut it down
+ EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
+ proc.expectAlreadyShutdown = true;
+ }
+ }
+ }
+}
+
+TEST_P(BinderRpc, OnewayCallbackWithNoThread) {
+ auto proc = createRpcTestSocketServerProcess({});
+ auto cb = sp<MyBinderRpcCallback>::make();
+
+ Status status = proc.rootIface->doCallback(cb, true /*oneway*/, false /*delayed*/, "anything");
+ EXPECT_EQ(WOULD_BLOCK, status.transactionError());
+}
+
+TEST_P(BinderRpc, AidlDelegatorTest) {
+ auto proc = createRpcTestSocketServerProcess({});
+ auto myDelegator = sp<IBinderRpcTestDelegator>::make(proc.rootIface);
+ ASSERT_NE(nullptr, myDelegator);
+
+ std::string doubled;
+ EXPECT_OK(myDelegator->doubleString("cool ", &doubled));
+ EXPECT_EQ("cool cool ", doubled);
+}
+
+} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 3904e1d..61a2412 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -20,6 +20,9 @@
java: {
enabled: false,
},
+ rust: {
+ enabled: true,
+ },
},
}
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
index 462ef9a..a1fb701 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
@@ -29,3 +29,12 @@
}
} // namespace android
+
+extern "C" {
+// This API is used by fuzzers to automatically fuzz aidl services
+void fuzzRustService(void* binder, const uint8_t* data, size_t len) {
+ AIBinder* aiBinder = static_cast<AIBinder*>(binder);
+ FuzzedDataProvider provider(data, len);
+ android::fuzzService(aiBinder, std::move(provider));
+}
+} // extern "C"
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 3d81c32..6d1dfe8 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -58,7 +58,7 @@
],
export_shared_lib_headers: [
- "android.hardware.graphics.common-V3-ndk",
+ "android.hardware.graphics.common-V4-ndk",
"android.hardware.graphics.mapper@4.0",
"libhidlbase",
],
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index a96a07a..af50a29 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -27,10 +27,13 @@
srcs: [
"GpuStatsInfo.cpp",
"GraphicsEnv.cpp",
- "IGpuService.cpp"
+ "IGpuService.cpp",
],
- cflags: ["-Wall", "-Werror"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
shared_libs: [
"libbase",
@@ -46,4 +49,13 @@
],
export_include_dirs: ["include"],
+
+ product_variables: {
+ // `debuggable` is set for eng and userdebug builds
+ debuggable: {
+ cflags: [
+ "-DANDROID_DEBUGGABLE",
+ ],
+ },
+ },
}
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 4a0a839..5f5f85a 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -126,7 +126,20 @@
}
bool GraphicsEnv::isDebuggable() {
- return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+ // This flag determines if the application is marked debuggable
+ bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+
+ // This flag is set only in `debuggable` builds of the platform
+#if defined(ANDROID_DEBUGGABLE)
+ bool platformDebuggable = true;
+#else
+ bool platformDebuggable = false;
+#endif
+
+ ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s",
+ appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false");
+
+ return appDebuggable || platformDebuggable;
}
void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 82a6b6c..73d3196 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -35,7 +35,7 @@
// Check if the process is debuggable. It returns false except in any of the
// following circumstances:
- // 1. ro.debuggable=1 (global debuggable enabled).
+ // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled).
// 2. android:debuggable="true" in the manifest for an individual app.
// 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
// 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 9c2ce0f..9e175ec 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2375,42 +2375,22 @@
return NAME_NOT_FOUND;
}
-status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
+status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
binder::Status status =
- ComposerServiceAIDL::getComposerService()
- ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
- primaryRefreshRateMin, primaryRefreshRateMax,
- appRequestRefreshRateMin,
- appRequestRefreshRateMax);
+ ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken,
+ specs);
return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId* outDefaultMode,
- bool* outAllowGroupSwitching,
- float* outPrimaryRefreshRateMin,
- float* outPrimaryRefreshRateMax,
- float* outAppRequestRefreshRateMin,
- float* outAppRequestRefreshRateMax) {
- if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin ||
- !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) {
+ gui::DisplayModeSpecs* outSpecs) {
+ if (!outSpecs) {
return BAD_VALUE;
}
- gui::DisplayModeSpecs specs;
binder::Status status =
ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken,
- &specs);
- if (status.isOk()) {
- *outDefaultMode = specs.defaultMode;
- *outAllowGroupSwitching = specs.allowGroupSwitching;
- *outPrimaryRefreshRateMin = specs.primaryRefreshRateMin;
- *outPrimaryRefreshRateMax = specs.primaryRefreshRateMax;
- *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin;
- *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax;
- }
+ outSpecs);
return statusTFromBinderStatus(status);
}
diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index fb4fcdf..af138c7 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -18,10 +18,58 @@
/** @hide */
parcelable DisplayModeSpecs {
+ /**
+ * Defines the refresh rates ranges that should be used by SF.
+ */
+ parcelable RefreshRateRanges {
+ /**
+ * Defines a range of refresh rates.
+ */
+ parcelable RefreshRateRange {
+ float min;
+ float max;
+ }
+
+ /**
+ * The range of refresh rates that the display should run at.
+ */
+ RefreshRateRange physical;
+
+ /**
+ * The range of refresh rates that apps should render at.
+ */
+ RefreshRateRange render;
+ }
+
+ /**
+ * Base mode ID. This is what system defaults to for all other settings, or
+ * if the refresh rate range is not available.
+ */
int defaultMode;
+
+ /**
+ * If true this will allow switching between modes in different display configuration
+ * groups. This way the user may see visual interruptions when the display mode changes.
+ */
+
boolean allowGroupSwitching;
- float primaryRefreshRateMin;
- float primaryRefreshRateMax;
- float appRequestRefreshRateMin;
- float appRequestRefreshRateMax;
+
+ /**
+ * The primary physical and render refresh rate ranges represent DisplayManager's general
+ * guidance on the display modes SurfaceFlinger will consider when switching refresh
+ * rates and scheduling the frame rate. Unless SurfaceFlinger has a specific reason to do
+ * otherwise, it will stay within this range.
+ */
+ RefreshRateRanges primaryRanges;
+
+ /**
+ * The app request physical and render refresh rate ranges allow SurfaceFlinger to consider
+ * more display modes when switching refresh rates. Although SurfaceFlinger will
+ * generally stay within the primary range, specific considerations, such as layer frame
+ * rate settings specified via the setFrameRate() API, may cause SurfaceFlinger to go
+ * outside the primary range. SurfaceFlinger never goes outside the app request range.
+ * The app request range will be greater than or equal to the primary refresh rate range,
+ * never smaller.
+ */
+ RefreshRateRanges appRequestRanges;
}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 92d9e77..40410fb 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -327,25 +327,9 @@
/**
* Sets the refresh rate boundaries for the display.
*
- * The primary refresh rate range represents display manager's general guidance on the display
- * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an
- * app, we should stay within this range.
- *
- * The app request refresh rate range allows us to consider more display modes when switching
- * refresh rates. Although we should generally stay within the primary range, specific
- * considerations, such as layer frame rate settings specified via the setFrameRate() api, may
- * cause us to go outside the primary range. We never go outside the app request range. The app
- * request range will be greater than or equal to the primary refresh rate range, never smaller.
- *
- * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider
- * switching between. Only modes with a mode group and resolution matching defaultMode
- * will be considered for switching. The defaultMode corresponds to an ID of mode in the list
- * of supported modes returned from getDynamicDisplayInfo().
+ * @see DisplayModeSpecs.aidl for details.
*/
- void setDesiredDisplayModeSpecs(
- IBinder displayToken, int defaultMode,
- boolean allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin, float appRequestRefreshRateMax);
+ void setDesiredDisplayModeSpecs(IBinder displayToken, in DisplayModeSpecs specs);
DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken);
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
index cdc9376..1c61d6b 100644
--- a/libs/gui/fuzzer/Android.bp
+++ b/libs/gui/fuzzer/Android.bp
@@ -46,7 +46,7 @@
"android.hardware.configstore-utils",
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
- "android.hardware.power-V2-cpp",
+ "android.hardware.power-V4-cpp",
"android.hidl.token@1.0",
"libSurfaceFlingerProp",
"libgui",
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 2025170..9d1ee8f 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -127,9 +127,7 @@
MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener,
(const sp<gui::ITunnelModeEnabledListener>&), (override));
MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs,
- (const sp<IBinder>&, int32_t, bool, float, float, float,
- float appRequestRefreshRateMax),
- (override));
+ (const sp<IBinder>&, const gui::DisplayModeSpecs&), (override));
MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs,
(const sp<IBinder>&, gui::DisplayModeSpecs*), (override));
MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp<IBinder>&, bool*),
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
index eecbe0f..57720dd 100644
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -123,10 +123,37 @@
sp<SurfaceControl> makeSurfaceControl();
BlurRegion getBlurRegion();
void fuzzOnPullAtom();
+ gui::DisplayModeSpecs getDisplayModeSpecs();
FuzzedDataProvider mFdp;
};
+gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() {
+ const auto getRefreshRateRange = [&] {
+ gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
+ range.min = mFdp.ConsumeFloatingPoint<float>();
+ range.max = mFdp.ConsumeFloatingPoint<float>();
+ return range;
+ };
+
+ const auto getRefreshRateRanges = [&] {
+ gui::DisplayModeSpecs::RefreshRateRanges ranges;
+ ranges.physical = getRefreshRateRange();
+ ranges.render = getRefreshRateRange();
+ return ranges;
+ };
+
+ String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
+ sp<IBinder> displayToken =
+ SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
+ gui::DisplayModeSpecs specs;
+ specs.defaultMode = mFdp.ConsumeIntegral<int32_t>();
+ specs.allowGroupSwitching = mFdp.ConsumeBool();
+ specs.primaryRanges = getRefreshRateRanges();
+ specs.appRequestRanges = getRefreshRateRanges();
+ return specs;
+}
+
BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() {
int32_t left = mFdp.ConsumeIntegral<int32_t>();
int32_t right = mFdp.ConsumeIntegral<int32_t>();
@@ -247,12 +274,7 @@
String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
sp<IBinder> displayToken =
SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
- SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, mFdp.ConsumeIntegral<int32_t>(),
- mFdp.ConsumeBool() /*allowGroupSwitching*/,
- mFdp.ConsumeFloatingPoint<float>(),
- mFdp.ConsumeFloatingPoint<float>(),
- mFdp.ConsumeFloatingPoint<float>(),
- mFdp.ConsumeFloatingPoint<float>());
+ SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs());
ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes);
SurfaceComposerClient::setActiveColorMode(displayToken, colorMode);
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 1f19f4e..7aec0bf 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -113,6 +113,24 @@
return surface != nullptr && surface->getIGraphicBufferProducer() != nullptr;
}
+ static sp<IGraphicBufferProducer> getIGraphicBufferProducer(ANativeWindow* window) {
+ int val;
+ if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+ val == NATIVE_WINDOW_SURFACE) {
+ return ((Surface*) window)->mGraphicBufferProducer;
+ }
+ return nullptr;
+ }
+
+ static sp<IBinder> getSurfaceControlHandle(ANativeWindow* window) {
+ int val;
+ if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+ val == NATIVE_WINDOW_SURFACE) {
+ return ((Surface*) window)->mSurfaceControlHandle;
+ }
+ return nullptr;
+ }
+
/* Attaches a sideband buffer stream to the Surface's IGraphicBufferProducer.
*
* A sideband stream is a device-specific mechanism for passing buffers
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index c450e85..2038f14 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -159,18 +159,11 @@
static status_t getActiveDisplayMode(const sp<IBinder>& display, ui::DisplayMode*);
// Sets the refresh rate boundaries for the display.
- static status_t setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin, float appRequestRefreshRateMax);
+ static status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs&);
// Gets the refresh rate boundaries for the display.
static status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId* outDefaultMode,
- bool* outAllowGroupSwitching,
- float* outPrimaryRefreshRateMin,
- float* outPrimaryRefreshRateMax,
- float* outAppRequestRefreshRateMin,
- float* outAppRequestRefreshRateMax);
+ gui::DisplayModeSpecs*);
// Get the coordinates of the display's native color primaries
static status_t getDisplayNativePrimaries(const sp<IBinder>& display,
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 346b686..67c669d 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -920,16 +920,12 @@
}
binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
- int32_t /*defaultMode*/, bool /*allowGroupSwitching*/,
- float /*primaryRefreshRateMin*/,
- float /*primaryRefreshRateMax*/,
- float /*appRequestRefreshRateMin*/,
- float /*appRequestRefreshRateMax*/) override {
+ const gui::DisplayModeSpecs&) override {
return binder::Status::ok();
}
binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
- gui::DisplayModeSpecs* /*outSpecs*/) override {
+ gui::DisplayModeSpecs*) override {
return binder::Status::ok();
}
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 3ab2ba8..0375915 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -23,16 +23,19 @@
cc_library_static {
name: "libjpegrecoverymap",
- vendor_available: true,
+ host_supported: true,
export_include_dirs: ["include"],
local_include_dirs: ["include"],
srcs: [
"recoverymap.cpp",
+ "recoverymapmath.cpp",
],
shared_libs: [
+ "libimage_io",
+ "libjpeg",
"libutils",
],
}
@@ -63,4 +66,4 @@
srcs: [
"jpegdecoder.cpp",
],
-}
\ No newline at end of file
+}
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index 2ab7550..df24b10 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -14,6 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+
// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>
extern "C" {
@@ -41,12 +45,22 @@
* Returns the decompressed raw image buffer pointer. This method must be called only after
* calling decompressImage().
*/
- const void* getDecompressedImagePtr();
+ void* getDecompressedImagePtr();
/*
* Returns the decompressed raw image buffer size. This method must be called only after
* calling decompressImage().
*/
size_t getDecompressedImageSize();
+ /*
+ * Returns the image width in pixels. This method must be called only after calling
+ * decompressImage().
+ */
+ size_t getDecompressedImageWidth();
+ /*
+ * Returns the image width in pixels. This method must be called only after calling
+ * decompressImage().
+ */
+ size_t getDecompressedImageHeight();
private:
bool decode(const void* image, int length);
// Returns false if errors occur.
@@ -56,7 +70,12 @@
// Process 16 lines of Y and 16 lines of U/V each time.
// We must pass at least 16 scanlines according to libjpeg documentation.
static const int kCompressBatchSize = 16;
- // The buffer that holds the compressed result.
+ // The buffer that holds the decompressed result.
std::vector<JOCTET> mResultBuffer;
+ // Resolution of the decompressed image.
+ size_t mWidth;
+ size_t mHeight;
};
} /* namespace android */
+
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
index 9641fda..61aeb8a 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+
// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
#include <cstdio>
@@ -50,7 +53,7 @@
* Returns the compressed JPEG buffer pointer. This method must be called only after calling
* compressImage().
*/
- const void* getCompressedImagePtr();
+ void* getCompressedImagePtr();
/*
* Returns the compressed JPEG buffer size. This method must be called only after calling
@@ -87,4 +90,6 @@
std::vector<JOCTET> mResultBuffer;
};
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
+
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
new file mode 100644
index 0000000..194cd2f
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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 <utils/Errors.h>
+
+namespace android::recoverymap {
+
+enum {
+ // status_t map for errors in the media framework
+ // OK or NO_ERROR or 0 represents no error.
+
+ // See system/core/include/utils/Errors.h
+ // System standard errors from -1 through (possibly) -133
+ //
+ // Errors with special meanings and side effects.
+ // INVALID_OPERATION: Operation attempted in an illegal state (will try to signal to app).
+ // DEAD_OBJECT: Signal from CodecBase to MediaCodec that MediaServer has died.
+ // NAME_NOT_FOUND: Signal from CodecBase to MediaCodec that the component was not found.
+
+ // JPEGR errors
+ JPEGR_IO_ERROR_BASE = -10000,
+ ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE,
+ ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1,
+ ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2,
+ ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3,
+ ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4,
+
+ JPEGR_RUNTIME_ERROR_BASE = -20000,
+ ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1,
+ ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2,
+ ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3,
+ ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4,
+};
+
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 15eca1e..b2ca481 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -14,10 +14,20 @@
* limitations under the License.
*/
- #include <utils/Errors.h>
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+
+#include "jpegrerrorcode.h"
namespace android::recoverymap {
+typedef enum {
+ JPEGR_COLORSPACE_UNSPECIFIED,
+ JPEGR_COLORSPACE_BT709,
+ JPEGR_COLORSPACE_P3,
+ JPEGR_COLORSPACE_BT2100,
+} jpegr_color_space;
+
/*
* Holds information for uncompressed image or recovery map.
*/
@@ -28,6 +38,8 @@
int width;
// Height of the recovery map or image in pixels.
int height;
+ // Color space.
+ jpegr_color_space colorSpace;
};
/*
@@ -36,12 +48,25 @@
struct jpegr_compressed_struct {
// Pointer to the data location.
void* data;
+ // Data length.
+ int length;
+ // Color space.
+ jpegr_color_space colorSpace;
+};
+
+/*
+ * Holds information for EXIF metadata.
+ */
+struct jpegr_exif_struct {
+ // Pointer to the data location.
+ void* data;
// Data length;
int length;
};
typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
typedef struct jpegr_compressed_struct* jr_compressed_ptr;
+typedef struct jpegr_exif_struct* jr_exif_ptr;
class RecoveryMap {
public:
@@ -49,21 +74,29 @@
* Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
*
* Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
- * the recovery map to the end of the compressed JPEG.
+ * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
+ * resolution and color space.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param dest destination of the compressed JPEGR image
+ * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+ * the highest quality
+ * @param exif pointer to the exif metadata.
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
- void* dest);
+ jr_compressed_ptr dest,
+ int quality,
+ jr_exif_ptr exif);
/*
- * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+ * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
+ *
+ * This method requires HAL Hardware JPEG encoder.
*
* Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the
- * compressed JPEG.
+ * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param compressed_jpeg_image compressed 8-bit JPEG image
@@ -72,31 +105,48 @@
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
- void* compressed_jpeg_image,
- void* dest);
+ jr_compressed_ptr compressed_jpeg_image,
+ jr_compressed_ptr dest);
/*
* Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
*
+ * This method requires HAL Hardware JPEG encoder.
+ *
* Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
- * and the decoded SDR result, append the recovery map to the end of the compressed JPEG.
+ * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
+ * and SDR inputs must be the same resolution and color space.
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param dest destination of the compressed JPEGR image
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- void* compressed_jpeg_image,
- void* dest);
+ jr_compressed_ptr compressed_jpeg_image,
+ jr_compressed_ptr dest);
/*
* Decompress JPEGR image.
*
* @param compressed_jpegr_image compressed JPEGR image
* @param dest destination of the uncompressed JPEGR image
+ * @param exif destination of the decoded EXIF metadata. Default value is nullptr where EXIF
+ * metadata will not be decoded.
+ * @param request_sdr flag that request SDR output, default to false (request HDR output). If
+ * set to true, decoder will only decode the primary image which is SDR.
+ * Setting of request_sdr and input source (HDR or SDR) can be found in
+ * the table below:
+ * | input source | request_sdr | output of decoding |
+ * | HDR | true | SDR |
+ * | HDR | false | HDR |
+ * | SDR | true | SDR |
+ * | SDR | false | SDR |
* @return NO_ERROR if decoding succeeds, error code if error occurs.
*/
- status_t decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest);
+ status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+ jr_uncompressed_ptr dest,
+ jr_exif_ptr exif = nullptr,
+ bool request_sdr = false);
private:
/*
* This method is called in the decoding pipeline. It will decode the recovery map.
@@ -105,7 +155,7 @@
* @param dest decoded recover map
* @return NO_ERROR if decoding succeeds, error code if error occurs.
*/
- status_t decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+ status_t decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
jr_uncompressed_ptr dest);
/*
@@ -115,21 +165,24 @@
* @param dest encoded recover map
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
- status_t encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+ status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
jr_compressed_ptr dest);
/*
* This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
- * 10-bit yuv images as input, and calculate the uncompressed recovery map.
+ * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images
+ * must be the same resolution.
*
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
- * @param dest recover map
+ * @param dest recovery map; caller responsible for memory of data
+ * @param hdr_ratio HDR ratio will be updated in this method
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr dest);
+ jr_uncompressed_ptr dest,
+ float &hdr_ratio);
/*
* This method is called in the decoding pipeline. It will take the uncompressed (decoded)
@@ -153,7 +206,7 @@
* @param dest destination of compressed recovery map
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest);
+ status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest);
/*
* This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
@@ -162,12 +215,57 @@
*
* @param compressed_jpeg_image compressed 8-bit JPEG image
* @param compress_recovery_map compressed recover map
+ * @param hdr_ratio HDR ratio
* @param dest compressed JPEGR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
- status_t appendRecoveryMap(void* compressed_jpeg_image,
+ status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
jr_compressed_ptr compressed_recovery_map,
- void* dest);
+ float hdr_ratio,
+ jr_compressed_ptr dest);
+
+ /*
+ * This method generates XMP metadata.
+ *
+ * below is an example of the XMP metadata that this function generates where
+ * secondary_image_length = 1000
+ * hdr_ratio = 1.25
+ *
+ * <x:xmpmeta
+ * xmlns:x="adobe:ns:meta/"
+ * x:xmptk="Adobe XMP Core 5.1.2">
+ * <rdf:RDF
+ * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ * <rdf:Description
+ * xmlns:GContainer="http://ns.google.com/photos/1.0/container/">
+ * <GContainer:Version>1</GContainer:Version>
+ * <GContainer:HdrRatio>1.25</GContainer:HdrRatio>
+ * <GContainer:Directory>
+ * <rdf:Seq>
+ * <rdf:li>
+ * <GContainer:Item
+ * Item:Semantic="Primary"
+ * Item:Mime="image/jpeg"/>
+ * </rdf:li>
+ * <rdf:li>
+ * <GContainer:Item
+ * Item:Semantic="RecoveryMap"
+ * Item:Mime="image/jpeg"
+ * Item:Length="1000"/>
+ * </rdf:li>
+ * </rdf:Seq>
+ * </GContainer:Directory>
+ * </rdf:Description>
+ * </rdf:RDF>
+ * </x:xmpmeta>
+ *
+ * @param secondary_image_length length of secondary image
+ * @param hdr_ratio hdr ratio
+ * @return XMP metadata in type of string
+ */
+ std::string generateXmp(int secondary_image_length, float hdr_ratio);
};
} // namespace android::recoverymap
+
+#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
new file mode 100644
index 0000000..8e9b07b
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2022 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_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+
+#include <stdint.h>
+
+#include <jpegrecoverymap/recoverymap.h>
+
+namespace android::recoverymap {
+
+const float kSdrWhiteNits = 100.0f;
+
+struct Color {
+ union {
+ struct {
+ float r;
+ float g;
+ float b;
+ };
+ struct {
+ float y;
+ float u;
+ float v;
+ };
+ };
+};
+
+/*
+ * Convert from OETF'd bt.2100 RGB to YUV, according to BT.2100
+ */
+Color bt2100RgbToYuv(Color e);
+
+/*
+ * Convert srgb YUV to RGB, according to ECMA TR/98.
+ */
+Color srgbYuvToRgb(Color e);
+
+/*
+ * TODO: better source for srgb transfer function
+ * Convert from srgb to linear, according to https://en.wikipedia.org/wiki/SRGB.
+ * [0.0, 1.0] range in and out.
+ */
+float srgbInvOetf(float e);
+Color srgbInvOetf(Color e);
+
+/*
+ * Convert from HLG to scene luminance in nits, according to BT.2100.
+ */
+float hlgInvOetf(float e);
+
+/*
+ * Convert from scene luminance in nits to HLG, according to BT.2100.
+ */
+float hlgOetf(float e);
+Color hlgOetf(Color e);
+
+/*
+ * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
+ * luminances in linear space, and the hdr ratio to encode against.
+ */
+uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio);
+
+/*
+ * Calculates the linear luminance in nits after applying the given recovery
+ * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
+ */
+Color applyRecovery(Color e, float recovery, float hdr_ratio);
+
+/*
+ * Helper for sampling from images.
+ */
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
+ * Sample the recovery value for the map from a given x,y coordinate on a scale
+ * that is map scale factor larger than the map size.
+ */
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the image Y value at the provided location, with a weighting based on nearby pixels
+ * and the map scale factor.
+ *
+ * Expect narrow-range image data for P010.
+ */
+float sampleYuv420Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+float sampleP010Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+} // namespace android::recoverymap
+
+#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index 22a5389..c1fb6c3 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -95,7 +95,7 @@
return true;
}
-const void* JpegDecoder::getDecompressedImagePtr() {
+void* JpegDecoder::getDecompressedImagePtr() {
return mResultBuffer.data();
}
@@ -103,6 +103,14 @@
return mResultBuffer.size();
}
+size_t JpegDecoder::getDecompressedImageWidth() {
+ return mWidth;
+}
+
+size_t JpegDecoder::getDecompressedImageHeight() {
+ return mHeight;
+}
+
bool JpegDecoder::decode(const void* image, int length) {
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
@@ -119,6 +127,9 @@
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
+ mWidth = cinfo.image_width;
+ mHeight = cinfo.image_height;
+
if (cinfo.jpeg_color_space == JCS_YCbCr) {
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
} else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
@@ -222,4 +233,4 @@
return true;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp
index d45d9b3..1997bf9 100644
--- a/libs/jpegrecoverymap/jpegencoder.cpp
+++ b/libs/jpegrecoverymap/jpegencoder.cpp
@@ -52,7 +52,7 @@
return true;
}
-const void* JpegEncoder::getCompressedImagePtr() {
+void* JpegEncoder::getCompressedImagePtr() {
return mResultBuffer.data();
}
@@ -236,4 +236,4 @@
return true;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 5d25722..bd16a68 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -14,91 +14,322 @@
* limitations under the License.
*/
+// TODO: need to clean up handling around hdr_ratio and passing it around
+// TODO: need to handle color space information; currently we assume everything
+// is srgb in.
+// TODO: handle PQ encode/decode (currently only HLG)
+
#include <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/recoverymapmath.h>
+
+#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/xml/xml_writer.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+using namespace std;
namespace android::recoverymap {
-status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image,
- void* dest) {
- if (uncompressed_p010_image == nullptr
- || uncompressed_yuv_420_image == nullptr
- || dest == nullptr) {
- return BAD_VALUE;
+#define JPEGR_CHECK(x) \
+ { \
+ status_t status = (x); \
+ if ((status) != NO_ERROR) { \
+ return status; \
+ } \
}
- // TBD
+// Map is quarter res / sixteenth size
+static const size_t kMapDimensionScaleFactor = 4;
+
+
+/*
+ * Helper function used for generating XMP metadata.
+ *
+ * @param prefix The prefix part of the name.
+ * @param suffix The suffix part of the name.
+ * @return A name of the form "prefix:suffix".
+ */
+string Name(const string &prefix, const string &suffix) {
+ std::stringstream ss;
+ ss << prefix << ":" << suffix;
+ return ss.str();
+}
+
+/*
+ * Helper function used for writing data to destination.
+ *
+ * @param destination destination of the data to be written.
+ * @param source source of data being written.
+ * @param length length of the data to be written.
+ * @param position cursor in desitination where the data is to be written.
+ * @return status of succeed or error code.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
+ if (position + length > destination->length) {
+ return ERROR_JPEGR_BUFFER_TOO_SMALL;
+ }
+
+ memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+ position += length;
return NO_ERROR;
}
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
jr_uncompressed_ptr uncompressed_yuv_420_image,
- void* compressed_jpeg_image,
- void* dest) {
-
+ jr_compressed_ptr dest,
+ int quality,
+ jr_exif_ptr /* exif */) {
if (uncompressed_p010_image == nullptr
|| uncompressed_yuv_420_image == nullptr
- || compressed_jpeg_image == nullptr
|| dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ if (quality < 0 || quality > 100) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+ || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+
+ jpegr_uncompressed_struct map;
+ float hdr_ratio = 0.0f;
+ JPEGR_CHECK(generateRecoveryMap(
+ uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+ jpegr_compressed_struct compressed_map;
+ std::unique_ptr<uint8_t[]> compressed_map_data =
+ std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.data = compressed_map_data.get();
+ JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+ JpegEncoder jpeg_encoder;
+ // TODO: ICC data - need color space information
+ if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
+ uncompressed_yuv_420_image->width,
+ uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
+ return ERROR_JPEGR_ENCODE_ERROR;
+ }
+ jpegr_compressed_struct jpeg;
+ jpeg.data = jpeg_encoder.getCompressedImagePtr();
+ jpeg.length = jpeg_encoder.getCompressedImageSize();
+
+ JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest));
+
return NO_ERROR;
}
status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
- void* compressed_jpeg_image,
- void* dest) {
+ jr_uncompressed_ptr uncompressed_yuv_420_image,
+ jr_compressed_ptr compressed_jpeg_image,
+ jr_compressed_ptr dest) {
if (uncompressed_p010_image == nullptr
+ || uncompressed_yuv_420_image == nullptr
|| compressed_jpeg_image == nullptr
|| dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+ || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+
+ jpegr_uncompressed_struct map;
+ float hdr_ratio = 0.0f;
+ JPEGR_CHECK(generateRecoveryMap(
+ uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+ jpegr_compressed_struct compressed_map;
+ std::unique_ptr<uint8_t[]> compressed_map_data =
+ std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.data = compressed_map_data.get();
+ JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+
return NO_ERROR;
}
-status_t RecoveryMap::decodeJPEGR(void* compressed_jpegr_image, jr_uncompressed_ptr dest) {
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+ jr_compressed_ptr compressed_jpeg_image,
+ jr_compressed_ptr dest) {
+ if (uncompressed_p010_image == nullptr
+ || compressed_jpeg_image == nullptr
+ || dest == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_uncompressed_struct uncompressed_yuv_420_image;
+ uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
+ uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
+ uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+
+ if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
+ || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+
+ jpegr_uncompressed_struct map;
+ float hdr_ratio = 0.0f;
+ JPEGR_CHECK(generateRecoveryMap(
+ &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+ jpegr_compressed_struct compressed_map;
+ std::unique_ptr<uint8_t[]> compressed_map_data =
+ std::make_unique<uint8_t[]>(map.width * map.height);
+ compressed_map.data = compressed_map_data.get();
+ JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+ JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+
+ return NO_ERROR;
+}
+
+status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+ jr_uncompressed_ptr dest,
+ jr_exif_ptr /* exif */,
+ bool /* request_sdr */) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ jpegr_compressed_struct compressed_map;
+ JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+
+ jpegr_uncompressed_struct map;
+ JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
+
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ jpegr_uncompressed_struct uncompressed_yuv_420_image;
+ uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
+ uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
+ uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+
+ JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
+
return NO_ERROR;
}
-status_t RecoveryMap::decodeRecoveryMap(jr_compressed_ptr compressed_recovery_map,
- jr_uncompressed_ptr dest) {
+status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+ jr_uncompressed_ptr dest) {
if (compressed_recovery_map == nullptr || dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
+ compressed_recovery_map->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ dest->data = jpeg_decoder.getDecompressedImagePtr();
+ dest->width = jpeg_decoder.getDecompressedImageWidth();
+ dest->height = jpeg_decoder.getDecompressedImageHeight();
+
return NO_ERROR;
}
-status_t RecoveryMap::encodeRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
- jr_compressed_ptr dest) {
+status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+ jr_compressed_ptr dest) {
if (uncompressed_recovery_map == nullptr || dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ // TODO: should we have ICC data?
+ JpegEncoder jpeg_encoder;
+ if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
+ uncompressed_recovery_map->height, 85, nullptr, 0,
+ true /* isSingleChannel */)) {
+ return ERROR_JPEGR_ENCODE_ERROR;
+ }
+
+ if (dest->length < jpeg_encoder.getCompressedImageSize()) {
+ return ERROR_JPEGR_BUFFER_TOO_SMALL;
+ }
+
+ memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
+ dest->length = jpeg_encoder.getCompressedImageSize();
+
return NO_ERROR;
}
status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr dest) {
+ jr_uncompressed_ptr dest,
+ float &hdr_ratio) {
if (uncompressed_yuv_420_image == nullptr
|| uncompressed_p010_image == nullptr
|| dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
+ || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+
+ size_t image_width = uncompressed_yuv_420_image->width;
+ size_t image_height = uncompressed_yuv_420_image->height;
+ size_t map_width = image_width / kMapDimensionScaleFactor;
+ size_t map_height = image_height / kMapDimensionScaleFactor;
+
+ dest->width = map_width;
+ dest->height = map_height;
+ dest->data = new uint8_t[map_width * map_height];
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
+
+ uint16_t yp_hdr_max = 0;
+ for (size_t y = 0; y < image_height; ++y) {
+ for (size_t x = 0; x < image_width; ++x) {
+ size_t pixel_idx = x + y * image_width;
+ uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx];
+ if (yp_hdr > yp_hdr_max) {
+ yp_hdr_max = yp_hdr;
+ }
+ }
+ }
+
+ float y_hdr_max_nits = hlgInvOetf(yp_hdr_max);
+ hdr_ratio = y_hdr_max_nits / kSdrWhiteNits;
+
+ for (size_t y = 0; y < map_height; ++y) {
+ for (size_t x = 0; x < map_width; ++x) {
+ float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
+ float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+
+ float y_sdr_nits = srgbInvOetf(yp_sdr);
+ float y_hdr_nits = hlgInvOetf(yp_hdr);
+
+ size_t pixel_idx = x + y * map_width;
+ reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+ encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio);
+ }
+ }
+
+ map_data.release();
return NO_ERROR;
}
@@ -108,33 +339,148 @@
if (uncompressed_yuv_420_image == nullptr
|| uncompressed_recovery_map == nullptr
|| dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ // TODO: need to get this from the XMP; should probably be a function
+ // parameter
+ float hdr_ratio = 4.0f;
+
+ size_t width = uncompressed_yuv_420_image->width;
+ size_t height = uncompressed_yuv_420_image->height;
+
+ dest->width = width;
+ dest->height = height;
+ size_t pixel_count = width * height;
+
+ for (size_t y = 0; y < height; ++y) {
+ for (size_t x = 0; x < width; ++x) {
+ size_t pixel_y_idx = x + y * width;
+
+ size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
+
+ Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+ Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
+ Color rgb_sdr = srgbInvOetf(rgbp_sdr);
+
+ float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
+ Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+
+ Color rgbp_hdr = hlgOetf(rgb_hdr);
+ Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr);
+
+ reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
+ reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
+ reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
+ }
+ }
+
return NO_ERROR;
}
-status_t RecoveryMap::extractRecoveryMap(void* compressed_jpegr_image, jr_compressed_ptr dest) {
+status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr dest) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
// TBD
return NO_ERROR;
}
-status_t RecoveryMap::appendRecoveryMap(void* compressed_jpeg_image,
- jr_compressed_ptr compressed_recovery_map,
- void* dest) {
+status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
+ jr_compressed_ptr compressed_recovery_map,
+ float hdr_ratio,
+ jr_compressed_ptr dest) {
if (compressed_jpeg_image == nullptr
|| compressed_recovery_map == nullptr
|| dest == nullptr) {
- return BAD_VALUE;
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
+ string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
+ string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+
+ // 2 bytes: APP1 sign (ff e1)
+ // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
+ // x bytes: length of xmp packet
+ int length = 2 + nameSpace.size() + xmp.size();
+ uint8_t lengthH = ((length >> 8) & 0xff);
+ uint8_t lengthL = (length & 0xff);
+
+ int pos = 0;
+
+ // JPEG/R structure:
+ // SOI (ff d8)
+ // APP1 (ff e1)
+ // 2 bytes of length (2 + 29 + length of xmp packet)
+ // name space ("http://ns.adobe.com/xap/1.0/\0")
+ // xmp
+ // primary image (without the first two bytes, the SOI sign)
+ // secondary image (the recovery map)
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
+ JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+ JPEGR_CHECK(Write(dest,
+ (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+ JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
+ dest->length = pos;
+
return NO_ERROR;
}
+string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
+ const string kContainerPrefix = "GContainer";
+ const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
+ const string kItemPrefix = "Item";
+ const string kRecoveryMap = "RecoveryMap";
+ const string kDirectory = "Directory";
+ const string kImageJpeg = "image/jpeg";
+ const string kItem = "Item";
+ const string kLength = "Length";
+ const string kMime = "Mime";
+ const string kPrimary = "Primary";
+ const string kSemantic = "Semantic";
+ const string kVersion = "Version";
+ const int kVersionValue = 1;
+
+ const string kConDir = Name(kContainerPrefix, kDirectory);
+ const string kContainerItem = Name(kContainerPrefix, kItem);
+ const string kItemLength = Name(kItemPrefix, kLength);
+ const string kItemMime = Name(kItemPrefix, kMime);
+ const string kItemSemantic = Name(kItemPrefix, kSemantic);
+
+ const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
+ const vector<string> kLiItem({string("rdf:li"), kContainerItem});
+
+ std::stringstream ss;
+ photos_editing_formats::image_io::XmlWriter writer(ss);
+ writer.StartWritingElement("x:xmpmeta");
+ writer.WriteXmlns("x", "adobe:ns:meta/");
+ writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
+ writer.StartWritingElement("rdf:RDF");
+ writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+ writer.StartWritingElement("rdf:Description");
+ writer.WriteXmlns(kContainerPrefix, kContainerUri);
+ writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
+ writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
+ writer.StartWritingElements(kConDirSeq);
+ size_t item_depth = writer.StartWritingElements(kLiItem);
+ writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
+ writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
+ writer.FinishWritingElementsToDepth(item_depth);
+ writer.StartWritingElements(kLiItem);
+ writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
+ writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
+ writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+ writer.FinishWriting();
+
+ return ss.str();
+}
+
} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
new file mode 100644
index 0000000..3e110bc
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 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 <cmath>
+
+#include <jpegrecoverymap/recoverymapmath.h>
+
+namespace android::recoverymap {
+
+static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
+static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
+
+Color bt2100RgbToYuv(Color e) {
+ float yp = kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
+ return {{{yp, (e.b - yp) / kBt2100Cb, (e.r - yp) / kBt2100Cr }}};
+}
+
+static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
+
+Color srgbYuvToRgb(Color e) {
+ return {{{ e.y + kSrgbRCr * e.v, e.y - kSrgbGCb * e.u - kSrgbGCr * e.v, e.y + kSrgbBCb * e.u }}};
+}
+
+float srgbInvOetf(float e) {
+ if (e <= 0.04045f) {
+ return e / 12.92f;
+ } else {
+ return pow((e + 0.055f) / 1.055f, 2.4);
+ }
+}
+
+Color srgbInvOetf(Color e) {
+ return {{{ srgbInvOetf(e.r), srgbInvOetf(e.g), srgbInvOetf(e.b) }}};
+}
+
+static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
+
+float hlgInvOetf(float e) {
+ if (e <= 0.5f) {
+ return pow(e, 2.0f) / 3.0f;
+ } else {
+ return (exp((e - kHlgC) / kHlgA) + kHlgB) / 12.0f;
+ }
+}
+
+float hlgOetf(float e) {
+ if (e <= 1.0f/12.0f) {
+ return sqrt(3.0f * e);
+ } else {
+ return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
+ }
+}
+
+Color hlgOetf(Color e) {
+ return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
+}
+
+uint8_t EncodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) {
+ float gain = 1.0f;
+ if (y_sdr > 0.0f) {
+ gain = y_hdr / y_sdr;
+ }
+
+ if (gain < -hdr_ratio) gain = -hdr_ratio;
+ if (gain > hdr_ratio) gain = hdr_ratio;
+
+ return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f);
+}
+
+float applyRecovery(float y_sdr, float recovery, float hdr_ratio) {
+ return exp2(log2(y_sdr) + recovery * log2(hdr_ratio));
+}
+
+// TODO: do we need something more clever for filtering either the map or images
+// to generate the map?
+
+static float mapUintToFloat(uint8_t map_uint) {
+ return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
+}
+
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
+ float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
+ float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
+
+ size_t x_lower = static_cast<size_t>(floor(x_map));
+ size_t x_upper = x_lower + 1;
+ size_t y_lower = static_cast<size_t>(floor(y_map));
+ size_t y_upper = y_lower + 1;
+
+ float x_influence = x_map - static_cast<float>(x_lower);
+ float y_influence = y_map - static_cast<float>(y_lower);
+
+ float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+
+ return e1 * (x_influence + y_influence) / 2.0f
+ + e2 * (x_influence + 1.0f - y_influence) / 2.0f
+ + e3 * (1.0f - x_influence + y_influence) / 2.0f
+ + e4 * (1.0f - x_influence + 1.0f - y_influence) / 2.0f;
+}
+
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+ size_t pixel_count = image->width * image->height;
+
+ size_t pixel_y_idx = x + y * image->width;
+ size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+
+ uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y_idx];
+ uint8_t u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
+ uint8_t v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+ // 128 bias for UV given we are using jpeglib; see:
+ // https://github.com/kornelski/libjpeg/blob/master/structure.doc
+ return {{{ static_cast<float>(y_uint) / 255.0f,
+ (static_cast<float>(u_uint) - 128.0f) / 255.0f,
+ (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
+}
+
+typedef float (*sampleComponentFn)(jr_uncompressed_ptr, size_t, size_t);
+
+static float sampleComponent(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
+ sampleComponentFn sample_fn) {
+ float e = 0.0f;
+ for (size_t dy = 0; dy < map_scale_factor; ++dy) {
+ for (size_t dx = 0; dx < map_scale_factor; ++dx) {
+ e += sample_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
+ }
+ }
+
+ return e / static_cast<float>(map_scale_factor * map_scale_factor);
+}
+
+static float getYuv420Y(jr_uncompressed_ptr image, size_t x, size_t y) {
+ size_t pixel_idx = x + y * image->width;
+ uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_idx];
+ return static_cast<float>(y_uint) / 255.0f;
+}
+
+
+float sampleYuv420Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+ return sampleComponent(image, map_scale_factor, x, y, getYuv420Y);
+}
+
+static float getP010Y(jr_uncompressed_ptr image, size_t x, size_t y) {
+ size_t pixel_idx = x + y * image->width;
+ uint8_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_idx];
+ // Expecting narrow range input
+ return (static_cast<float>(y_uint) - 64.0f) / 960.0f;
+}
+
+float sampleP010Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+ return sampleComponent(image, map_scale_factor, x, y, getP010Y);
+}
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 7f37f61..41af991 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -62,4 +62,4 @@
"libjpegdecoder",
"libgtest",
],
-}
\ No newline at end of file
+}
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index b075080..c345385 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -20,10 +20,15 @@
// from nativewindow/includes/system/window.h
// (not to be confused with the compatibility-only window.h from system/core/includes)
#include <system/window.h>
+#include <android/native_window_aidl.h>
#include <private/android/AHardwareBufferHelpers.h>
+#include <log/log.h>
#include <ui/GraphicBuffer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+#include <android/binder_libbinder.h>
using namespace android;
@@ -59,6 +64,13 @@
return false;
}
}
+static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) {
+ return Surface::getIGraphicBufferProducer(window);
+}
+
+static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) {
+ return Surface::getSurfaceControlHandle(window);
+}
/**************************************************************************************************
* NDK
@@ -350,6 +362,42 @@
return native_window_set_auto_prerotation(window, autoPrerotation);
}
+binder_status_t ANativeWindow_readFromParcel(
+ const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
+ const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+
+ // Use a android::view::Surface to unparcel the window
+ std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+ status_t ret = shimSurface->readFromParcel(nativeParcel);
+ if (ret != OK) {
+ ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
+ return STATUS_BAD_VALUE;
+ }
+ sp<Surface> surface = sp<Surface>::make(
+ shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle);
+ ANativeWindow* anw = surface.get();
+ ANativeWindow_acquire(anw);
+ *outWindow = anw;
+ return STATUS_OK;
+}
+
+binder_status_t ANativeWindow_writeToParcel(
+ ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) {
+ int value;
+ int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
+ if (err != OK || value != NATIVE_WINDOW_SURFACE) {
+ ALOGE("Error: ANativeWindow is not backed by Surface");
+ return STATUS_BAD_VALUE;
+ }
+ // Use a android::view::Surface to parcelize the window
+ std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+ shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window);
+ shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window);
+
+ Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+ return shimSurface->writeToParcel(nativeParcel);
+}
+
/**************************************************************************************************
* apex-stable
**************************************************************************************************/
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 3b58265..bc0bfc5 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -110,9 +110,11 @@
static_libs: [
"libarect",
"libgrallocusage",
+ "libgui_aidl_static",
],
header_libs: [
+ "libgui_headers",
"libarect_headers",
"libnativebase_headers",
"libnativewindow_headers",
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 281ec52..a27e3dd 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -376,7 +376,7 @@
__INTRODUCED_IN(__ANDROID_API_U__);
#ifdef __cplusplus
-};
+}
#endif
#endif // ANDROID_NATIVE_WINDOW_H
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
new file mode 100644
index 0000000..a252245
--- /dev/null
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2022 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.
+ */
+
+/**
+ * @file native_window_aidl.h
+ * @brief NativeWindow NDK AIDL glue code
+ */
+
+/**
+ * @addtogroup ANativeWindow
+ *
+ * Parcelable support for ANativeWindow. Can be used with libbinder_ndk
+ *
+ * @{
+ */
+
+#ifndef ANDROID_NATIVE_WINDOW_AIDL_H
+#define ANDROID_NATIVE_WINDOW_AIDL_H
+
+#include <android/binder_parcel.h>
+#include <android/native_window.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Read an ANativeWindow from a AParcel. The output buffer will have an
+ * initial reference acquired and will need to be released with
+ * ANativeWindow_release.
+ *
+ * Available since API level 34.
+ *
+ * \return STATUS_OK on success
+ * STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an
+ * issue deserializing (eg, corrupted parcel)
+ * STATUS_BAD_TYPE if the parcel's current data position is not that of
+ * an ANativeWindow type
+ * STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t ANativeWindow_readFromParcel(const AParcel* _Nonnull parcel,
+ ANativeWindow* _Nullable* _Nonnull outWindow) __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
+ * Write an ANativeWindow to an AParcel.
+ *
+ * Available since API level 34.
+ *
+ * \return STATUS_OK on success.
+ * STATUS_BAD_VALUE if either buffer or parcel is null, or if the ANativeWindow*
+ * fails to serialize (eg, internally corrupted)
+ * STATUS_NO_MEMORY if the parcel runs out of space to store the buffer & is
+ * unable to allocate more
+ * STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t ANativeWindow_writeToParcel(ANativeWindow* _Nonnull window,
+ AParcel* _Nonnull parcel) __INTRODUCED_IN(__ANDROID_API_U__);
+
+__END_DECLS
+
+// Only enable the AIDL glue helper if this is C++
+#ifdef __cplusplus
+
+namespace aidl::android::hardware {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * Takes ownership of the ANativeWindow* given to it in reset() and will automatically
+ * destroy it in the destructor, similar to a smart pointer container
+ */
+class NativeWindow {
+public:
+ NativeWindow() noexcept {}
+ explicit NativeWindow(ANativeWindow* _Nullable window) {
+ reset(window);
+ }
+
+ explicit NativeWindow(NativeWindow&& other) noexcept {
+ mWindow = other.release(); // steal ownership from r-value
+ }
+
+ ~NativeWindow() {
+ reset();
+ }
+
+ binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+ reset();
+ return ANativeWindow_readFromParcel(parcel, &mWindow);
+ }
+
+ binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+ if (!mWindow) {
+ return STATUS_BAD_VALUE;
+ }
+ return ANativeWindow_writeToParcel(mWindow, parcel);
+ }
+
+ /**
+ * Destroys any currently owned ANativeWindow* and takes ownership of the given
+ * ANativeWindow*
+ *
+ * @param buffer The buffer to take ownership of
+ */
+ void reset(ANativeWindow* _Nullable window = nullptr) noexcept {
+ if (mWindow) {
+ ANativeWindow_release(mWindow);
+ mWindow = nullptr;
+ }
+ if (window != nullptr) {
+ ANativeWindow_acquire(window);
+ }
+ mWindow = window;
+ }
+ inline ANativeWindow* _Nullable operator-> () const { return mWindow; }
+ inline ANativeWindow* _Nullable get() const { return mWindow; }
+ inline explicit operator bool () const { return mWindow != nullptr; }
+
+ NativeWindow& operator=(NativeWindow&& other) noexcept {
+ mWindow = other.release(); // steal ownership from r-value
+ return *this;
+ }
+
+ /**
+ * Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
+ * is released.
+ * @return ANativeWindow* or null if this was empty
+ */
+ [[nodiscard]] ANativeWindow* _Nullable release() noexcept {
+ ANativeWindow* _Nullable ret = mWindow;
+ mWindow = nullptr;
+ return ret;
+ }
+private:
+ ANativeWindow* _Nullable mWindow = nullptr;
+ NativeWindow(const NativeWindow &other) = delete;
+ NativeWindow& operator=(const NativeWindow &other) = delete;
+};
+
+} // aidl::android::hardware
+ //
+namespace aidl::android::view {
+ using Surface = aidl::android::hardware::NativeWindow;
+}
+
+#endif // __cplusplus
+
+#endif // ANDROID_NATIVE_WINDOW_AIDL_H
+
+/** @} */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index ce108b6..76d23fa 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -57,6 +57,8 @@
ANativeWindow_setUsage; # llndk
ANativeWindow_tryAllocateBuffers; # introduced=30
ANativeWindow_unlockAndPost;
+ ANativeWindow_readFromParcel; # introduced=UpsideDownCake
+ ANativeWindow_writeToParcel; # introduced=UpsideDownCake
local:
*;
};
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 9d9cb6b..f1fc0a4 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -19,6 +19,7 @@
#include <cutils/properties.h>
#include <log/log.h>
#include "gl/GLESRenderEngine.h"
+#include "renderengine/ExternalTexture.h"
#include "threaded/RenderEngineThreaded.h"
#include "skia/SkiaGLRenderEngine.h"
@@ -70,10 +71,22 @@
base::unique_fd&& bufferFence) {
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
+ updateProtectedContext(layers, buffer);
drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
std::move(bufferFence));
return resultFuture;
}
+void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer) {
+ const bool needsProtectedContext =
+ (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
+ std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
+ const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
+ return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+ });
+ useProtectedContext(needsProtectedContext);
+}
+
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 1ee5cba..1b34921 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -61,7 +61,7 @@
std::future<void> primeCache() override;
void genTextures(size_t count, uint32_t* names) override;
void deleteTextures(size_t count, uint32_t const* names) override;
- bool isProtected() const override { return mInProtectedContext; }
+ bool isProtected() const { return mInProtectedContext; }
bool supportsProtectedContent() const override;
void useProtectedContext(bool useProtectedContext) override;
void cleanupPostRender() override;
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 199392c..9182feb 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -126,12 +126,8 @@
// ----- BEGIN NEW INTERFACE -----
// queries that are required to be thread safe
- virtual bool isProtected() const = 0;
virtual bool supportsProtectedContent() const = 0;
- // Attempt to switch RenderEngine into and out of protectedContext mode
- virtual void useProtectedContext(bool useProtectedContext) = 0;
-
// Notify RenderEngine of changes to the dimensions of the active display
// so that it can configure its internal caches accordingly.
virtual void onActiveDisplaySizeChanged(ui::Size size) = 0;
@@ -238,6 +234,13 @@
friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
const RenderEngineType mRenderEngineType;
+ // Update protectedContext mode depending on whether or not any layer has a protected buffer.
+ void updateProtectedContext(const std::vector<LayerSettings>&,
+ const std::shared_ptr<ExternalTexture>&);
+
+ // Attempt to switch RenderEngine into and out of protectedContext mode
+ virtual void useProtectedContext(bool useProtectedContext) = 0;
+
virtual void drawLayersInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index e7c5b8f..1973c7d 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -68,7 +68,6 @@
std::future<void> primeCache() override final;
void cleanupPostRender() override final;
void cleanFramebufferCache() override final{ }
- bool isProtected() const override final{ return mInProtectedContext; }
bool supportsBackgroundBlur() override final {
return mBlurFilter != nullptr;
}
@@ -102,6 +101,8 @@
size_t getMaxViewportDims() const override final;
GrDirectContext* getActiveGrContext();
+ bool isProtected() const { return mInProtectedContext; }
+
// Implements PersistentCache as a way to monitor what SkSL shaders Skia has
// cached.
class SkSLCacheMonitor : public GrContextOptions::PersistentCache {
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 1a96289..fe3a16d 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -17,8 +17,10 @@
#include <cutils/properties.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <hardware/gralloc.h>
#include <renderengine/impl/ExternalTexture.h>
#include <renderengine/mock/RenderEngine.h>
+#include <ui/PixelFormat.h>
#include "../threaded/RenderEngineThreaded.h"
namespace android {
@@ -95,18 +97,6 @@
ASSERT_EQ(dims, result);
}
-TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) {
- EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
- status_t result = mThreadedRE->isProtected();
- ASSERT_EQ(false, result);
-}
-
-TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) {
- EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true));
- size_t result = mThreadedRE->isProtected();
- ASSERT_EQ(true, result);
-}
-
TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) {
EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false));
status_t result = mThreadedRE->supportsProtectedContent();
@@ -119,28 +109,6 @@
ASSERT_EQ(true, result);
}
-TEST_F(RenderEngineThreadedTest, useProtectedContext) {
- EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
- auto& ipExpect = EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
- EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true));
- EXPECT_CALL(*mRenderEngine, isProtected()).After(ipExpect).WillOnce(Return(true));
-
- mThreadedRE->useProtectedContext(true);
- ASSERT_EQ(true, mThreadedRE->isProtected());
-
- // call ANY synchronous function to ensure that useProtectedContext has completed.
- mThreadedRE->getContextPriority();
- ASSERT_EQ(true, mThreadedRE->isProtected());
-}
-
-TEST_F(RenderEngineThreadedTest, useProtectedContext_quickReject) {
- EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).Times(0);
- EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
- mThreadedRE->useProtectedContext(false);
- // call ANY synchronous function to ensure that useProtectedContext has completed.
- mThreadedRE->getContextPriority();
-}
-
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
@@ -182,6 +150,68 @@
base::unique_fd bufferFence;
+ EXPECT_CALL(*mRenderEngine, useProtectedContext(false));
+ EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+ .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ ASSERT_TRUE(future.valid());
+ auto result = future.get();
+ ASSERT_TRUE(result.ok());
+}
+
+TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) {
+ renderengine::DisplaySettings settings;
+ auto layerBuffer = sp<GraphicBuffer>::make();
+ layerBuffer->usage |= GRALLOC_USAGE_PROTECTED;
+ renderengine::LayerSettings layer;
+ layer.source.buffer.buffer = std::make_shared<
+ renderengine::impl::ExternalTexture>(std::move(layerBuffer), *mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::
+ READABLE);
+ std::vector<renderengine::LayerSettings> layers = {std::move(layer)};
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::make(), *mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+ base::unique_fd bufferFence;
+
+ EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
+ EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+ .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+ ASSERT_TRUE(future.valid());
+ auto result = future.get();
+ ASSERT_TRUE(result.ok());
+}
+
+TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) {
+ renderengine::DisplaySettings settings;
+ std::vector<renderengine::LayerSettings> layers;
+ auto graphicBuffer = sp<GraphicBuffer>::make();
+ graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED;
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(std::move(graphicBuffer), *mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+ base::unique_fd bufferFence;
+
+ EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
EXPECT_CALL(*mRenderEngine, drawLayersInternal)
.WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index b41e843..8aa41b3 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -90,7 +90,6 @@
}
mRenderEngine = factory();
- mIsProtected = mRenderEngine->isProtected();
pthread_setname_np(pthread_self(), mThreadName);
@@ -255,41 +254,11 @@
return mRenderEngine->getMaxViewportDims();
}
-bool RenderEngineThreaded::isProtected() const {
- waitUntilInitialized();
- std::lock_guard lock(mThreadMutex);
- return mIsProtected;
-}
-
bool RenderEngineThreaded::supportsProtectedContent() const {
waitUntilInitialized();
return mRenderEngine->supportsProtectedContent();
}
-void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) {
- if (isProtected() == useProtectedContext ||
- (useProtectedContext && !supportsProtectedContent())) {
- return;
- }
-
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::useProtectedContext");
- instance.useProtectedContext(useProtectedContext);
- if (instance.isProtected() != useProtectedContext) {
- ALOGE("Failed to switch RenderEngine context.");
- // reset the cached mIsProtected value to a good state, but this does not
- // prevent other callers of this method and isProtected from reading the
- // invalid cached value.
- mIsProtected = instance.isProtected();
- }
- });
- mIsProtected = useProtectedContext;
- }
- mCondition.notify_one();
-}
-
void RenderEngineThreaded::cleanupPostRender() {
if (canSkipPostRenderCleanup()) {
return;
@@ -334,6 +303,7 @@
mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
fd](renderengine::RenderEngine& instance) {
ATRACE_NAME("REThreaded::drawLayers");
+ instance.updateProtectedContext(layers, buffer);
instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
useFramebufferCache, base::unique_fd(fd));
});
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index bf2ebea..168e2d2 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -51,9 +51,7 @@
size_t getMaxTextureSize() const override;
size_t getMaxViewportDims() const override;
- bool isProtected() const override;
bool supportsProtectedContent() const override;
- void useProtectedContext(bool useProtectedContext) override;
void cleanupPostRender() override;
ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
@@ -84,6 +82,9 @@
void waitUntilInitialized() const;
static status_t setSchedFifo(bool enabled);
+ // No-op. This method is only called on leaf implementations of RenderEngine.
+ void useProtectedContext(bool) override {}
+
/* ------------------------------------------------------------------------
* Threading
*/
@@ -107,7 +108,6 @@
* Render Engine
*/
std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
- std::atomic<bool> mIsProtected = false;
};
} // namespace threaded
} // namespace renderengine
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9a6ebaa..f698217 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2092,9 +2092,8 @@
}
bool isSplit = shouldSplitTouch(tempTouchState, entry);
- const bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 &&
- (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source ||
- tempTouchState.displayId != displayId);
+ const bool switchedDevice = (oldState != nullptr) &&
+ (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source);
const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
@@ -2104,7 +2103,7 @@
const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
if (newGesture) {
bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
- if (switchedDevice && tempTouchState.down && !down && !isHoverAction) {
+ if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) {
ALOGI("Dropping event because a pointer for a different device is already down "
"in display %" PRId32,
displayId);
@@ -2113,10 +2112,8 @@
return touchedWindows; // wrong device
}
tempTouchState.reset();
- tempTouchState.down = down;
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
- tempTouchState.displayId = displayId;
isSplit = false;
} else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
ALOGI("Dropping move event because a pointer for a different device is already active "
@@ -2234,7 +2231,7 @@
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
// If the pointer is not currently down, then ignore the event.
- if (!tempTouchState.down) {
+ if (!tempTouchState.isDown()) {
if (DEBUG_FOCUS) {
ALOGD("Dropping event because the pointer is not down or we previously "
"dropped the pointer down event in display %" PRId32,
@@ -2445,7 +2442,7 @@
if (isHoverAction) {
// Started hovering, therefore no longer down.
- if (oldState && oldState->down) {
+ if (oldState && oldState->isDown()) {
if (DEBUG_FOCUS) {
ALOGD("Conflicting pointer actions: Hover received while pointer was "
"down.");
@@ -2457,7 +2454,6 @@
maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
- tempTouchState.displayId = displayId;
}
} else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
@@ -2465,7 +2461,7 @@
tempTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
// First pointer went down.
- if (oldState && oldState->down) {
+ if (oldState && oldState->isDown()) {
if (DEBUG_FOCUS) {
ALOGD("Conflicting pointer actions: Down received while already down.");
}
@@ -2501,7 +2497,7 @@
// Save changes unless the action was scroll in which case the temporary touch
// state was only valid for this one action.
if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
- if (tempTouchState.displayId >= 0) {
+ if (displayId >= 0) {
mTouchStatesByDisplay[displayId] = tempTouchState;
} else {
mTouchStatesByDisplay.erase(displayId);
@@ -5104,16 +5100,16 @@
mMaximumObscuringOpacityForTouch = opacity;
}
-std::pair<TouchState*, TouchedWindow*> InputDispatcher::findTouchStateAndWindowLocked(
- const sp<IBinder>& token) {
+std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
for (auto& [displayId, state] : mTouchStatesByDisplay) {
for (TouchedWindow& w : state.windows) {
if (w.windowHandle->getToken() == token) {
- return std::make_pair(&state, &w);
+ return std::make_tuple(&state, &w, displayId);
}
}
}
- return std::make_pair(nullptr, nullptr);
+ return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT);
}
bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
@@ -5129,13 +5125,12 @@
std::scoped_lock _l(mLock);
// Find the target touch state and touched window by fromToken.
- auto [state, touchedWindow] = findTouchStateAndWindowLocked(fromToken);
+ auto [state, touchedWindow, displayId] = findTouchStateWindowAndDisplayLocked(fromToken);
if (state == nullptr || touchedWindow == nullptr) {
ALOGD("Focus transfer failed because from window is not being touched.");
return false;
}
- const int32_t displayId = state->displayId;
sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
if (toWindowHandle == nullptr) {
ALOGW("Cannot transfer focus because to window not found.");
@@ -5324,11 +5319,9 @@
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
- for (const std::pair<int32_t, TouchState>& pair : mTouchStatesByDisplay) {
- const TouchState& state = pair.second;
- dump += StringPrintf(INDENT2 "%d: down=%s, deviceId=%d, source=0x%08x\n",
- state.displayId, toString(state.down), state.deviceId,
- state.source);
+ for (const auto& [displayId, state] : mTouchStatesByDisplay) {
+ dump += StringPrintf(INDENT2 "%d: deviceId=%d, source=0x%08x\n", displayId,
+ state.deviceId, state.source);
if (!state.windows.empty()) {
dump += INDENT3 "Windows:\n";
for (size_t i = 0; i < state.windows.size(); i++) {
@@ -5687,8 +5680,8 @@
return BAD_VALUE;
}
- auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token);
- if (statePtr == nullptr || windowPtr == nullptr || !statePtr->down) {
+ auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
+ if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) {
ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
" Ignoring.");
return BAD_VALUE;
@@ -5700,7 +5693,7 @@
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"input channel stole pointer stream");
options.deviceId = state.deviceId;
- options.displayId = state.displayId;
+ options.displayId = displayId;
options.pointerIds = window.pointerIds;
std::string canceledWindows;
for (const TouchedWindow& w : state.windows) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 3b44a8e..0ddbbeb 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -676,8 +676,8 @@
bool handled) REQUIRES(mLock);
// Find touched state and touched window by token.
- std::pair<TouchState*, TouchedWindow*> findTouchStateAndWindowLocked(const sp<IBinder>& token)
- REQUIRES(mLock);
+ std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+ findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
// Statistics gathering.
LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index eb31046..f5b7cb8 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -138,14 +138,9 @@
return nullptr;
}
-sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
- for (const TouchedWindow& touchedWindow : windows) {
- const auto& windowHandle = touchedWindow.windowHandle;
- if (windowHandle->getToken() == token) {
- return windowHandle;
- }
- }
- return nullptr;
+bool TouchState::isDown() const {
+ return std::any_of(windows.begin(), windows.end(),
+ [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index d324611..d1d3e9a 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -28,14 +28,10 @@
namespace inputdispatcher {
struct TouchState {
- bool down = false;
-
// id of the device that is currently down, others are rejected
int32_t deviceId = -1;
// source of the device that is current down, others are rejected
uint32_t source = 0;
- // id to the display that currently has a touch, others are rejected
- int32_t displayId = ADISPLAY_ID_NONE;
std::vector<TouchedWindow> windows;
@@ -59,7 +55,8 @@
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
- sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
+ // Whether any of the windows are currently being touched
+ bool isDown() const;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index a53fcd7..24168a1 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -55,6 +55,7 @@
"mapper/accumulator/CursorButtonAccumulator.cpp",
"mapper/accumulator/CursorScrollAccumulator.cpp",
"mapper/accumulator/HidUsageAccumulator.cpp",
+ "mapper/accumulator/MultiTouchMotionAccumulator.cpp",
"mapper/accumulator/SingleTouchMotionAccumulator.cpp",
"mapper/accumulator/TouchButtonAccumulator.cpp",
],
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index acba4f6..8e757a5 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -28,163 +28,6 @@
// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
static constexpr size_t MAX_SLOTS = 32;
-// --- MultiTouchMotionAccumulator ---
-
-MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
- : mCurrentSlot(-1),
- mUsingSlotsProtocol(false),
- mHaveStylus(false) {}
-
-void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
- bool usingSlotsProtocol) {
- mUsingSlotsProtocol = usingSlotsProtocol;
- mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
- mSlots = std::vector<Slot>(slotCount);
-
- mCurrentSlot = -1;
- if (mUsingSlotsProtocol) {
- // Query the driver for the current slot index and use it as the initial slot
- // before we start reading events from the device. It is possible that the
- // current slot index will not be the same as it was when the first event was
- // written into the evdev buffer, which means the input mapper could start
- // out of sync with the initial state of the events in the evdev buffer.
- // In the extremely unlikely case that this happens, the data from
- // two slots will be confused until the next ABS_MT_SLOT event is received.
- // This can cause the touch point to "jump", but at least there will be
- // no stuck touches.
- int32_t initialSlot;
- if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
- status == OK) {
- mCurrentSlot = initialSlot;
- } else {
- ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
- }
- }
-}
-
-void MultiTouchMotionAccumulator::resetSlots() {
- for (Slot& slot : mSlots) {
- slot.clear();
- }
- mCurrentSlot = -1;
-}
-
-void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
- if (rawEvent->type == EV_ABS) {
- bool newSlot = false;
- if (mUsingSlotsProtocol) {
- if (rawEvent->code == ABS_MT_SLOT) {
- mCurrentSlot = rawEvent->value;
- newSlot = true;
- }
- } else if (mCurrentSlot < 0) {
- mCurrentSlot = 0;
- }
-
- if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
- if (DEBUG_POINTERS) {
- if (newSlot) {
- ALOGW("MultiTouch device emitted invalid slot index %d but it "
- "should be between 0 and %zd; ignoring this slot.",
- mCurrentSlot, mSlots.size() - 1);
- }
- }
- } else {
- Slot& slot = mSlots[mCurrentSlot];
- // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
- // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while
- // updating the slot.
- if (!mUsingSlotsProtocol) {
- slot.mInUse = true;
- }
-
- switch (rawEvent->code) {
- case ABS_MT_POSITION_X:
- slot.mAbsMTPositionX = rawEvent->value;
- warnIfNotInUse(*rawEvent, slot);
- break;
- case ABS_MT_POSITION_Y:
- slot.mAbsMTPositionY = rawEvent->value;
- warnIfNotInUse(*rawEvent, slot);
- break;
- case ABS_MT_TOUCH_MAJOR:
- slot.mAbsMTTouchMajor = rawEvent->value;
- break;
- case ABS_MT_TOUCH_MINOR:
- slot.mAbsMTTouchMinor = rawEvent->value;
- slot.mHaveAbsMTTouchMinor = true;
- break;
- case ABS_MT_WIDTH_MAJOR:
- slot.mAbsMTWidthMajor = rawEvent->value;
- break;
- case ABS_MT_WIDTH_MINOR:
- slot.mAbsMTWidthMinor = rawEvent->value;
- slot.mHaveAbsMTWidthMinor = true;
- break;
- case ABS_MT_ORIENTATION:
- slot.mAbsMTOrientation = rawEvent->value;
- break;
- case ABS_MT_TRACKING_ID:
- if (mUsingSlotsProtocol && rawEvent->value < 0) {
- // The slot is no longer in use but it retains its previous contents,
- // which may be reused for subsequent touches.
- slot.mInUse = false;
- } else {
- slot.mInUse = true;
- slot.mAbsMTTrackingId = rawEvent->value;
- }
- break;
- case ABS_MT_PRESSURE:
- slot.mAbsMTPressure = rawEvent->value;
- break;
- case ABS_MT_DISTANCE:
- slot.mAbsMTDistance = rawEvent->value;
- break;
- case ABS_MT_TOOL_TYPE:
- slot.mAbsMTToolType = rawEvent->value;
- slot.mHaveAbsMTToolType = true;
- break;
- }
- }
- } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
- // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
- mCurrentSlot += 1;
- }
-}
-
-void MultiTouchMotionAccumulator::finishSync() {
- if (!mUsingSlotsProtocol) {
- resetSlots();
- }
-}
-
-bool MultiTouchMotionAccumulator::hasStylus() const {
- return mHaveStylus;
-}
-
-void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) {
- if (!slot.mInUse) {
- ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i",
- event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId);
- }
-}
-
-// --- MultiTouchMotionAccumulator::Slot ---
-
-int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
- if (mHaveAbsMTToolType) {
- switch (mAbsMTToolType) {
- case MT_TOOL_FINGER:
- return AMOTION_EVENT_TOOL_TYPE_FINGER;
- case MT_TOOL_PEN:
- return AMOTION_EVENT_TOOL_TYPE_STYLUS;
- case MT_TOOL_PALM:
- return AMOTION_EVENT_TOOL_TYPE_PALM;
- }
- }
- return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
-}
-
// --- MultiTouchInputMapper ---
MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext)
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 047e62d..ddf9e80 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -17,77 +17,10 @@
#pragma once
#include "TouchInputMapper.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
namespace android {
-/* Keeps track of the state of multi-touch protocol. */
-class MultiTouchMotionAccumulator {
-public:
- class Slot {
- public:
- inline bool isInUse() const { return mInUse; }
- inline int32_t getX() const { return mAbsMTPositionX; }
- inline int32_t getY() const { return mAbsMTPositionY; }
- inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
- inline int32_t getTouchMinor() const {
- return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor;
- }
- inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
- inline int32_t getToolMinor() const {
- return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor;
- }
- inline int32_t getOrientation() const { return mAbsMTOrientation; }
- inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
- inline int32_t getPressure() const { return mAbsMTPressure; }
- inline int32_t getDistance() const { return mAbsMTDistance; }
- inline int32_t getToolType() const;
-
- private:
- friend class MultiTouchMotionAccumulator;
-
- bool mInUse = false;
- bool mHaveAbsMTTouchMinor = false;
- bool mHaveAbsMTWidthMinor = false;
- bool mHaveAbsMTToolType = false;
-
- int32_t mAbsMTPositionX = 0;
- int32_t mAbsMTPositionY = 0;
- int32_t mAbsMTTouchMajor = 0;
- int32_t mAbsMTTouchMinor = 0;
- int32_t mAbsMTWidthMajor = 0;
- int32_t mAbsMTWidthMinor = 0;
- int32_t mAbsMTOrientation = 0;
- int32_t mAbsMTTrackingId = -1;
- int32_t mAbsMTPressure = 0;
- int32_t mAbsMTDistance = 0;
- int32_t mAbsMTToolType = 0;
-
- void clear() { *this = Slot(); }
- };
-
- MultiTouchMotionAccumulator();
-
- void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
- void process(const RawEvent* rawEvent);
- void finishSync();
- bool hasStylus() const;
-
- inline size_t getSlotCount() const { return mSlots.size(); }
- inline const Slot& getSlot(size_t index) const {
- LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
- return mSlots[index];
- }
-
-private:
- int32_t mCurrentSlot;
- std::vector<Slot> mSlots;
- bool mUsingSlotsProtocol;
- bool mHaveStylus;
-
- void resetSlots();
- void warnIfNotInUse(const RawEvent& event, const Slot& slot);
-};
-
class MultiTouchInputMapper : public TouchInputMapper {
public:
explicit MultiTouchInputMapper(InputDeviceContext& deviceContext);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 605b8f8..bf73ce5 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -437,6 +437,10 @@
mParameters.supportsUsi = false;
getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi",
mParameters.supportsUsi);
+
+ mParameters.enableForInactiveViewport = false;
+ getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
+ mParameters.enableForInactiveViewport);
}
void TouchInputMapper::dumpParameters(std::string& dump) {
@@ -454,6 +458,8 @@
dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi));
+ dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n",
+ toString(mParameters.enableForInactiveViewport));
}
void TouchInputMapper::configureRawPointerAxes() {
@@ -824,6 +830,8 @@
mDeviceMode = DeviceMode::POINTER;
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
+ } else {
+ mSource |= AINPUT_SOURCE_TOUCHPAD;
}
} else if (isTouchScreen()) {
mSource = AINPUT_SOURCE_TOUCHSCREEN;
@@ -856,7 +864,7 @@
"becomes available.",
getDeviceName().c_str());
mDeviceMode = DeviceMode::DISABLED;
- } else if (!newViewportOpt->isActive) {
+ } else if (!mParameters.enableForInactiveViewport && !newViewportOpt->isActive) {
ALOGI("Disabling %s (device %i) because the associated viewport is not active",
getDeviceName().c_str(), getDeviceId());
mDeviceMode = DeviceMode::DISABLED;
@@ -3559,6 +3567,8 @@
std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
uint32_t policyFlags, bool down,
bool hovering) {
+ LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
+ "%s cannot be used when the device is not in POINTER mode.", __func__);
std::list<NotifyArgs> out;
int32_t metaState = getContext()->getGlobalMetaState();
@@ -3685,6 +3695,10 @@
if (down || hovering) {
mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+ mPointerSimple.displayId = displayId;
+ mPointerSimple.source = mSource;
+ mPointerSimple.lastCursorX = xCursorPosition;
+ mPointerSimple.lastCursorY = yCursorPosition;
} else {
mPointerSimple.reset();
}
@@ -3693,10 +3707,25 @@
std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime,
uint32_t policyFlags) {
- mPointerSimple.currentCoords.clear();
- mPointerSimple.currentProperties.clear();
-
- return dispatchPointerSimple(when, readTime, policyFlags, false, false);
+ std::list<NotifyArgs> out;
+ if (mPointerSimple.down || mPointerSimple.hovering) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+ mPointerSimple.source, mPointerSimple.displayId, policyFlags,
+ AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+ metaState, mLastRawState.buttonState,
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+ &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
+ mPointerSimple.downTime,
+ /* videoFrames */ {}));
+ if (mPointerController != nullptr) {
+ mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+ }
+ }
+ mPointerSimple.reset();
+ return out;
}
NotifyMotionArgs TouchInputMapper::dispatchMotion(
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 68cdef0..c20f28b 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -236,6 +236,9 @@
// Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses.
bool supportsUsi;
+
+ // Allows touches while the display is off.
+ bool enableForInactiveViewport;
} mParameters;
// Immutable calibration parameters in parsed form.
@@ -678,6 +681,12 @@
// Time the pointer last went down.
nsecs_t downTime;
+ // Values reported for the last pointer event.
+ uint32_t source;
+ int32_t displayId;
+ float lastCursorX;
+ float lastCursorY;
+
void reset() {
currentCoords.clear();
currentProperties.clear();
@@ -686,6 +695,10 @@
down = false;
hovering = false;
downTime = 0;
+ source = 0;
+ displayId = ADISPLAY_ID_NONE;
+ lastCursorX = 0.f;
+ lastCursorY = 0.f;
}
} mPointerSimple;
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
new file mode 100644
index 0000000..b0cef67
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+// clang-format off
+#include "../Macros.h"
+// clang-format on
+#include "MultiTouchMotionAccumulator.h"
+
+namespace android {
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
+ : mCurrentSlot(-1), mUsingSlotsProtocol(false), mHaveStylus(false) {}
+
+void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
+ bool usingSlotsProtocol) {
+ mUsingSlotsProtocol = usingSlotsProtocol;
+ mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
+ mSlots = std::vector<Slot>(slotCount);
+
+ mCurrentSlot = -1;
+ if (mUsingSlotsProtocol) {
+ // Query the driver for the current slot index and use it as the initial slot before we
+ // start reading events from the device. It is possible that the current slot index will
+ // not be the same as it was when the first event was written into the evdev buffer, which
+ // means the input mapper could start out of sync with the initial state of the events in
+ // the evdev buffer. In the extremely unlikely case that this happens, the data from two
+ // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
+ // touch point to "jump", but at least there will be no stuck touches.
+ int32_t initialSlot;
+ if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+ status == OK) {
+ mCurrentSlot = initialSlot;
+ } else {
+ ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
+ }
+ }
+}
+
+void MultiTouchMotionAccumulator::resetSlots() {
+ for (Slot& slot : mSlots) {
+ slot.clear();
+ }
+ mCurrentSlot = -1;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+ bool newSlot = false;
+ if (mUsingSlotsProtocol) {
+ if (rawEvent->code == ABS_MT_SLOT) {
+ mCurrentSlot = rawEvent->value;
+ newSlot = true;
+ }
+ } else if (mCurrentSlot < 0) {
+ mCurrentSlot = 0;
+ }
+
+ if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
+ if (newSlot) {
+ ALOGW_IF(DEBUG_POINTERS,
+ "MultiTouch device emitted invalid slot index %d but it "
+ "should be between 0 and %zd; ignoring this slot.",
+ mCurrentSlot, mSlots.size() - 1);
+ }
+ } else {
+ Slot& slot = mSlots[mCurrentSlot];
+ // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
+ // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while
+ // updating the slot.
+ if (!mUsingSlotsProtocol) {
+ slot.mInUse = true;
+ }
+
+ switch (rawEvent->code) {
+ case ABS_MT_POSITION_X:
+ slot.mAbsMtPositionX = rawEvent->value;
+ warnIfNotInUse(*rawEvent, slot);
+ break;
+ case ABS_MT_POSITION_Y:
+ slot.mAbsMtPositionY = rawEvent->value;
+ warnIfNotInUse(*rawEvent, slot);
+ break;
+ case ABS_MT_TOUCH_MAJOR:
+ slot.mAbsMtTouchMajor = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ slot.mAbsMtTouchMinor = rawEvent->value;
+ slot.mHaveAbsMtTouchMinor = true;
+ break;
+ case ABS_MT_WIDTH_MAJOR:
+ slot.mAbsMtWidthMajor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MINOR:
+ slot.mAbsMtWidthMinor = rawEvent->value;
+ slot.mHaveAbsMtWidthMinor = true;
+ break;
+ case ABS_MT_ORIENTATION:
+ slot.mAbsMtOrientation = rawEvent->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (mUsingSlotsProtocol && rawEvent->value < 0) {
+ // The slot is no longer in use but it retains its previous contents,
+ // which may be reused for subsequent touches.
+ slot.mInUse = false;
+ } else {
+ slot.mInUse = true;
+ slot.mAbsMtTrackingId = rawEvent->value;
+ }
+ break;
+ case ABS_MT_PRESSURE:
+ slot.mAbsMtPressure = rawEvent->value;
+ break;
+ case ABS_MT_DISTANCE:
+ slot.mAbsMtDistance = rawEvent->value;
+ break;
+ case ABS_MT_TOOL_TYPE:
+ slot.mAbsMtToolType = rawEvent->value;
+ slot.mHaveAbsMtToolType = true;
+ break;
+ }
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+ // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+ mCurrentSlot += 1;
+ }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+ if (!mUsingSlotsProtocol) {
+ resetSlots();
+ }
+}
+
+bool MultiTouchMotionAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) {
+ if (!slot.mInUse) {
+ ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i",
+ event.code, event.value, mCurrentSlot, slot.mAbsMtTrackingId);
+ }
+}
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+ if (mHaveAbsMtToolType) {
+ switch (mAbsMtToolType) {
+ case MT_TOOL_FINGER:
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ case MT_TOOL_PEN:
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ case MT_TOOL_PALM:
+ return AMOTION_EVENT_TOOL_TYPE_PALM;
+ }
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
new file mode 100644
index 0000000..625a00f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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 <linux/input-event-codes.h>
+#include <stdint.h>
+#include <vector>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+
+namespace android {
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+ class Slot {
+ public:
+ inline bool isInUse() const { return mInUse; }
+ inline int32_t getX() const { return mAbsMtPositionX; }
+ inline int32_t getY() const { return mAbsMtPositionY; }
+ inline int32_t getTouchMajor() const { return mAbsMtTouchMajor; }
+ inline int32_t getTouchMinor() const {
+ return mHaveAbsMtTouchMinor ? mAbsMtTouchMinor : mAbsMtTouchMajor;
+ }
+ inline int32_t getToolMajor() const { return mAbsMtWidthMajor; }
+ inline int32_t getToolMinor() const {
+ return mHaveAbsMtWidthMinor ? mAbsMtWidthMinor : mAbsMtWidthMajor;
+ }
+ inline int32_t getOrientation() const { return mAbsMtOrientation; }
+ inline int32_t getTrackingId() const { return mAbsMtTrackingId; }
+ inline int32_t getPressure() const { return mAbsMtPressure; }
+ inline int32_t getDistance() const { return mAbsMtDistance; }
+ int32_t getToolType() const;
+
+ private:
+ friend class MultiTouchMotionAccumulator;
+
+ bool mInUse = false;
+ bool mHaveAbsMtTouchMinor = false;
+ bool mHaveAbsMtWidthMinor = false;
+ bool mHaveAbsMtToolType = false;
+
+ int32_t mAbsMtPositionX = 0;
+ int32_t mAbsMtPositionY = 0;
+ int32_t mAbsMtTouchMajor = 0;
+ int32_t mAbsMtTouchMinor = 0;
+ int32_t mAbsMtWidthMajor = 0;
+ int32_t mAbsMtWidthMinor = 0;
+ int32_t mAbsMtOrientation = 0;
+ int32_t mAbsMtTrackingId = -1;
+ int32_t mAbsMtPressure = 0;
+ int32_t mAbsMtDistance = 0;
+ int32_t mAbsMtToolType = 0;
+
+ void clear() { *this = Slot(); }
+ };
+
+ MultiTouchMotionAccumulator();
+
+ void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+ bool hasStylus() const;
+
+ inline size_t getSlotCount() const { return mSlots.size(); }
+ inline const Slot& getSlot(size_t index) const {
+ LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index);
+ return mSlots[index];
+ }
+
+private:
+ int32_t mCurrentSlot;
+ std::vector<Slot> mSlots;
+ bool mUsingSlotsProtocol;
+ bool mHaveStylus;
+
+ void resetSlots();
+ void warnIfNotInUse(const RawEvent& event, const Slot& slot);
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 5d5bee7..1891205 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -108,13 +108,13 @@
}
void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) {
- int32_t outKeyCode, outMetaState;
- uint32_t outFlags;
+ int32_t keyCode, metaState;
+ uint32_t flags;
if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(),
- 0 /*metaState*/, &outKeyCode, &outMetaState, &outFlags) != OK) {
+ 0 /*metaState*/, &keyCode, &metaState, &flags) != OK) {
return;
}
- switch (outKeyCode) {
+ switch (keyCode) {
case AKEYCODE_STYLUS_BUTTON_PRIMARY:
mBtnStylus = down;
break;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 6a174d0..879d36e 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2379,7 +2379,7 @@
// An invalid input device that is only used for this test.
class InvalidUinputDevice : public UinputDevice {
public:
- InvalidUinputDevice() : UinputDevice("Invalid Device") {}
+ InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {}
private:
void configureDevice(int fd, uinput_user_dev* device) override {}
@@ -2831,6 +2831,96 @@
WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
}
+TEST_F(TouchIntegrationTest, StylusButtonsSurroundingTouchGesture) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Press the stylus button.
+ mDevice->sendKey(BTN_STYLUS, 1);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+
+ // Start and finish a stylus gesture.
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+ mDevice->sendTrackingId(INVALID_TRACKING_ID);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+
+ // Release the stylus button.
+ mDevice->sendKey(BTN_STYLUS, 0);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+}
+
+TEST_F(TouchIntegrationTest, StylusButtonsWithinTouchGesture) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Start a stylus gesture.
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendToolType(MT_TOOL_PEN);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+
+ // Press and release a stylus button. Each change in button state also generates a MOVE event.
+ mDevice->sendKey(BTN_STYLUS, 1);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+ WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+ mDevice->sendKey(BTN_STYLUS, 0);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+ WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+
+ // Finish the stylus gesture.
+ mDevice->sendTrackingId(INVALID_TRACKING_ID);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0))));
+}
+
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
@@ -5773,7 +5863,7 @@
prepareAxes(POSITION);
SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
}
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
@@ -9147,8 +9237,8 @@
prepareAxes(POSITION);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
- // Check source is mouse that would obtain the PointerController.
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+ // Check source is a touchpad that would obtain the PointerController.
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
NotifyMotionArgs motionArgs;
processPosition(mapper, 100, 100);
@@ -9191,6 +9281,7 @@
*/
TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) {
addConfigurationProperty("touch.deviceType", "touchScreen");
+ // Don't set touch.enableForInactiveViewport to verify the default behavior.
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT,
ViewportType::INTERNAL);
@@ -9205,8 +9296,31 @@
mFakeListener->assertNotifyMotionWasNotCalled();
}
+/**
+ * When the viewport is not active (isActive=false) and touch.enableForInactiveViewport is true,
+ * the touch mapper can process the events and the events can be delivered to the listener.
+ */
+TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ addConfigurationProperty("touch.enableForInactiveViewport", "1");
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT,
+ ViewportType::INTERNAL);
+ configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ prepareAxes(POSITION);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+ NotifyMotionArgs motionArgs;
+ processPosition(mapper, 100, 100);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+}
+
TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) {
addConfigurationProperty("touch.deviceType", "touchScreen");
+ addConfigurationProperty("touch.enableForInactiveViewport", "0");
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT,
ViewportType::INTERNAL);
@@ -10151,11 +10265,11 @@
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
- // non captured touchpad should be a mouse source
+ // A non captured touchpad should have a mouse and touchpad source.
mFakePolicy->setPointerCapture(false);
configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
}
TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
@@ -10214,10 +10328,10 @@
mFakePolicy->setPointerCapture(false);
MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
- // uncaptured touchpad should be a pointer device
- ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+ // An uncaptured touchpad should be a pointer device, with additional touchpad source.
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
- // captured touchpad should be a touchpad device
+ // A captured touchpad should just have a touchpad source.
mFakePolicy->setPointerCapture(true);
configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
@@ -10579,6 +10693,46 @@
ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0);
}
+TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+ preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ // Start a stylus gesture.
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processId(mapper, FIRST_TRACKING_ID);
+ processPosition(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+ // TODO(b/257078296): Pointer mode generates extra event.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus
+ // gesture should be disabled.
+ auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+ viewport->isActive = false;
+ mFakePolicy->updateViewport(*viewport);
+ configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+ WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+ // TODO(b/257078296): Pointer mode generates extra event.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+ WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+ WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
// --- JoystickInputMapperTest ---
class JoystickInputMapperTest : public InputMapperTest {
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index c4830dc..bc695b8 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -24,7 +24,8 @@
// --- UinputDevice ---
-UinputDevice::UinputDevice(const char* name) : mName(name) {}
+UinputDevice::UinputDevice(const char* name, int16_t productId)
+ : mName(name), mProductId(productId) {}
UinputDevice::~UinputDevice() {
if (ioctl(mDeviceFd, UI_DEV_DESTROY)) {
@@ -43,7 +44,7 @@
strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE);
device.id.bustype = BUS_USB;
device.id.vendor = 0x01;
- device.id.product = 0x01;
+ device.id.product = mProductId;
device.id.version = 1;
ASSERT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device));
@@ -76,8 +77,8 @@
// --- UinputKeyboard ---
-UinputKeyboard::UinputKeyboard(const char* name, std::initializer_list<int> keys)
- : UinputDevice(name), mKeys(keys.begin(), keys.end()) {}
+UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list<int> keys)
+ : UinputDevice(name, productId), mKeys(keys.begin(), keys.end()) {}
void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) {
// enable key press/release event
@@ -121,23 +122,26 @@
// --- UinputHomeKey ---
-UinputHomeKey::UinputHomeKey() : UinputKeyboard("Test Uinput Home Key", {KEY_HOME}) {}
+UinputHomeKey::UinputHomeKey() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {KEY_HOME}) {}
void UinputHomeKey::pressAndReleaseHomeKey() {
pressAndReleaseKey(KEY_HOME);
}
-// --- UinputSteamController
-UinputSteamController::UinputSteamController()
- : UinputKeyboard("Test Uinput Steam Controller", {BTN_GEAR_DOWN, BTN_GEAR_UP}) {}
+// --- UinputSteamController ---
-// --- UinputExternalStylus
+UinputSteamController::UinputSteamController()
+ : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_GEAR_DOWN, BTN_GEAR_UP}) {}
+
+// --- UinputExternalStylus ---
+
UinputExternalStylus::UinputExternalStylus()
- : UinputKeyboard("Test Uinput External Stylus", {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}
+ : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}
// --- UinputTouchScreen ---
-UinputTouchScreen::UinputTouchScreen(const Rect* size)
- : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {}
+
+UinputTouchScreen::UinputTouchScreen(const Rect& size)
+ : UinputDevice(DEVICE_NAME, PRODUCT_ID), mSize(size) {}
void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) {
// Setup the touch screen device
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index 53dcfd0..d661bd3 100644
--- a/services/inputflinger/tests/UinputDevice.h
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -32,7 +32,7 @@
template <class D, class... Ts>
std::unique_ptr<D> createUinputDevice(Ts... args) {
// Using `new` to access non-public constructors.
- std::unique_ptr<D> dev(new D(&args...));
+ std::unique_ptr<D> dev(new D(args...));
EXPECT_NO_FATAL_FAILURE(dev->init());
return dev;
}
@@ -51,8 +51,9 @@
protected:
const char* mName;
+ const int16_t mProductId;
- UinputDevice(const char* name);
+ explicit UinputDevice(const char* name, int16_t productId);
// Signals which types of events this device supports before it is created.
// This must be overridden by subclasses.
@@ -71,7 +72,8 @@
class UinputKeyboard : public UinputDevice {
public:
- static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device";
+ static constexpr const char* KEYBOARD_NAME = "Test Uinput Keyboard Device";
+ static constexpr int16_t PRODUCT_ID = 42;
// Injects key press and sync.
void pressKey(int key);
@@ -84,7 +86,8 @@
friend std::unique_ptr<D> createUinputDevice(Ts... args);
protected:
- UinputKeyboard(const char* name, std::initializer_list<int> keys = {});
+ explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID,
+ std::initializer_list<int> keys = {});
private:
void configureDevice(int fd, uinput_user_dev* device) override;
@@ -97,6 +100,9 @@
// A keyboard device that has a single HOME key.
class UinputHomeKey : public UinputKeyboard {
public:
+ static constexpr const char* DEVICE_NAME = "Test Uinput Home Key";
+ static constexpr int16_t PRODUCT_ID = 43;
+
// Injects 4 events: key press, sync, key release, and sync.
void pressAndReleaseHomeKey();
@@ -104,34 +110,47 @@
friend std::unique_ptr<D> createUinputDevice(Ts... args);
private:
- UinputHomeKey();
+ explicit UinputHomeKey();
};
+// --- UinputSteamController ---
+
// A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key.
class UinputSteamController : public UinputKeyboard {
public:
+ static constexpr const char* DEVICE_NAME = "Test Uinput Steam Controller";
+ static constexpr int16_t PRODUCT_ID = 44;
+
template <class D, class... Ts>
friend std::unique_ptr<D> createUinputDevice(Ts... args);
private:
- UinputSteamController();
+ explicit UinputSteamController();
};
+// --- UinputExternalStylus ---
+
// A stylus that reports button presses.
class UinputExternalStylus : public UinputKeyboard {
public:
+ static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus";
+ static constexpr int16_t PRODUCT_ID = 45;
+
template <class D, class... Ts>
friend std::unique_ptr<D> createUinputDevice(Ts... args);
private:
- UinputExternalStylus();
+ explicit UinputExternalStylus();
};
// --- UinputTouchScreen ---
-// A touch screen device with specific size.
+
+// A multi-touch touchscreen device with specific size that also supports styluses.
class UinputTouchScreen : public UinputDevice {
public:
- static constexpr const char* DEVICE_NAME = "Test Touch Screen";
+ static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen";
+ static constexpr int16_t PRODUCT_ID = 46;
+
static const int32_t RAW_TOUCH_MIN = 0;
static const int32_t RAW_TOUCH_MAX = 31;
static const int32_t RAW_ID_MIN = 0;
@@ -157,7 +176,7 @@
const Point getCenterPoint();
protected:
- UinputTouchScreen(const Rect* size);
+ explicit UinputTouchScreen(const Rect& size);
private:
void configureDevice(int fd, uinput_user_dev* device) override;
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index b7de619..7fb33e5 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -40,7 +40,7 @@
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V3-cpp",
+ "android.hardware.power-V4-cpp",
],
cflags: [
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index 0286a81..4343aec 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -40,7 +40,7 @@
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V3-cpp",
+ "android.hardware.power-V4-cpp",
],
static_libs: [
"libtestUtil",
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index eec6801..54dffcf 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -51,7 +51,7 @@
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V3-cpp",
+ "android.hardware.power-V4-cpp",
],
static_libs: [
"libgmock",
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 892c07d..14fdd12 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -49,7 +49,7 @@
"android.hardware.graphics.composer@2.4",
"android.hardware.power@1.0",
"android.hardware.power@1.3",
- "android.hardware.power-V2-cpp",
+ "android.hardware.power-V4-cpp",
"libbase",
"libbinder",
"libbinder_ndk",
@@ -189,6 +189,7 @@
"Scheduler/VsyncConfiguration.cpp",
"Scheduler/VsyncModulator.cpp",
"Scheduler/VsyncSchedule.cpp",
+ "ScreenCaptureOutput.cpp",
"StartPropertySetThread.cpp",
"SurfaceFlinger.cpp",
"SurfaceFlingerDefaultFactory.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 0ae8bf9..c1460cf 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -25,7 +25,7 @@
"android.hardware.graphics.composer@2.4",
"android.hardware.power@1.0",
"android.hardware.power@1.3",
- "android.hardware.power-V2-cpp",
+ "android.hardware.power-V4-cpp",
"libbase",
"libcutils",
"libgui",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 8661063..7c10fa5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -58,7 +58,7 @@
virtual renderengine::RenderEngine& getRenderEngine() const = 0;
virtual void setRenderEngine(renderengine::RenderEngine*) = 0;
- virtual TimeStats& getTimeStats() const = 0;
+ virtual TimeStats* getTimeStats() const = 0;
virtual void setTimeStats(const std::shared_ptr<TimeStats>&) = 0;
virtual bool needsAnotherUpdate() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index 2af6c80..c699557 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -36,7 +36,7 @@
renderengine::RenderEngine& getRenderEngine() const override;
void setRenderEngine(renderengine::RenderEngine*) override;
- TimeStats& getTimeStats() const override;
+ TimeStats* getTimeStats() const override;
void setTimeStats(const std::shared_ptr<TimeStats>&) override;
bool needsAnotherUpdate() const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 23d5570..e06da33 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -134,9 +134,11 @@
void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
bool getSkipColorTransform() const override;
compositionengine::Output::FrameFences presentAndGetFrameFences() override;
+ virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
bool supportsProtectedContent, ui::Dataspace outputDataspace,
std::vector<LayerFE*> &outLayerFEs) override;
+ virtual bool layerNeedsFiltering(const OutputLayer*) const;
void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
void setExpensiveRenderingExpected(bool enabled) override;
void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index fc71649..9b2387b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -42,7 +42,7 @@
MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&());
MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*));
- MOCK_CONST_METHOD0(getTimeStats, TimeStats&());
+ MOCK_CONST_METHOD0(getTimeStats, TimeStats*());
MOCK_METHOD1(setTimeStats, void(const std::shared_ptr<TimeStats>&));
MOCK_CONST_METHOD0(needsAnotherUpdate, bool());
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index e42f11a..15fadbc 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -72,8 +72,8 @@
mRenderEngine = renderEngine;
}
-TimeStats& CompositionEngine::getTimeStats() const {
- return *mTimeStats.get();
+TimeStats* CompositionEngine::getTimeStats() const {
+ return mTimeStats.get();
}
void CompositionEngine::setTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 0622534..d1daca6 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1178,15 +1178,9 @@
bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) {
return layer->getLayerFE().getCompositionState()->hasProtectedContent;
});
- if (needsProtected != renderEngine.isProtected()) {
- renderEngine.useProtectedContext(needsProtected);
- }
- if (needsProtected != mRenderSurface->isProtected() &&
- needsProtected == renderEngine.isProtected()) {
+ if (needsProtected != mRenderSurface->isProtected()) {
mRenderSurface->setProtected(needsProtected);
}
- } else if (!outputState.isSecure && renderEngine.isProtected()) {
- renderEngine.useProtectedContext(false);
}
}
@@ -1232,40 +1226,8 @@
ALOGV("hasClientComposition");
- renderengine::DisplaySettings clientCompositionDisplay;
- clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent();
- clientCompositionDisplay.clip = outputState.layerStackSpace.getContent();
- clientCompositionDisplay.orientation =
- ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation());
- clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut()
- ? outputState.dataspace
- : ui::Dataspace::UNKNOWN;
-
- // If we have a valid current display brightness use that, otherwise fall back to the
- // display's max desired
- clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
- ? outputState.displayBrightnessNits
- : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
- clientCompositionDisplay.maxLuminance =
- mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
- clientCompositionDisplay.targetLuminanceNits =
- outputState.clientTargetBrightness * outputState.displayBrightnessNits;
- clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
- clientCompositionDisplay.renderIntent =
- static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
- outputState.renderIntent);
-
- // Compute the global color transform matrix.
- clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
- for (auto& info : outputState.borderInfoList) {
- renderengine::BorderRenderInfo borderInfo;
- borderInfo.width = info.width;
- borderInfo.color = info.color;
- borderInfo.combinedRegion = info.combinedRegion;
- clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
- }
- clientCompositionDisplay.deviceHandlesColorTransform =
- outputState.usesDeviceComposition || getSkipColorTransform();
+ renderengine::DisplaySettings clientCompositionDisplay =
+ generateClientCompositionDisplaySettings();
// Generate the client composition requests for the layers on this output.
auto& renderEngine = getCompositionEngine().getRenderEngine();
@@ -1340,10 +1302,13 @@
const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
- if (auto& timeStats = getCompositionEngine().getTimeStats(); fence->isValid()) {
- timeStats.recordRenderEngineDuration(renderEngineStart, std::make_shared<FenceTime>(fence));
- } else {
- timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
+ if (auto timeStats = getCompositionEngine().getTimeStats()) {
+ if (fence->isValid()) {
+ timeStats->recordRenderEngineDuration(renderEngineStart,
+ std::make_shared<FenceTime>(fence));
+ } else {
+ timeStats->recordRenderEngineDuration(renderEngineStart, systemTime());
+ }
}
for (auto* clientComposedLayer : clientCompositionLayersFE) {
@@ -1353,6 +1318,46 @@
return base::unique_fd(fence->dup());
}
+renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const {
+ const auto& outputState = getState();
+
+ renderengine::DisplaySettings clientCompositionDisplay;
+ clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent();
+ clientCompositionDisplay.clip = outputState.layerStackSpace.getContent();
+ clientCompositionDisplay.orientation =
+ ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation());
+ clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut()
+ ? outputState.dataspace
+ : ui::Dataspace::UNKNOWN;
+
+ // If we have a valid current display brightness use that, otherwise fall back to the
+ // display's max desired
+ clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
+ ? outputState.displayBrightnessNits
+ : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+ clientCompositionDisplay.maxLuminance =
+ mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+ clientCompositionDisplay.targetLuminanceNits =
+ outputState.clientTargetBrightness * outputState.displayBrightnessNits;
+ clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
+ clientCompositionDisplay.renderIntent =
+ static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
+ outputState.renderIntent);
+
+ // Compute the global color transform matrix.
+ clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
+ for (auto& info : outputState.borderInfoList) {
+ renderengine::BorderRenderInfo borderInfo;
+ borderInfo.width = info.width;
+ borderInfo.color = info.color;
+ borderInfo.combinedRegion = info.combinedRegion;
+ clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
+ }
+ clientCompositionDisplay.deviceHandlesColorTransform =
+ outputState.usesDeviceComposition || getSkipColorTransform();
+ return clientCompositionDisplay;
+}
+
std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector<LayerFE*>& outLayerFEs) {
std::vector<LayerFE::LayerSettings> clientCompositionLayers;
@@ -1418,7 +1423,7 @@
Enabled);
compositionengine::LayerFE::ClientCompositionTargetSettings
targetSettings{.clip = clip,
- .needsFiltering = layer->needsFiltering() ||
+ .needsFiltering = layerNeedsFiltering(layer) ||
outputState.needsFiltering,
.isSecure = outputState.isSecure,
.supportsProtectedContent = supportsProtectedContent,
@@ -1449,6 +1454,10 @@
return clientCompositionLayers;
}
+bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
+ return layer->needsFiltering();
+}
+
void Output::appendRegionFlashRequests(
const Region& flashRegion, std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
if (flashRegion.isEmpty()) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 8d99f89..60ed660 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -71,7 +71,7 @@
TEST_F(CompositionEngineTest, canSetTimeStats) {
mEngine.setTimeStats(mTimeStats);
- EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats());
+ EXPECT_EQ(mTimeStats.get(), mEngine.getTimeStats());
}
/*
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 514a8ff..2109987 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3332,8 +3332,7 @@
EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
- EXPECT_CALL(mCompositionEngine, getTimeStats())
- .WillRepeatedly(ReturnRef(*mTimeStats.get()));
+ EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
.WillRepeatedly(ReturnRef(kHdrCapabilities));
}
@@ -4010,39 +4009,11 @@
Layer mLayer2;
};
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) {
- mOutput.mState.isSecure = false;
- mLayer2.mLayerFEState.hasProtectedContent = true;
- EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
- EXPECT_CALL(mRenderEngine, useProtectedContext(false));
-
- base::unique_fd fd;
- std::shared_ptr<renderengine::ExternalTexture> tex;
- mOutput.updateProtectedContentState();
- mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) {
- mOutput.mState.isSecure = true;
- mLayer2.mLayerFEState.hasProtectedContent = true;
- EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
-
- base::unique_fd fd;
- std::shared_ptr<renderengine::ExternalTexture> tex;
- mOutput.updateProtectedContentState();
- mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
mOutput.mState.isSecure = true;
mLayer2.mLayerFEState.hasProtectedContent = false;
EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false));
EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
- EXPECT_CALL(mRenderEngine, useProtectedContext(false));
EXPECT_CALL(*mRenderSurface, setProtected(false));
base::unique_fd fd;
@@ -4060,10 +4031,7 @@
// For this test, we also check the call order of key functions.
InSequence seq;
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
- EXPECT_CALL(mRenderEngine, useProtectedContext(true));
EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
EXPECT_CALL(*mRenderSurface, setProtected(true));
// Must happen after setting the protected content state.
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
@@ -4081,7 +4049,6 @@
mOutput.mState.isSecure = true;
mLayer2.mLayerFEState.hasProtectedContent = true;
EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
base::unique_fd fd;
@@ -4091,43 +4058,11 @@
mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
}
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) {
- mOutput.mState.isSecure = true;
- mLayer2.mLayerFEState.hasProtectedContent = true;
- EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false));
- EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
- EXPECT_CALL(mRenderEngine, useProtectedContext(true));
-
- base::unique_fd fd;
- std::shared_ptr<renderengine::ExternalTexture> tex;
- mOutput.updateProtectedContentState();
- mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) {
- mOutput.mState.isSecure = true;
- mLayer2.mLayerFEState.hasProtectedContent = true;
- EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true));
- EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
- EXPECT_CALL(*mRenderSurface, setProtected(true));
-
- base::unique_fd fd;
- std::shared_ptr<renderengine::ExternalTexture> tex;
- mOutput.updateProtectedContentState();
- mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
mOutput.mState.isSecure = true;
mLayer2.mLayerFEState.hasProtectedContent = true;
EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
- EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
- EXPECT_CALL(mRenderEngine, useProtectedContext(true));
base::unique_fd fd;
std::shared_ptr<renderengine::ExternalTexture> tex;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 18ddfbc..9868c8e 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -313,26 +313,10 @@
return sPrimaryDisplayRotationFlags;
}
-std::string DisplayDevice::getDebugName() const {
- using namespace std::string_literals;
-
- std::string name = "Display "s + to_string(getId()) + " ("s;
-
- name += isVirtual() ? "virtual"s : "physical"s;
-
- if (isPrimary()) {
- name += ", primary"s;
- }
-
- return name + ", \""s + mDisplayName + "\")"s;
-}
-
void DisplayDevice::dump(utils::Dumper& dumper) const {
using namespace std::string_view_literals;
- dumper.dump({}, getDebugName());
-
- utils::Dumper::Indent indent(dumper);
+ dumper.dump("name"sv, '"' + mDisplayName + '"');
dumper.dump("powerMode"sv, mPowerMode);
if (mRefreshRateSelector) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 6c848bb..1602a71 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -248,10 +248,6 @@
// release HWC resources (if any) for removable displays
void disconnect();
- /* ------------------------------------------------------------------------
- * Debugging
- */
- std::string getDebugName() const;
void dump(utils::Dumper&) const;
private:
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 33a7bca..8238828 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -178,7 +178,8 @@
}
// For utils::Dumper ADL.
-namespace hardware::graphics::composer::V2_2 {
+namespace hardware::graphics::composer {
+namespace V2_2 {
inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) {
switch (mode) {
@@ -197,7 +198,9 @@
}
}
-} // namespace hardware::graphics::composer::V2_2
+} // namespace V2_2
+
+namespace V2_1 {
inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) {
switch (vsync) {
@@ -210,4 +213,6 @@
}
}
+} // namespace V2_1
+} // namespace hardware::graphics::composer
} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 3478eaa..2a18521 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -472,7 +472,7 @@
snapshot->geomLayerTransform = getTransform();
snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse();
snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState);
-
+ snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse();
snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
snapshot->alpha = alpha;
snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
@@ -3499,6 +3499,12 @@
return mSnapshot.get();
}
+sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
+ auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName());
+ result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
+ return result;
+}
+
void Layer::useSurfaceDamage() {
if (mFlinger->mForceFullDamage) {
surfaceDamageRegion = Region::INVALID_REGION;
@@ -3965,14 +3971,17 @@
}
LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
- if (mLayer) {
- mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
- }
+ LOG_ALWAYS_FATAL_IF(!mLayer, "LayerSnapshotGuard received a null layer.");
+ mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
+ LOG_ALWAYS_FATAL_IF(!mLayer->mLayerFE->mSnapshot,
+ "LayerFE snapshot null after taking ownership from layer");
}
LayerSnapshotGuard::~LayerSnapshotGuard() {
if (mLayer) {
mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot);
+ LOG_ALWAYS_FATAL_IF(!mLayer->mSnapshot,
+ "Layer snapshot null after taking ownership from LayerFE");
}
}
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index a3c4e59..9585fa9 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -323,6 +323,7 @@
ui::Dataspace getRequestedDataSpace() const;
virtual sp<LayerFE> getCompositionEngineLayerFE() const;
+ virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
const LayerSnapshot* getLayerSnapshot() const;
LayerSnapshot* editLayerSnapshot();
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 3bdb521..363adc6 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -148,14 +148,14 @@
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
layerSettings.blurRegions = mSnapshot->blurRegions;
- layerSettings.blurRegionTransform = mSnapshot->geomInverseLayerTransform.asMatrix4();
+ layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
break;
case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
default:
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index e4f6889..822bcb7 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -57,6 +57,7 @@
gui::LayerMetadata relativeLayerMetadata;
bool contentDirty;
bool hasReadyFrame;
+ ui::Transform blurRegionTransform;
};
struct CompositionResult {
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index a6cd47b..1acf15a 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -668,6 +668,7 @@
StringAppendF(&result, " %s\n", toString(*connection).c_str());
}
}
+ result += '\n';
}
const char* EventThread::toCString(State state) {
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
index c233455..cb9bfe9 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
@@ -54,10 +54,9 @@
std::vector<FrameRateOverride> FrameRateOverrideMappings::getAllFrameRateOverrides(
bool supportsFrameRateOverrideByContent) {
std::lock_guard lock(mFrameRateOverridesLock);
+
std::vector<FrameRateOverride> overrides;
- overrides.reserve(std::max({mFrameRateOverridesFromGameManager.size(),
- mFrameRateOverridesFromBackdoor.size(),
- mFrameRateOverridesByContent.size()}));
+ overrides.reserve(maxOverridesCount());
for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
@@ -83,28 +82,34 @@
return overrides;
}
-void FrameRateOverrideMappings::dump(std::string& result) const {
- using base::StringAppendF;
+void FrameRateOverrideMappings::dump(utils::Dumper& dumper) const {
+ using namespace std::string_view_literals;
std::lock_guard lock(mFrameRateOverridesLock);
- StringAppendF(&result, "Frame Rate Overrides (backdoor): {");
- for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
- StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
- }
- StringAppendF(&result, "}\n");
+ const bool hasOverrides = maxOverridesCount() > 0;
+ dumper.dump("FrameRateOverrides"sv, hasOverrides ? ""sv : "none"sv);
- StringAppendF(&result, "Frame Rate Overrides (GameManager): {");
- for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
- StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
- }
- StringAppendF(&result, "}\n");
+ if (!hasOverrides) return;
- StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
- for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
- StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+ dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent);
+ dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager);
+ dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor);
+}
+
+void FrameRateOverrideMappings::dump(utils::Dumper& dumper, std::string_view name,
+ const UidToFrameRateOverride& overrides) const {
+ if (overrides.empty()) return;
+
+ utils::Dumper::Indent indent(dumper);
+ dumper.dump(name);
+ {
+ utils::Dumper::Indent indent(dumper);
+ for (const auto& [uid, frameRate] : overrides) {
+ using namespace std::string_view_literals;
+ dumper.dump("(uid, frameRate)"sv, uid, frameRate);
+ }
}
- StringAppendF(&result, "}\n");
}
bool FrameRateOverrideMappings::updateFrameRateOverridesByContent(
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
index 4185a4c..da0f276 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
@@ -23,7 +23,10 @@
#include <map>
#include <optional>
+#include "Utils/Dumper.h"
+
namespace android::scheduler {
+
class FrameRateOverrideMappings {
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
using UidToFrameRateOverride = std::map<uid_t, Fps>;
@@ -34,7 +37,6 @@
EXCLUDES(mFrameRateOverridesLock);
std::vector<FrameRateOverride> getAllFrameRateOverrides(bool supportsFrameRateOverrideByContent)
EXCLUDES(mFrameRateOverridesLock);
- void dump(std::string& result) const;
bool updateFrameRateOverridesByContent(const UidToFrameRateOverride& frameRateOverrides)
EXCLUDES(mFrameRateOverridesLock);
void setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride)
@@ -42,7 +44,17 @@
void setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride)
EXCLUDES(mFrameRateOverridesLock);
+ void dump(utils::Dumper&) const;
+
private:
+ size_t maxOverridesCount() const REQUIRES(mFrameRateOverridesLock) {
+ return std::max({mFrameRateOverridesByContent.size(),
+ mFrameRateOverridesFromGameManager.size(),
+ mFrameRateOverridesFromBackdoor.size()});
+ }
+
+ void dump(utils::Dumper&, std::string_view name, const UidToFrameRateOverride&) const;
+
// The frame rate override lists need their own mutex as they are being read
// by SurfaceFlinger, Scheduler and EventThread (as a callback) to prevent deadlocks
mutable std::mutex mFrameRateOverridesLock;
@@ -53,4 +65,5 @@
UidToFrameRateOverride mFrameRateOverridesFromBackdoor GUARDED_BY(mFrameRateOverridesLock);
UidToFrameRateOverride mFrameRateOverridesFromGameManager GUARDED_BY(mFrameRateOverridesLock);
};
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index b884dc8..7c9cedfa 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -275,7 +275,7 @@
std::string LayerHistory::dump() const {
std::lock_guard lock(mLock);
- return base::StringPrintf("LayerHistory{size=%zu, active=%zu}",
+ return base::StringPrintf("{size=%zu, active=%zu}",
mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
mActiveLayerInfos.size());
}
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index 3c8dc64..cd45bfd 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -179,11 +179,5 @@
}
}
-std::string OneShotTimer::dump() const {
- std::ostringstream stream;
- stream << mInterval.count() << " ms";
- return stream.str();
-}
-
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index 2017c31..f95646c 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -23,6 +23,7 @@
#include "../Clock.h"
#include <android-base/thread_annotations.h>
+#include <scheduler/Time.h>
namespace android {
namespace scheduler {
@@ -42,6 +43,8 @@
std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
~OneShotTimer();
+ Duration interval() const { return mInterval; }
+
// Initializes and turns on the idle timer.
void start();
// Stops the idle timer and any held resources.
@@ -49,8 +52,6 @@
// Resets the wakeup time and fires the reset callback.
void reset();
- std::string dump() const;
-
private:
// Enum to track in what state is the timer.
enum class TimerState {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 40af6ee..8d4ea05 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -30,6 +30,7 @@
#include <ftl/enum.h>
#include <ftl/fake_guard.h>
#include <ftl/match.h>
+#include <ftl/unit.h>
#include <utils/Trace.h>
#include "../SurfaceFlingerProperties.h"
@@ -105,7 +106,7 @@
return sortedModes;
}
-bool canModesSupportFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
+bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
for (const auto it1 : sortedModes) {
const auto& mode1 = it1->second;
for (const auto it2 : sortedModes) {
@@ -164,9 +165,10 @@
std::string RefreshRateSelector::Policy::toString() const {
return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
- ", primaryRange=%s, appRequestRange=%s}",
+ ", primaryRanges=%s, appRequestRanges=%s}",
defaultMode.value(), allowGroupSwitching ? "true" : "false",
- to_string(primaryRange).c_str(), to_string(appRequestRange).c_str());
+ to_string(primaryRanges).c_str(),
+ to_string(appRequestRanges).c_str());
}
std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
@@ -263,7 +265,7 @@
if (layer.vote == LayerVoteType::ExplicitExact) {
const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
- if (mSupportsFrameRateOverrideByContent) {
+ if (supportsFrameRateOverrideByContent()) {
// Since we support frame rate override, allow refresh rates which are
// multiples of the layer's request, as those apps would be throttled
// down to run at the desired refresh rate.
@@ -381,7 +383,7 @@
// move out the of range if layers explicitly request a different refresh
// rate.
const bool primaryRangeIsSingleRate =
- isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
+ isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max);
if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
ALOGV("Idle");
@@ -450,7 +452,7 @@
continue;
}
- const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps());
+ const bool inPrimaryRange = policy->primaryRanges.physical.includes(mode->getFps());
if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
!(layer.focused &&
(layer.vote == LayerVoteType::ExplicitDefault ||
@@ -578,7 +580,7 @@
// 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 bool touchBoostForExplicitExact = [&] {
- if (mSupportsFrameRateOverrideByContent) {
+ if (supportsFrameRateOverrideByContent()) {
// Enable touch boost if there are other layers besides exact
return explicitExact + noVoteLayers != layers.size();
} else {
@@ -647,23 +649,43 @@
GlobalSignals globalSignals) const
-> UidToFrameRateOverride {
ATRACE_CALL();
-
ALOGV("%s: %zu layers", __func__, layers.size());
std::lock_guard lock(mLock);
- std::vector<RefreshRateScore> scores;
- scores.reserve(mDisplayModes.size());
-
- for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
- scores.emplace_back(RefreshRateScore{it, 0.0f});
+ // Prepare a set of supported display refresh rates for easy lookup
+ constexpr size_t kStaticCapacity = 8;
+ ftl::SmallMap<Fps, ftl::Unit, kStaticCapacity, FpsApproxEqual> supportedDisplayRefreshRates;
+ if (mConfig.enableFrameRateOverride ==
+ Config::FrameRateOverride::EnabledForNativeRefreshRates) {
+ for (const auto& [_, modePtr] : mDisplayModes) {
+ supportedDisplayRefreshRates.try_emplace(modePtr->getFps(), ftl::unit);
+ }
}
- std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) {
- const auto& mode1 = lhs.modeIt->second;
- const auto& mode2 = rhs.modeIt->second;
- return isStrictlyLess(mode1->getFps(), mode2->getFps());
- });
+ const auto* policyPtr = getCurrentPolicyLocked();
+ // We don't want to run lower than 30fps
+ const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
+
+ using fps_approx_ops::operator/;
+ const unsigned numMultiples = displayRefreshRate / minFrameRate;
+
+ std::vector<std::pair<Fps, float>> scoredFrameRates;
+ scoredFrameRates.reserve(numMultiples);
+
+ for (unsigned n = numMultiples; n > 0; n--) {
+ const Fps divisor = displayRefreshRate / n;
+ if (mConfig.enableFrameRateOverride ==
+ Config::FrameRateOverride::EnabledForNativeRefreshRates &&
+ !supportedDisplayRefreshRates.contains(divisor)) {
+ continue;
+ }
+
+ if (policyPtr->appRequestRanges.render.includes(divisor)) {
+ ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str());
+ scoredFrameRates.emplace_back(divisor, 0);
+ }
+ }
const auto layersByUid = groupLayersByUid(layers);
UidToFrameRateOverride frameRateOverrides;
@@ -679,7 +701,7 @@
continue;
}
- for (auto& [_, score, _1] : scores) {
+ for (auto& [_, score] : scoredFrameRates) {
score = 0;
}
@@ -691,36 +713,33 @@
LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
layer->vote != LayerVoteType::ExplicitExact);
- for (auto& [modeIt, score, _] : scores) {
+ for (auto& [fps, score] : scoredFrameRates) {
constexpr bool isSeamlessSwitch = true;
- const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(),
- isSeamlessSwitch);
+ const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
score += layer->weight * layerScore;
}
}
- // We just care about the refresh rates which are a divisor of the
- // display refresh rate
- const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) {
- const auto& [id, mode] = *score.modeIt;
- return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0;
- });
- scores.erase(it, scores.end());
-
// If we never scored any layers, we don't have a preferred frame rate
- if (std::all_of(scores.begin(), scores.end(),
- [](RefreshRateScore score) { return score.overallScore == 0; })) {
+ if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(),
+ [](const auto& scoredFrameRate) {
+ const auto [_, score] = scoredFrameRate;
+ return score == 0;
+ })) {
continue;
}
// Now that we scored all the refresh rates we need to pick the lowest refresh rate
// that got the highest score.
- const DisplayModePtr& bestRefreshRate =
- std::min_element(scores.begin(), scores.end(),
- RefreshRateScoreComparator{.refreshRateOrder =
- RefreshRateOrder::Ascending})
- ->modeIt->second;
- frameRateOverrides.emplace(uid, bestRefreshRate->getFps());
+ const auto [overrideFps, _] =
+ *std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(),
+ [](const auto& lhsPair, const auto& rhsPair) {
+ const float lhs = lhsPair.second;
+ const float rhs = rhsPair.second;
+ return lhs < rhs && !ScoredRefreshRate::scoresEqual(lhs, rhs);
+ });
+ ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
+ frameRateOverrides.emplace(uid, overrideFps);
}
return frameRateOverrides;
@@ -893,8 +912,17 @@
mDisplayManagerPolicy = {};
mDisplayManagerPolicy.defaultMode = activeModeId;
- mSupportsFrameRateOverrideByContent =
- mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes);
+ mFrameRateOverrideConfig = [&] {
+ switch (mConfig.enableFrameRateOverride) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::Enabled:
+ return mConfig.enableFrameRateOverride;
+ case Config::FrameRateOverride::EnabledForNativeRefreshRates:
+ return shouldEnableFrameRateOverride(sortedModes)
+ ? Config::FrameRateOverride::EnabledForNativeRefreshRates
+ : Config::FrameRateOverride::Disabled;
+ }
+ }();
constructAvailableRefreshRates();
}
@@ -902,7 +930,7 @@
bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const {
// defaultMode must be a valid mode, and within the given refresh rate range.
if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
- if (!policy.primaryRange.includes(mode->get()->getFps())) {
+ if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) {
ALOGE("Default mode is not in the primary range.");
return false;
}
@@ -912,8 +940,8 @@
}
using namespace fps_approx_ops;
- return policy.appRequestRange.min <= policy.primaryRange.min &&
- policy.appRequestRange.max >= policy.primaryRange.max;
+ return policy.appRequestRanges.physical.min <= policy.primaryRanges.physical.min &&
+ policy.appRequestRanges.physical.max >= policy.primaryRanges.physical.max;
}
auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
@@ -1026,8 +1054,8 @@
return modes;
};
- mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary");
- mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request");
+ mPrimaryRefreshRates = filterRefreshRates(policy->primaryRanges.physical, "primary");
+ mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRanges.physical, "app request");
}
Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
@@ -1067,7 +1095,7 @@
if (minByPolicy == maxByPolicy) {
// Turn on the timer when the min of the primary range is below the device min.
if (const Policy* currentPolicy = getCurrentPolicyLocked();
- isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) {
+ isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) {
return KernelIdleTimerAction::TurnOn;
}
return KernelIdleTimerAction::TurnOff;
@@ -1127,22 +1155,17 @@
dumper.dump("overridePolicy"sv, currentPolicy.toString());
}
- dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent);
+ dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig));
- std::string idleTimer;
- if (mIdleTimer) {
- idleTimer = mIdleTimer->dump();
- } else {
- idleTimer = "off"sv;
+ dumper.dump("idleTimer"sv);
+ {
+ utils::Dumper::Indent indent(dumper);
+ dumper.dump("interval"sv, mIdleTimer.transform(&OneShotTimer::interval));
+ dumper.dump("controller"sv,
+ mConfig.kernelIdleTimerController
+ .and_then(&ftl::enum_name<KernelIdleTimerController>)
+ .value_or("Platform"sv));
}
-
- if (const auto controller = mConfig.kernelIdleTimerController) {
- base::StringAppendF(&idleTimer, " (kernel via %s)", ftl::enum_string(*controller).c_str());
- } else {
- idleTimer += " (platform)"sv;
- }
-
- dumper.dump("idleTimer"sv, idleTimer);
}
std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index bff16d3..0e80817 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -18,12 +18,12 @@
#include <algorithm>
#include <numeric>
-#include <optional>
#include <type_traits>
#include <utility>
#include <variant>
#include <ftl/concat.h>
+#include <ftl/optional.h>
#include <gui/DisplayEventReceiver.h>
#include <scheduler/Fps.h>
@@ -67,40 +67,31 @@
DisplayModeId defaultMode;
// Whether or not we switch mode groups to get the best frame rate.
bool allowGroupSwitching = kAllowGroupSwitchingDefault;
- // The primary refresh rate range represents display manager's general guidance on the
- // display modes we'll consider when switching refresh rates. Unless we get an explicit
- // signal from an app, we should stay within this range.
- FpsRange primaryRange;
- // The app request refresh rate range allows us to consider more display modes when
- // switching refresh rates. Although we should generally stay within the primary range,
- // specific considerations, such as layer frame rate settings specified via the
- // setFrameRate() api, may cause us to go outside the primary range. We never go outside the
- // app request range. The app request range will be greater than or equal to the primary
- // refresh rate range, never smaller.
- FpsRange appRequestRange;
+ // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details.
+ // TODO(b/257072060): use the render range when selecting SF render rate
+ // or the app override frame rate
+ FpsRanges primaryRanges;
+ // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
+ FpsRanges appRequestRanges;
Policy() = default;
- Policy(DisplayModeId defaultMode, FpsRange range)
- : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {}
+ Policy(DisplayModeId defaultMode, FpsRange range,
+ bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+ : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
+ allowGroupSwitching) {}
- Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range)
- : Policy(defaultMode, allowGroupSwitching, range, range) {}
-
- Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange)
- : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
-
- Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange,
- FpsRange appRequestRange)
+ Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
+ bool allowGroupSwitching = kAllowGroupSwitchingDefault)
: defaultMode(defaultMode),
allowGroupSwitching(allowGroupSwitching),
- primaryRange(primaryRange),
- appRequestRange(appRequestRange) {}
+ primaryRanges(primaryRanges),
+ appRequestRanges(appRequestRanges) {}
bool operator==(const Policy& other) const {
using namespace fps_approx_ops;
- return defaultMode == other.defaultMode && primaryRange == other.primaryRange &&
- appRequestRange == other.appRequestRange &&
+ return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges &&
+ appRequestRanges == other.appRequestRanges &&
allowGroupSwitching == other.allowGroupSwitching;
}
@@ -260,7 +251,20 @@
// Configuration flags.
struct Config {
- bool enableFrameRateOverride = false;
+ enum class FrameRateOverride {
+ // Do not override the frame rate for an app
+ Disabled,
+
+ // Override the frame rate for an app to a value which is also
+ // a display refresh rate
+ EnabledForNativeRefreshRates,
+
+ // Override the frame rate for an app to any value
+ Enabled,
+
+ ftl_last = Enabled
+ };
+ FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled;
// Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple
// or heuristic, such that refresh rates higher than this value will not be voted for. 0 if
@@ -272,14 +276,15 @@
// The controller representing how the kernel idle timer will be configured
// either on the HWC api or sysprop.
- std::optional<KernelIdleTimerController> kernelIdleTimerController;
+ ftl::Optional<KernelIdleTimerController> kernelIdleTimerController;
};
- RefreshRateSelector(DisplayModes, DisplayModeId activeModeId,
- Config config = {.enableFrameRateOverride = false,
- .frameRateMultipleThreshold = 0,
- .idleTimerTimeout = 0ms,
- .kernelIdleTimerController = {}});
+ RefreshRateSelector(
+ DisplayModes, DisplayModeId activeModeId,
+ Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
+ .frameRateMultipleThreshold = 0,
+ .idleTimerTimeout = 0ms,
+ .kernelIdleTimerController = {}});
RefreshRateSelector(const RefreshRateSelector&) = delete;
RefreshRateSelector& operator=(const RefreshRateSelector&) = delete;
@@ -302,7 +307,9 @@
// refresh rates.
KernelIdleTimerAction getIdleTimerAction() const;
- bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; }
+ bool supportsFrameRateOverrideByContent() const {
+ return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled;
+ }
// Return the display refresh rate divisor to match the layer
// frame rate, or 0 if the display refresh rate is not a multiple of the
@@ -455,7 +462,7 @@
const std::vector<Fps> mKnownFrameRates;
const Config mConfig;
- bool mSupportsFrameRateOverrideByContent;
+ Config::FrameRateOverride mFrameRateOverrideConfig;
struct GetRankedRefreshRatesCache {
std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
@@ -467,7 +474,7 @@
std::mutex mIdleTimerCallbacksMutex;
std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
// Used to detect (lack of) frame activity.
- std::optional<scheduler::OneShotTimer> mIdleTimer;
+ ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 499cee6..6108d92 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -25,6 +25,7 @@
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
#include <configstore/Utils.h>
+#include <ftl/enum.h>
#include <ftl/fake_guard.h>
#include <ftl/small_map.h>
#include <gui/WindowInfo.h>
@@ -94,45 +95,32 @@
}
}
-void Scheduler::setRefreshRateSelector(std::shared_ptr<RefreshRateSelector> selectorPtr) {
- // The current RefreshRateSelector instance may outlive this call, so unbind its idle timer.
- {
- // mRefreshRateSelectorLock is not locked here to avoid the deadlock
- // as the callback can attempt to acquire the lock before stopIdleTimer can finish
- // the execution. It's safe to FakeGuard as main thread is the only thread that
- // writes to the mRefreshRateSelector.
- ftl::FakeGuard guard(mRefreshRateSelectorLock);
- if (mRefreshRateSelector) {
- mRefreshRateSelector->stopIdleTimer();
- mRefreshRateSelector->clearIdleTimerCallbacks();
- }
+void Scheduler::setRefreshRateSelector(RefreshRateSelectorPtr newSelectorPtr) {
+ // No need to lock for reads on kMainThreadContext.
+ if (const auto& selectorPtr = FTL_FAKE_GUARD(mRefreshRateSelectorLock, mRefreshRateSelector)) {
+ unbindIdleTimer(*selectorPtr);
}
+
{
- // Clear state that depends on the current instance.
+ // Clear state that depends on the current RefreshRateSelector.
std::scoped_lock lock(mPolicyLock);
mPolicy = {};
}
std::scoped_lock lock(mRefreshRateSelectorLock);
- mRefreshRateSelector = std::move(selectorPtr);
- if (!mRefreshRateSelector) return;
+ mRefreshRateSelector = std::move(newSelectorPtr);
- mRefreshRateSelector->setIdleTimerCallbacks(
- {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
- .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
- .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
- .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
-
- mRefreshRateSelector->startIdleTimer();
+ if (mRefreshRateSelector) {
+ bindIdleTimer(*mRefreshRateSelector);
+ }
}
-void Scheduler::registerDisplay(sp<const DisplayDevice> display) {
- if (display->isPrimary()) {
- mLeaderDisplayId = display->getPhysicalId();
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+ if (!mLeaderDisplayId) {
+ mLeaderDisplayId = displayId;
}
- const bool ok = mDisplays.try_emplace(display->getPhysicalId(), std::move(display)).second;
- ALOGE_IF(!ok, "%s: Duplicate display", __func__);
+ mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
}
void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
@@ -140,7 +128,7 @@
mLeaderDisplayId.reset();
}
- mDisplays.erase(displayId);
+ mRefreshRateSelectors.erase(displayId);
}
void Scheduler::run() {
@@ -547,6 +535,21 @@
mLayerHistory.clear();
}
+void Scheduler::bindIdleTimer(RefreshRateSelector& selector) {
+ selector.setIdleTimerCallbacks(
+ {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
+ .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
+ .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
+ .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+
+ selector.startIdleTimer();
+}
+
+void Scheduler::unbindIdleTimer(RefreshRateSelector& selector) {
+ selector.stopIdleTimer();
+ selector.clearIdleTimerCallbacks();
+}
+
void Scheduler::kernelIdleTimerCallback(TimerState state) {
ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
@@ -597,22 +600,36 @@
ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
}
-void Scheduler::dump(std::string& result) const {
- using base::StringAppendF;
-
- StringAppendF(&result, "+ Touch timer: %s\n",
- mTouchTimer ? mTouchTimer->dump().c_str() : "off");
- StringAppendF(&result, "+ Content detection: %s %s\n\n",
- mFeatures.test(Feature::kContentDetection) ? "on" : "off",
- mLayerHistory.dump().c_str());
-
- mFrameRateOverrideMappings.dump(result);
+void Scheduler::dump(utils::Dumper& dumper) const {
+ using namespace std::string_view_literals;
{
+ utils::Dumper::Section section(dumper, "Features"sv);
+
+ for (Feature feature : ftl::enum_range<Feature>()) {
+ if (const auto flagOpt = ftl::flag_name(feature)) {
+ dumper.dump(flagOpt->substr(1), mFeatures.test(feature));
+ }
+ }
+ }
+ {
+ utils::Dumper::Section section(dumper, "Policy"sv);
+
+ dumper.dump("layerHistory"sv, mLayerHistory.dump());
+ dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval));
+ dumper.dump("displayPowerTimer"sv, mDisplayPowerTimer.transform(&OneShotTimer::interval));
+ }
+
+ mFrameRateOverrideMappings.dump(dumper);
+ dumper.eol();
+
+ {
+ utils::Dumper::Section section(dumper, "Hardware VSYNC"sv);
+
std::lock_guard lock(mHWVsyncLock);
- StringAppendF(&result,
- "mScreenAcquired=%d mPrimaryHWVsyncEnabled=%d mHWVsyncAvailable=%d\n",
- mScreenAcquired.load(), mPrimaryHWVsyncEnabled, mHWVsyncAvailable);
+ dumper.dump("screenAcquired"sv, mScreenAcquired.load());
+ dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable);
+ dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled);
}
}
@@ -711,10 +728,9 @@
const auto globalSignals = makeGlobalSignals();
- for (const auto& [id, display] : mDisplays) {
+ for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
auto rankedRefreshRates =
- display->holdRefreshRateSelector()
- ->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
+ selectorPtr->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
@@ -733,7 +749,7 @@
// Find the first refresh rate common to all displays.
while (maxScoreIt != refreshRateTallies.cend() &&
- maxScoreIt->second.displayCount != mDisplays.size()) {
+ maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) {
++maxScoreIt;
}
@@ -742,7 +758,8 @@
for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) {
const auto [fps, tally] = *it;
- if (tally.displayCount == mDisplays.size() && tally.score > maxScoreIt->second.score) {
+ if (tally.displayCount == mRefreshRateSelectors.size() &&
+ tally.score > maxScoreIt->second.score) {
maxScoreIt = it;
}
}
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 39c41b9..04f3b69 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -39,13 +39,13 @@
#include "Display/DisplayMap.h"
#include "Display/DisplayModeRequest.h"
-#include "DisplayDevice.h"
#include "EventThread.h"
#include "FrameRateOverrideMappings.h"
#include "LayerHistory.h"
#include "MessageQueue.h"
#include "OneShotTimer.h"
#include "RefreshRateSelector.h"
+#include "Utils/Dumper.h"
#include "VsyncSchedule.h"
namespace android::scheduler {
@@ -107,10 +107,12 @@
virtual ~Scheduler();
void startTimers();
- void setRefreshRateSelector(std::shared_ptr<RefreshRateSelector>)
+
+ using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
+ void setRefreshRateSelector(RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
EXCLUDES(mRefreshRateSelectorLock);
- void registerDisplay(sp<const DisplayDevice>);
+ void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr);
void unregisterDisplay(PhysicalDisplayId);
void run();
@@ -196,7 +198,7 @@
// for a given uid
bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
- void dump(std::string&) const;
+ void dump(utils::Dumper&) const;
void dump(ConnectionHandle, std::string&) const;
void dumpVsync(std::string&) const;
@@ -253,6 +255,13 @@
sp<EventThreadConnection> createConnectionInternal(
EventThread*, EventRegistrationFlags eventRegistration = {});
+ void bindIdleTimer(RefreshRateSelector&) REQUIRES(kMainThreadContext, mRefreshRateSelectorLock);
+
+ // Blocks until the timer thread exits. `mRefreshRateSelectorLock` must not be locked by the
+ // caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
+ static void unbindIdleTimer(RefreshRateSelector&) REQUIRES(kMainThreadContext)
+ EXCLUDES(mRefreshRateSelectorLock);
+
// Update feature state machine to given state when corresponding timer resets or expires.
void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateSelectorLock);
void idleTimerCallback(TimerState);
@@ -299,8 +308,7 @@
EXCLUDES(mRefreshRateSelectorLock);
android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
- std::shared_ptr<RefreshRateSelector> holdRefreshRateSelector() const
- EXCLUDES(mRefreshRateSelectorLock) {
+ RefreshRateSelectorPtr holdRefreshRateSelector() const EXCLUDES(mRefreshRateSelectorLock) {
std::scoped_lock lock(mRefreshRateSelectorLock);
return mRefreshRateSelector;
}
@@ -328,15 +336,15 @@
LayerHistory mLayerHistory;
// Timer used to monitor touch events.
- std::optional<OneShotTimer> mTouchTimer;
+ ftl::Optional<OneShotTimer> mTouchTimer;
// Timer used to monitor display power mode.
- std::optional<OneShotTimer> mDisplayPowerTimer;
+ ftl::Optional<OneShotTimer> mDisplayPowerTimer;
ISchedulerCallback& mSchedulerCallback;
mutable std::mutex mPolicyLock;
- display::PhysicalDisplayMap<PhysicalDisplayId, sp<const DisplayDevice>> mDisplays;
+ display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors;
std::optional<PhysicalDisplayId> mLeaderDisplayId;
struct Policy {
@@ -359,8 +367,9 @@
std::optional<ModeChangedParams> cachedModeChangedParams;
} mPolicy GUARDED_BY(mPolicyLock);
+ // TODO(b/255635821): Remove this by instead looking up the `mLeaderDisplayId` selector.
mutable std::mutex mRefreshRateSelectorLock;
- std::shared_ptr<RefreshRateSelector> mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock);
+ RefreshRateSelectorPtr mRefreshRateSelector GUARDED_BY(mRefreshRateSelectorLock);
std::mutex mVsyncTimelineLock;
std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index bd4f409..31b1d69 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -68,6 +68,15 @@
bool includes(Fps) const;
};
+struct FpsRanges {
+ // The range of refresh rates that refers to the display mode setting.
+ FpsRange physical;
+
+ // the range of frame rates that refers to the render rate, which is
+ // the rate that frames are swapped.
+ FpsRange render;
+};
+
static_assert(std::is_trivially_copyable_v<Fps>);
constexpr Fps operator""_Hz(unsigned long long frequency) {
@@ -127,8 +136,24 @@
return !(lhs == rhs);
}
+inline bool operator==(const FpsRanges& lhs, const FpsRanges& rhs) {
+ return lhs.physical == rhs.physical && lhs.render == rhs.render;
+}
+
+inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) {
+ return !(lhs == rhs);
+}
+
+inline unsigned operator/(Fps lhs, Fps rhs) {
+ return static_cast<unsigned>(std::ceil(lhs.getValue() / rhs.getValue()));
+}
+
} // namespace fps_approx_ops
+constexpr Fps operator/(Fps fps, unsigned divisor) {
+ return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast<nsecs_t>(divisor));
+}
+
inline bool FpsRange::includes(Fps fps) const {
using fps_approx_ops::operator<=;
return min <= fps && fps <= max;
@@ -151,4 +176,10 @@
return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str());
}
+inline std::string to_string(FpsRanges ranges) {
+ const auto& [physical, render] = ranges;
+ return base::StringPrintf("{physical=%s, render=%s}", to_string(physical).c_str(),
+ to_string(render).c_str());
+}
+
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
index 2ca55d4..bd4e3c2 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
@@ -17,7 +17,9 @@
#pragma once
#include <chrono>
+#include <string>
+#include <android-base/stringprintf.h>
#include <utils/Timers.h>
namespace android {
@@ -79,4 +81,8 @@
return std::chrono::duration_cast<D>(d).count();
}
+inline std::string to_string(Duration d) {
+ return base::StringPrintf("%.3f ms", ticks<std::milli, float>(d));
+}
+
} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
new file mode 100644
index 0000000..8f93ba4
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2022 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 "ScreenCaptureOutput.h"
+#include "ScreenCaptureRenderSurface.h"
+
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
+#include <ui/Rotation.h>
+
+namespace android {
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs&& args) {
+ std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
+ ScreenCaptureOutput, compositionengine::CompositionEngine,
+ ScreenCaptureOutputArgs&&>(args.compositionEngine, std::move(args));
+ output->editState().isSecure = args.renderArea.isSecure();
+ output->setCompositionEnabled(true);
+ output->setLayerFilter({args.layerStack});
+ output->setRenderSurface(std::make_unique<ScreenCaptureRenderSurface>(std::move(args.buffer)));
+ output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits);
+
+ output->setDisplayColorProfile(std::make_unique<compositionengine::impl::DisplayColorProfile>(
+ compositionengine::DisplayColorProfileCreationArgsBuilder()
+ .setHasWideColorGamut(true)
+ .Build()));
+
+ ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
+ Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()};
+ output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(),
+ orientedDisplaySpaceRect);
+
+ Rect sourceCrop = args.renderArea.getSourceCrop();
+ output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
+
+ return output;
+}
+
+ScreenCaptureOutput::ScreenCaptureOutput(ScreenCaptureOutputArgs&& args)
+ : mRenderArea(args.renderArea),
+ mFilterForScreenshot(std::move(args.filterForScreenshot)),
+ mColorProfile(args.colorProfile),
+ mRegionSampling(args.regionSampling) {}
+
+void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
+ auto& outputState = editState();
+ outputState.dataspace = mColorProfile.dataspace;
+ outputState.renderIntent = mColorProfile.renderIntent;
+}
+
+renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings()
+ const {
+ auto clientCompositionDisplay =
+ compositionengine::impl::Output::generateClientCompositionDisplaySettings();
+ clientCompositionDisplay.clip = mRenderArea.getSourceCrop();
+ clientCompositionDisplay.targetLuminanceNits = -1;
+ return clientCompositionDisplay;
+}
+
+std::vector<compositionengine::LayerFE::LayerSettings>
+ScreenCaptureOutput::generateClientCompositionRequests(
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<compositionengine::LayerFE*>& outLayerFEs) {
+ auto clientCompositionLayers = compositionengine::impl::Output::
+ generateClientCompositionRequests(supportsProtectedContent, outputDataspace,
+ outLayerFEs);
+
+ if (mRegionSampling) {
+ for (auto& layer : clientCompositionLayers) {
+ layer.backgroundBlurRadius = 0;
+ layer.blurRegions.clear();
+ }
+ }
+
+ Rect sourceCrop = mRenderArea.getSourceCrop();
+ compositionengine::LayerFE::LayerSettings fillLayer;
+ fillLayer.source.buffer.buffer = nullptr;
+ fillLayer.source.solidColor = half3(0.0f, 0.0f, 0.0f);
+ fillLayer.geometry.boundaries =
+ FloatRect(static_cast<float>(sourceCrop.left), static_cast<float>(sourceCrop.top),
+ static_cast<float>(sourceCrop.right), static_cast<float>(sourceCrop.bottom));
+ fillLayer.alpha = half(RenderArea::getCaptureFillValue(mRenderArea.getCaptureFill()));
+ clientCompositionLayers.insert(clientCompositionLayers.begin(), fillLayer);
+
+ return clientCompositionLayers;
+}
+
+bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
+ return mRenderArea.needsFiltering() ||
+ mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
new file mode 100644
index 0000000..61b5ddb
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 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 <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/Output.h>
+#include <ui/Rect.h>
+
+#include "RenderArea.h"
+
+namespace android {
+
+struct ScreenCaptureOutputArgs {
+ const compositionengine::CompositionEngine& compositionEngine;
+ const compositionengine::Output::ColorProfile& colorProfile;
+ const RenderArea& renderArea;
+ ui::LayerStack layerStack;
+ std::shared_ptr<renderengine::ExternalTexture> buffer;
+ float sdrWhitePointNits;
+ float displayBrightnessNits;
+ std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
+ bool regionSampling;
+};
+
+// ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer.
+//
+// SurfaceFlinger passes instances of ScreenCaptureOutput to CompositionEngine in calls to
+// SurfaceFlinger::captureLayers and SurfaceFlinger::captureDisplay.
+class ScreenCaptureOutput : public compositionengine::impl::Output {
+public:
+ ScreenCaptureOutput(ScreenCaptureOutputArgs&&);
+
+ void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
+
+ std::vector<compositionengine::LayerFE::LayerSettings> generateClientCompositionRequests(
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<compositionengine::LayerFE*>& outLayerFEs) override;
+
+ bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override;
+
+protected:
+ bool getSkipColorTransform() const override { return false; }
+ renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override;
+
+private:
+ const RenderArea& mRenderArea;
+ const std::unordered_set<compositionengine::LayerFE*> mFilterForScreenshot;
+ const compositionengine::Output::ColorProfile& mColorProfile;
+ const bool mRegionSampling;
+};
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs&&);
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h
new file mode 100644
index 0000000..2097300
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureRenderSurface.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 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 <memory>
+
+#include <compositionengine/RenderSurface.h>
+#include <renderengine/impl/ExternalTexture.h>
+#include <ui/Fence.h>
+#include <ui/Size.h>
+
+namespace android {
+
+// ScreenCaptureRenderSurface is a RenderSurface that returns a preallocated buffer used by
+// ScreenCaptureOutput.
+class ScreenCaptureRenderSurface : public compositionengine::RenderSurface {
+public:
+ ScreenCaptureRenderSurface(std::shared_ptr<renderengine::ExternalTexture> buffer)
+ : mBuffer(std::move(buffer)){};
+
+ std::shared_ptr<renderengine::ExternalTexture> dequeueBuffer(
+ base::unique_fd* /* bufferFence */) override {
+ return mBuffer;
+ }
+
+ void queueBuffer(base::unique_fd readyFence) override {
+ mRenderFence = sp<Fence>::make(readyFence.release());
+ }
+
+ const sp<Fence>& getClientTargetAcquireFence() const override { return mRenderFence; }
+
+ bool supportsCompositionStrategyPrediction() const override { return false; }
+
+ bool isValid() const override { return true; }
+
+ void initialize() override {}
+
+ const ui::Size& getSize() const override { return mSize; }
+
+ bool isProtected() const override { return mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED; }
+
+ void setDisplaySize(const ui::Size&) override {}
+
+ void setBufferDataspace(ui::Dataspace) override {}
+
+ void setBufferPixelFormat(ui::PixelFormat) override {}
+
+ void setProtected(bool /* useProtected */) override {}
+
+ status_t beginFrame(bool /* mustRecompose */) override { return OK; }
+
+ void prepareFrame(bool /* usesClientComposition */, bool /* usesDeviceComposition */) override {
+ }
+
+ void onPresentDisplayCompleted() override {}
+
+ void dump(std::string& /* result */) const override {}
+
+private:
+ std::shared_ptr<renderengine::ExternalTexture> mBuffer;
+
+ sp<Fence> mRenderFence = Fence::NO_FENCE;
+
+ ui::Size mSize;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 89d905a..d68b00a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -44,16 +44,19 @@
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/Display.h>
#include <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
#include <compositionengine/DisplayCreationArgs.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/OutputLayer.h>
#include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
#include <compositionengine/impl/OutputCompositionState.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
#include <configstore/Utils.h>
#include <cutils/compiler.h>
#include <cutils/properties.h>
#include <ftl/algorithm.h>
+#include <ftl/concat.h>
#include <ftl/fake_guard.h>
#include <ftl/future.h>
#include <ftl/unit.h>
@@ -137,6 +140,7 @@
#include "Scheduler/LayerHistory.h"
#include "Scheduler/Scheduler.h"
#include "Scheduler/VsyncConfiguration.h"
+#include "ScreenCaptureOutput.h"
#include "StartPropertySetThread.h"
#include "SurfaceFlingerProperties.h"
#include "TimeStats/TimeStats.h"
@@ -160,6 +164,7 @@
using namespace std::chrono_literals;
using namespace std::string_literals;
+using namespace std::string_view_literals;
using namespace hardware::configstore;
using namespace hardware::configstore::V1_0;
@@ -357,9 +362,6 @@
// debugging stuff...
char value[PROPERTY_VALUE_MAX];
- property_get("ro.bq.gpu_to_cpu_unsupported", value, "0");
- mGpuToCpuSupported = !atoi(value);
-
property_get("ro.build.type", value, "user");
mIsUserBuild = strcmp(value, "user") == 0;
@@ -1132,8 +1134,8 @@
display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
- allowGroupSwitching,
- {fps, fps}};
+ {fps, fps},
+ allowGroupSwitching};
return setDesiredDisplayModeSpecsInternal(display, policy);
});
@@ -2182,13 +2184,14 @@
refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
- std::vector<Layer*> layers;
+ std::vector<sp<Layer>> layers;
mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) {
+ auto strongLayer = sp<Layer>::fromExisting(layer);
if (auto layerFE = layer->getCompositionEngineLayerFE()) {
layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
refreshArgs.layers.push_back(layerFE);
- layers.push_back(layer);
+ layers.push_back(std::move(strongLayer));
}
});
refreshArgs.blursAreExpensive = mBlursAreExpensive;
@@ -2220,8 +2223,8 @@
{
std::vector<LayerSnapshotGuard> layerSnapshotGuards;
- for (Layer* layer : layers) {
- layerSnapshotGuards.emplace_back(layer);
+ for (auto& layer : layers) {
+ layerSnapshotGuards.emplace_back(layer.get());
}
mCompositionEngine->present(refreshArgs);
}
@@ -2777,8 +2780,21 @@
const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
getKernelIdleTimerProperties(compositionDisplay->getId());
+ const auto enableFrameRateOverride = [&] {
+ using Config = scheduler::RefreshRateSelector::Config;
+ if (!sysprop::enable_frame_rate_override(false)) {
+ return Config::FrameRateOverride::Disabled;
+ }
+
+ if (sysprop::frame_rate_override_for_native_rates(true)) {
+ return Config::FrameRateOverride::EnabledForNativeRefreshRates;
+ }
+
+ return Config::FrameRateOverride::Enabled;
+ }();
+
scheduler::RefreshRateSelector::Config config =
- {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false),
+ {.enableFrameRateOverride = enableFrameRateOverride,
.frameRateMultipleThreshold =
base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
.idleTimerTimeout = idleTimerTimeoutMs,
@@ -2937,11 +2953,17 @@
displaySurface, producer);
if (mScheduler && !display->isVirtual()) {
+ auto selectorPtr = display->holdRefreshRateSelector();
+
// Display modes are reloaded on hotplug reconnect.
if (display->isPrimary()) {
- mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector());
+ // TODO(b/241285876): Annotate `processDisplayAdded` instead.
+ ftl::FakeGuard guard(kMainThreadContext);
+ mScheduler->setRefreshRateSelector(selectorPtr);
}
- mScheduler->registerDisplay(display);
+
+ const auto displayId = display->getPhysicalId();
+ mScheduler->registerDisplay(displayId, std::move(selectorPtr));
dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
}
@@ -2994,8 +3016,6 @@
display->disconnect();
if (display->isVirtual()) {
releaseVirtualDisplay(display->getVirtualId());
- } else {
- mScheduler->unregisterDisplay(display->getPhysicalId());
}
}
@@ -3260,7 +3280,7 @@
ALOGE_IF(error != NO_ERROR,
"Error setting display brightness for display %s: %d (%s)",
- display->getDebugName().c_str(), error, strerror(error));
+ to_string(display->getId()).c_str(), error, strerror(error));
}
display->persistBrightness(needsComposite);
}
@@ -3325,7 +3345,12 @@
std::vector<LayerSnapshotGuard> layerSnapshotGuards;
mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) {
- if (layer->getLayerSnapshot()->compositionType ==
+ auto strongLayer = sp<Layer>::fromExisting(layer);
+ const LayerSnapshot* snapshot = layer->getLayerSnapshot();
+ if (!snapshot) {
+ LOG_ALWAYS_FATAL("Layer snapshot unexpectedly null");
+ }
+ if (snapshot->compositionType ==
aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
layer->updateSnapshot(false /* updateGeometry */);
layerSnapshotGuards.emplace_back(layer);
@@ -3399,19 +3424,18 @@
features |= Feature::kPresentFences;
}
+ auto selectorPtr = display->holdRefreshRateSelector();
+ if (selectorPtr->kernelIdleTimerController()) {
+ features |= Feature::kKernelIdleTimer;
+ }
+
mScheduler = std::make_unique<scheduler::Scheduler>(static_cast<ICompositor&>(*this),
static_cast<ISchedulerCallback&>(*this),
features);
- {
- auto selectorPtr = display->holdRefreshRateSelector();
- if (selectorPtr->kernelIdleTimerController()) {
- features |= Feature::kKernelIdleTimer;
- }
+ mScheduler->createVsyncSchedule(features);
+ mScheduler->setRefreshRateSelector(selectorPtr);
+ mScheduler->registerDisplay(display->getPhysicalId(), std::move(selectorPtr));
- mScheduler->createVsyncSchedule(features);
- mScheduler->setRefreshRateSelector(std::move(selectorPtr));
- mScheduler->registerDisplay(display);
- }
setVsyncEnabled(false);
mScheduler->startTimers();
@@ -4753,17 +4777,18 @@
{"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
{"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
{"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
- {"--dispsync"s, dumper([this](std::string& s) { mScheduler->dumpVsync(s); })},
{"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
+ {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
+ {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
+ {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)},
{"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
{"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
{"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
{"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
+ {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
{"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
- {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)},
+ {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
{"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
- {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
- {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)},
};
const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
@@ -4887,24 +4912,39 @@
result.append("]");
}
-void SurfaceFlinger::dumpVSync(std::string& result) const {
- mScheduler->dump(result);
+void SurfaceFlinger::dumpScheduler(std::string& result) const {
+ utils::Dumper dumper{result};
+
+ mScheduler->dump(dumper);
+
+ // TODO(b/241286146): Move to Scheduler.
+ {
+ utils::Dumper::Indent indent(dumper);
+ dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState);
+ dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState);
+ }
+ dumper.eol();
+
+ // TODO(b/241285876): Move to DisplayModeController.
+ dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
+ dumper.eol();
mRefreshRateStats->dump(result);
- result.append("\n");
+ dumper.eol();
mVsyncConfiguration->dump(result);
StringAppendF(&result,
- " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64 " ns\n\n",
+ " present offset: %9" PRId64 " ns\t VSYNC period: %9" PRId64
+ " ns\n\n",
dispSyncPresentTimeOffset, getVsyncPeriodFromHWC());
+}
- StringAppendF(&result, "(mode override by backdoor: %s)\n\n",
- mDebugDisplayModeSetByBackdoor ? "yes" : "no");
-
+void SurfaceFlinger::dumpEvents(std::string& result) const {
mScheduler->dump(mAppConnectionHandle, result);
+}
+
+void SurfaceFlinger::dumpVsync(std::string& result) const {
mScheduler->dumpVsync(result);
- StringAppendF(&result, "mHWCVsyncPendingState=%s mLastHWCVsyncState=%s\n",
- to_string(mHWCVsyncPendingState).c_str(), to_string(mLastHWCVsyncState).c_str());
}
void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const {
@@ -4925,19 +4965,21 @@
utils::Dumper dumper{result};
for (const auto& [id, display] : mPhysicalDisplays) {
+ utils::Dumper::Section section(dumper, ftl::Concat("Display ", id.value).str());
+
+ display.snapshot().dump(dumper);
+
if (const auto device = getDisplayDeviceLocked(id)) {
device->dump(dumper);
}
-
- utils::Dumper::Indent indent(dumper);
- display.snapshot().dump(dumper);
- dumper.eol();
}
for (const auto& [token, display] : mDisplays) {
if (display->isVirtual()) {
+ const auto displayId = display->getId();
+ utils::Dumper::Section section(dumper,
+ ftl::Concat("Virtual Display ", displayId.value).str());
display->dump(dumper);
- dumper.eol();
}
}
}
@@ -5016,8 +5058,22 @@
}
LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
+ std::unordered_set<uint64_t> stackIdsToSkip;
+
+ // Determine if virtual layers display should be skipped
+ if ((traceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
+ for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+ if (display->isVirtual()) {
+ stackIdsToSkip.insert(display->getLayerStack().id);
+ }
+ }
+ }
+
LayersProto layersProto;
for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
+ if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
+ continue;
+ }
layer->writeToProto(layersProto, traceFlags);
}
@@ -5094,7 +5150,7 @@
Layer::miniDumpHeader(result);
const DisplayDevice& ref = *display;
- mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); });
+ mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); });
result.append("\n");
}
}
@@ -5134,7 +5190,9 @@
colorizer.bold(result);
result.append("Scheduler:\n");
colorizer.reset(result);
- dumpVSync(result);
+ dumpScheduler(result);
+ dumpEvents(result);
+ dumpVsync(result);
result.append("\n");
StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load());
@@ -5179,10 +5237,7 @@
StringAppendF(&result, " orientation=%s, isPoweredOn=%d\n",
toCString(display->getOrientation()), display->isPoweredOn());
}
- StringAppendF(&result,
- " transaction-flags : %08x\n"
- " gpu_to_cpu_unsupported : %d\n",
- mTransactionFlags.load(), !mGpuToCpuSupported);
+ StringAppendF(&result, " transaction-flags : %08x\n", mTransactionFlags.load());
if (const auto display = getDefaultDisplayDeviceLocked()) {
std::string fps, xDpi, yDpi;
@@ -6227,7 +6282,8 @@
return BAD_VALUE;
}
- Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height);
+ Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width,
+ crop.top + reqSize.height);
bool childrenOnly = args.childrenOnly;
RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
@@ -6347,7 +6403,7 @@
ftl::SharedFuture<FenceResult> renderFuture;
renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
- renderFuture = renderScreenImpl(*renderArea, traverseLayers, buffer,
+ renderFuture = renderScreenImpl(std::move(renderArea), traverseLayers, buffer,
canCaptureBlackoutContent, regionSampling,
grayscale, captureResults);
});
@@ -6375,19 +6431,19 @@
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
- const RenderArea& renderArea, TraverseLayersFunction traverseLayers,
+ std::unique_ptr<RenderArea> renderArea, TraverseLayersFunction traverseLayers,
const std::shared_ptr<renderengine::ExternalTexture>& buffer,
bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
ScreenCaptureResults& captureResults) {
ATRACE_CALL();
+ size_t layerCount = 0;
traverseLayers([&](Layer* layer) {
+ layerCount++;
captureResults.capturedSecureLayers =
captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure());
});
- const bool useProtected = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-
// We allow the system server to take screenshots of secure layers for
// use in situations like the Screen-rotation animation and place
// the impetus on WindowManager to not persist them.
@@ -6397,8 +6453,8 @@
}
captureResults.buffer = buffer->getBuffer();
- auto dataspace = renderArea.getReqDataSpace();
- auto parent = renderArea.getParentLayer();
+ auto dataspace = renderArea->getReqDataSpace();
+ auto parent = renderArea->getParentLayer();
auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance;
auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance;
@@ -6420,125 +6476,107 @@
}
captureResults.capturedDataspace = dataspace;
- const auto reqWidth = renderArea.getReqWidth();
- const auto reqHeight = renderArea.getReqHeight();
- const auto sourceCrop = renderArea.getSourceCrop();
- const auto transform = renderArea.getTransform();
- const auto rotation = renderArea.getRotationFlags();
- const auto& layerStackSpaceRect = renderArea.getLayerStackSpaceRect();
+ const auto transform = renderArea->getTransform();
+ const auto display = renderArea->getDisplayDevice();
- renderengine::DisplaySettings clientCompositionDisplay;
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompositionLayers;
-
- // assume that bounds are never offset, and that they are the same as the
- // buffer bounds.
- clientCompositionDisplay.physicalDisplay = Rect(reqWidth, reqHeight);
- clientCompositionDisplay.clip = sourceCrop;
- clientCompositionDisplay.orientation = rotation;
-
- clientCompositionDisplay.outputDataspace = dataspace;
- clientCompositionDisplay.currentLuminanceNits = displayBrightnessNits;
- clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance;
- clientCompositionDisplay.renderIntent =
- static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(renderIntent);
-
- const float colorSaturation = grayscale ? 0 : 1;
- clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation);
-
- const float alpha = RenderArea::getCaptureFillValue(renderArea.getCaptureFill());
-
- compositionengine::LayerFE::LayerSettings fillLayer;
- fillLayer.source.buffer.buffer = nullptr;
- fillLayer.source.solidColor = half3(0.0, 0.0, 0.0);
- fillLayer.geometry.boundaries =
- FloatRect(sourceCrop.left, sourceCrop.top, sourceCrop.right, sourceCrop.bottom);
- fillLayer.alpha = half(alpha);
- clientCompositionLayers.push_back(fillLayer);
-
- const auto display = renderArea.getDisplayDevice();
- std::vector<Layer*> renderedLayers;
- bool disableBlurs = false;
- traverseLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
- auto layerFE = layer->getCompositionEngineLayerFE();
- if (!layerFE) {
- return;
- }
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+ layers.reserve(layerCount);
+ std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
+ traverseLayers([&](Layer* layer) {
+ auto strongLayer = sp<Layer>::fromExisting(layer);
+ captureResults.capturedHdrLayers |= isHdrLayer(layer);
// Layer::prepareClientComposition uses the layer's snapshot to populate the resulting
// LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are
// generated with the layer's current buffer and geometry.
layer->updateSnapshot(true /* updateGeometry */);
- disableBlurs |= layer->getDrawingState().sidebandStream != nullptr;
+ layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
- Region clip(renderArea.getBounds());
- compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
- clip,
- layer->needsFilteringForScreenshots(display.get(), transform) ||
- renderArea.needsFiltering(),
- renderArea.isSecure(),
- useProtected,
- layerStackSpaceRect,
- clientCompositionDisplay.outputDataspace,
- true, /* realContentIsVisible */
- false, /* clearContent */
- disableBlurs ? compositionengine::LayerFE::ClientCompositionTargetSettings::
- BlurSetting::Disabled
- : compositionengine::LayerFE::ClientCompositionTargetSettings::
- BlurSetting::Enabled,
- isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits,
+ sp<LayerFE>& layerFE = layers.back().second;
- };
- std::optional<compositionengine::LayerFE::LayerSettings> settings;
- {
- LayerSnapshotGuard layerSnapshotGuard(layer);
- settings = layerFE->prepareClientComposition(targetSettings);
+ layerFE->mSnapshot->geomLayerTransform =
+ renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
+
+ if (layer->needsFilteringForScreenshots(display.get(), transform)) {
+ filterForScreenshot.insert(layerFE.get());
}
-
- if (!settings) {
- return;
- }
-
- settings->geometry.positionTransform =
- transform.asMatrix4() * settings->geometry.positionTransform;
- // There's no need to process blurs when we're executing region sampling,
- // we're just trying to understand what we're drawing, and doing so without
- // blurs is already a pretty good approximation.
- if (regionSampling) {
- settings->backgroundBlurRadius = 0;
- settings->blurRegions.clear();
- }
- captureResults.capturedHdrLayers |= isHdrLayer(layer);
-
- clientCompositionLayers.push_back(std::move(*settings));
- renderedLayers.push_back(layer);
});
- std::vector<renderengine::LayerSettings> clientRenderEngineLayers;
- clientRenderEngineLayers.reserve(clientCompositionLayers.size());
- std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
- std::back_inserter(clientRenderEngineLayers),
- [](compositionengine::LayerFE::LayerSettings& settings)
- -> renderengine::LayerSettings { return settings; });
-
- // Use an empty fence for the buffer fence, since we just created the buffer so
- // there is no need for synchronization with the GPU.
- base::unique_fd bufferFence;
- getRenderEngine().useProtectedContext(useProtected);
-
- constexpr bool kUseFramebufferCache = false;
- const auto future = getRenderEngine()
- .drawLayers(clientCompositionDisplay, clientRenderEngineLayers,
- buffer, kUseFramebufferCache, std::move(bufferFence))
- .share();
-
- for (auto* layer : renderedLayers) {
- layer->onLayerDisplayed(future);
+ ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
+ if (!layers.empty()) {
+ const sp<LayerFE>& layerFE = layers.back().second;
+ layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
}
- // Always switch back to unprotected context.
- getRenderEngine().useProtectedContext(false);
+ auto copyLayerFEs = [&layers]() {
+ std::vector<sp<compositionengine::LayerFE>> layerFEs;
+ layerFEs.reserve(layers.size());
+ for (const auto& [_, layerFE] : layers) {
+ layerFEs.push_back(layerFE);
+ }
+ return layerFEs;
+ };
- return future;
+ auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits,
+ displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot),
+ grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling,
+ renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+ std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
+ mFactory.createCompositionEngine();
+ compositionEngine->setRenderEngine(mRenderEngine.get());
+
+ compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace,
+ .renderIntent = renderIntent};
+
+ std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput(
+ ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine,
+ .colorProfile = colorProfile,
+ .renderArea = *renderArea,
+ .layerStack = layerStack,
+ .buffer = std::move(buffer),
+ .sdrWhitePointNits = sdrWhitePointNits,
+ .displayBrightnessNits = displayBrightnessNits,
+ .filterForScreenshot = std::move(filterForScreenshot),
+ .regionSampling = regionSampling});
+
+ const float colorSaturation = grayscale ? 0 : 1;
+ compositionengine::CompositionRefreshArgs refreshArgs{
+ .outputs = {output},
+ .layers = std::move(layerFEs),
+ .updatingOutputGeometryThisFrame = true,
+ .updatingGeometryThisFrame = true,
+ .colorTransformMatrix = calculateColorMatrix(colorSaturation),
+ };
+ compositionEngine->present(refreshArgs);
+
+ return output->getRenderSurface()->getClientTargetAcquireFence();
+ };
+
+ // If RenderEngine is threaded, we can safely call CompositionEngine::present off the main
+ // thread as the RenderEngine::drawLayers call will run on RenderEngine's thread. Otherwise,
+ // we need RenderEngine to run on the main thread so we call CompositionEngine::present
+ // immediately.
+ //
+ // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
+ // to CompositionEngine::present.
+ const bool renderEngineIsThreaded = [&]() {
+ using Type = renderengine::RenderEngine::RenderEngineType;
+ const auto type = mRenderEngine->getRenderEngineType();
+ return type == Type::THREADED || type == Type::SKIA_GL_THREADED;
+ }();
+ auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
+ : ftl::yield(present()).share();
+
+ for (auto& [layer, layerFE] : layers) {
+ layer->onLayerDisplayed(
+ ftl::Future(presentFuture)
+ .then([layerFE = std::move(layerFE)](FenceResult) {
+ return layerFE->stealCompositionResult().releaseFences.back().get();
+ })
+ .share());
+ }
+
+ return presentFuture;
}
// ---------------------------------------------------------------------------
@@ -6651,10 +6689,33 @@
return NO_ERROR;
}
-status_t SurfaceFlinger::setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
+namespace {
+FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) {
+ return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)};
+}
+
+FpsRanges translate(const gui::DisplayModeSpecs::RefreshRateRanges& aidlRanges) {
+ return FpsRanges{translate(aidlRanges.physical), translate(aidlRanges.render)};
+}
+
+gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange translate(const FpsRange& range) {
+ gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange aidlRange;
+ aidlRange.min = range.min.getValue();
+ aidlRange.max = range.max.getValue();
+ return aidlRange;
+}
+
+gui::DisplayModeSpecs::RefreshRateRanges translate(const FpsRanges& ranges) {
+ gui::DisplayModeSpecs::RefreshRateRanges aidlRanges;
+ aidlRanges.physical = translate(ranges.physical);
+ aidlRanges.render = translate(ranges.render);
+ return aidlRanges;
+}
+
+} // namespace
+
+status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
ATRACE_CALL();
if (!displayToken) {
@@ -6672,12 +6733,8 @@
return INVALID_OPERATION;
} else {
using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy;
- const Policy policy{DisplayModeId(defaultMode),
- allowGroupSwitching,
- {Fps::fromValue(primaryRefreshRateMin),
- Fps::fromValue(primaryRefreshRateMax)},
- {Fps::fromValue(appRequestRefreshRateMin),
- Fps::fromValue(appRequestRefreshRateMax)}};
+ const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges),
+ translate(specs.appRequestRanges), specs.allowGroupSwitching};
return setDesiredDisplayModeSpecsInternal(display, policy);
}
@@ -6687,16 +6744,10 @@
}
status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId* outDefaultMode,
- bool* outAllowGroupSwitching,
- float* outPrimaryRefreshRateMin,
- float* outPrimaryRefreshRateMax,
- float* outAppRequestRefreshRateMin,
- float* outAppRequestRefreshRateMax) {
+ gui::DisplayModeSpecs* outSpecs) {
ATRACE_CALL();
- if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin ||
- !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) {
+ if (!displayToken || !outSpecs) {
return BAD_VALUE;
}
@@ -6712,12 +6763,10 @@
scheduler::RefreshRateSelector::Policy policy =
display->refreshRateSelector().getDisplayManagerPolicy();
- *outDefaultMode = policy.defaultMode.value();
- *outAllowGroupSwitching = policy.allowGroupSwitching;
- *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue();
- *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue();
- *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue();
- *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue();
+ outSpecs->defaultMode = policy.defaultMode.value();
+ outSpecs->allowGroupSwitching = policy.allowGroupSwitching;
+ outSpecs->primaryRanges = translate(policy.primaryRanges);
+ outSpecs->appRequestRanges = translate(policy.appRequestRanges);
return NO_ERROR;
}
@@ -7608,18 +7657,11 @@
return binderStatusFromStatusT(status);
}
-binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, int32_t defaultMode, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
+binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
status_t status = checkAccessPermission();
if (status == OK) {
- status = mFlinger->setDesiredDisplayModeSpecs(displayToken,
- static_cast<ui::DisplayModeId>(defaultMode),
- allowGroupSwitching, primaryRefreshRateMin,
- primaryRefreshRateMax,
- appRequestRefreshRateMin,
- appRequestRefreshRateMax);
+ status = mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
}
return binderStatusFromStatusT(status);
}
@@ -7635,25 +7677,7 @@
return binderStatusFromStatusT(status);
}
- ui::DisplayModeId displayModeId;
- bool allowGroupSwitching;
- float primaryRefreshRateMin;
- float primaryRefreshRateMax;
- float appRequestRefreshRateMin;
- float appRequestRefreshRateMax;
- status = mFlinger->getDesiredDisplayModeSpecs(displayToken, &displayModeId,
- &allowGroupSwitching, &primaryRefreshRateMin,
- &primaryRefreshRateMax, &appRequestRefreshRateMin,
- &appRequestRefreshRateMax);
- if (status == NO_ERROR) {
- outSpecs->defaultMode = displayModeId;
- outSpecs->allowGroupSwitching = allowGroupSwitching;
- outSpecs->primaryRefreshRateMin = primaryRefreshRateMin;
- outSpecs->primaryRefreshRateMax = primaryRefreshRateMax;
- outSpecs->appRequestRefreshRateMin = appRequestRefreshRateMin;
- outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax;
- }
-
+ status = mFlinger->getDesiredDisplayModeSpecs(displayToken, outSpecs);
return binderStatusFromStatusT(status);
}
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index d290762..fd96fbb 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -551,17 +551,8 @@
status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
status_t removeTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId displayModeId, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin,
- float appRequestRefreshRateMax);
- status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId* outDefaultMode,
- bool* outAllowGroupSwitching,
- float* outPrimaryRefreshRateMin,
- float* outPrimaryRefreshRateMax,
- float* outAppRequestRefreshRateMin,
- float* outAppRequestRefreshRateMax);
+ const gui::DisplayModeSpecs&);
+ status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, gui::DisplayModeSpecs*);
status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken, bool* outSupport) const;
status_t setDisplayBrightness(const sp<IBinder>& displayToken,
const gui::DisplayBrightness& brightness);
@@ -694,7 +685,7 @@
void commitInputWindowCommands() REQUIRES(mStateLock);
void updateCursorAsync();
- void initScheduler(const sp<const DisplayDevice>&) REQUIRES(mStateLock);
+ void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock);
void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod);
@@ -789,7 +780,7 @@
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
bool grayscale, const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> renderScreenImpl(
- const RenderArea&, TraverseLayersFunction,
+ std::unique_ptr<RenderArea>, TraverseLayersFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
REQUIRES(kMainThreadContext);
@@ -1022,7 +1013,9 @@
void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
- void dumpVSync(std::string& result) const REQUIRES(mStateLock);
+ void dumpScheduler(std::string& result) const REQUIRES(mStateLock);
+ void dumpEvents(std::string& result) const REQUIRES(mStateLock);
+ void dumpVsync(std::string& result) const REQUIRES(mStateLock);
void dumpCompositionDisplays(std::string& result) const REQUIRES(mStateLock);
void dumpDisplays(std::string& result) const REQUIRES(mStateLock);
@@ -1109,7 +1102,6 @@
// constant members (no synchronization needed for access)
const nsecs_t mBootTime = systemTime();
- bool mGpuToCpuSupported = false;
bool mIsUserBuild = true;
// Can only accessed from the main thread, these members
@@ -1451,11 +1443,8 @@
const sp<gui::ITunnelModeEnabledListener>& listener) override;
binder::Status removeTunnelModeEnabledListener(
const sp<gui::ITunnelModeEnabledListener>& listener) override;
- binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, int32_t defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin,
- float primaryRefreshRateMax,
- float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) override;
+ binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs&) override;
binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
gui::DisplayModeSpecs* outSpecs) override;
binder::Status getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 20fa091..c8c71df 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -367,6 +367,10 @@
return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue);
}
+bool frame_rate_override_for_native_rates(bool defaultValue) {
+ return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue);
+}
+
bool enable_layer_caching(bool defaultValue) {
return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue);
}
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 080feee..5e316cf 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -96,6 +96,8 @@
bool enable_frame_rate_override(bool defaultValue);
+bool frame_rate_override_for_native_rates(bool defaultValue);
+
bool enable_layer_caching(bool defaultValue);
bool enable_sdr_dimming(bool defaultValue);
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index e73dac6..b32001c 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -55,6 +55,7 @@
TRACE_EXTRA = 1 << 3,
TRACE_HWC = 1 << 4,
TRACE_BUFFERS = 1 << 5,
+ TRACE_VIRTUAL_DISPLAYS = 1 << 6,
TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA,
};
void setTraceFlags(uint32_t flags);
diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h
index 3761f9e..ee94217 100644
--- a/services/surfaceflinger/Utils/Dumper.h
+++ b/services/surfaceflinger/Utils/Dumper.h
@@ -16,10 +16,11 @@
#pragma once
-#include <optional>
#include <string>
#include <string_view>
+#include <ftl/optional.h>
+
namespace android::utils {
// Dumps variables by appending their name and value to the output string. A variable is formatted
@@ -44,16 +45,48 @@
eol();
}
+ void dump(std::string_view name, const std::string& value) {
+ dump(name, static_cast<const std::string_view&>(value));
+ }
+
void dump(std::string_view name, bool value) {
using namespace std::string_view_literals;
dump(name, value ? "true"sv : "false"sv);
}
template <typename T>
- void dump(std::string_view name, const std::optional<T>& value) {
- using namespace std::string_view_literals;
+ void dump(std::string_view name, const std::optional<T>& opt) {
+ if (opt) {
+ dump(name, *opt);
+ } else {
+ using namespace std::string_view_literals;
+ dump(name, "nullopt"sv);
+ }
+ }
+
+ template <typename T>
+ void dump(std::string_view name, const ftl::Optional<T>& opt) {
+ dump(name, static_cast<const std::optional<T>&>(opt));
+ }
+
+ template <typename T, typename... Ts>
+ void dump(std::string_view name, const T& value, const Ts&... rest) {
+ std::string string;
+
+ constexpr bool kIsTuple = sizeof...(Ts) > 0;
+ if constexpr (kIsTuple) {
+ string += '{';
+ }
+
using std::to_string;
- dump(name, value ? to_string(*value) : "nullopt"sv);
+ string += to_string(value);
+
+ if constexpr (kIsTuple) {
+ string += ((", " + to_string(rest)) + ...);
+ string += '}';
+ }
+
+ dump(name, string);
}
struct Indent {
@@ -63,6 +96,21 @@
Dumper& dumper;
};
+ struct Section {
+ Section(Dumper& dumper, std::string_view heading) : dumper(dumper) {
+ dumper.dump({}, heading);
+ indent.emplace(dumper);
+ }
+
+ ~Section() {
+ indent.reset();
+ dumper.eol();
+ }
+
+ Dumper& dumper;
+ std::optional<Indent> indent;
+ };
+
private:
std::string& mOut;
int mIndent = 0;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 94de6e5..9ba9b90 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -505,16 +505,8 @@
}
void getDesiredDisplayModeSpecs(sp<IBinder> &display) {
- ui::DisplayModeId outDefaultMode;
- bool outAllowGroupSwitching;
- float outPrimaryRefreshRateMin;
- float outPrimaryRefreshRateMax;
- float outAppRequestRefreshRateMin;
- float outAppRequestRefreshRateMax;
- mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching,
- &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax,
- &outAppRequestRefreshRateMin,
- &outAppRequestRefreshRateMax);
+ gui::DisplayModeSpecs _;
+ mFlinger->getDesiredDisplayModeSpecs(display, &_);
}
void setVsyncConfig(FuzzedDataProvider *fdp) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index f2d008d..bd11a37 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -249,7 +249,9 @@
scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
(std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
- dump<scheduler::TestableScheduler>(scheduler, &mFdp);
+ std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength);
+ utils::Dumper dumper(result);
+ scheduler->dump(dumper);
}
void SchedulerFuzzer::fuzzVSyncReactor() {
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index bcbe21a..28da81f 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -445,6 +445,18 @@
prop_name: "ro.surface_flinger.enable_frame_rate_override"
}
+# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate
+# to native display refresh rates only. Before introducing this flag, native display refresh rates
+# was the default behvaiour. With this flag we can control which behaviour we want explicitly.
+# This flag is intoruduced as a fail-safe machanism and planned to be defaulted to false.
+prop {
+ api_name: "frame_rate_override_for_native_rates"
+ type: Boolean
+ scope: Public
+ access: Readonly
+ prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates"
+}
+
# Enables Layer Caching
prop {
api_name: "enable_layer_caching"
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 348a462..0dfb80e 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -61,6 +61,10 @@
prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays"
}
prop {
+ api_name: "frame_rate_override_for_native_rates"
+ prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates"
+ }
+ prop {
api_name: "has_HDR_display"
prop_name: "ro.surface_flinger.has_HDR_display"
}
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 4f04934..1676844 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -207,23 +207,12 @@
TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) {
const auto display = getFirstDisplayToken();
- ui::DisplayModeId defaultMode;
- bool allowGroupSwitching;
- float primaryFpsMin;
- float primaryFpsMax;
- float appRequestFpsMin;
- float appRequestFpsMax;
- status_t res =
- SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &defaultMode,
- &allowGroupSwitching, &primaryFpsMin,
- &primaryFpsMax, &appRequestFpsMin,
- &appRequestFpsMax);
+ gui::DisplayModeSpecs specs;
+ status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &specs);
ASSERT_EQ(res, NO_ERROR);
+ gui::DisplayModeSpecs setSpecs;
std::function<status_t()> condition = [=]() {
- return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode,
- allowGroupSwitching, primaryFpsMin,
- primaryFpsMax, appRequestFpsMin,
- appRequestFpsMax);
+ return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs);
};
ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
}
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 02c934e..10dae46 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -39,37 +39,19 @@
*/
class RefreshRateRangeTest : public ::testing::Test {
private:
- ui::DisplayModeId initialDefaultMode;
- bool initialAllowGroupSwitching;
- float initialPrimaryMin;
- float initialPrimaryMax;
- float initialAppRequestMin;
- float initialAppRequestMax;
+ gui::DisplayModeSpecs mSpecs;
protected:
void SetUp() override {
const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
ASSERT_FALSE(ids.empty());
mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
- status_t res =
- SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken,
- &initialDefaultMode,
- &initialAllowGroupSwitching,
- &initialPrimaryMin,
- &initialPrimaryMax,
- &initialAppRequestMin,
- &initialAppRequestMax);
+ status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs);
ASSERT_EQ(res, NO_ERROR);
}
void TearDown() override {
- status_t res =
- SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, initialDefaultMode,
- initialAllowGroupSwitching,
- initialPrimaryMin,
- initialPrimaryMax,
- initialAppRequestMin,
- initialAppRequestMax);
+ status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, mSpecs);
ASSERT_EQ(res, NO_ERROR);
}
@@ -85,61 +67,39 @@
ASSERT_EQ(res, NO_ERROR);
ASSERT_GT(modes.size(), 0);
+ gui::DisplayModeSpecs setSpecs;
+ setSpecs.allowGroupSwitching = false;
for (size_t i = 0; i < modes.size(); i++) {
- res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate);
+ setSpecs.defaultMode = modes[i].id;
+ setSpecs.primaryRanges.physical.min = modes[i].refreshRate;
+ setSpecs.primaryRanges.physical.max = modes[i].refreshRate;
+ setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
+ setSpecs.appRequestRanges = setSpecs.primaryRanges;
+ res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
ASSERT_EQ(res, NO_ERROR);
- ui::DisplayModeId defaultConfig;
- bool allowGroupSwitching;
- float primaryRefreshRateMin;
- float primaryRefreshRateMax;
- float appRequestRefreshRateMin;
- float appRequestRefreshRateMax;
- res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig,
- &allowGroupSwitching,
- &primaryRefreshRateMin,
- &primaryRefreshRateMax,
- &appRequestRefreshRateMin,
- &appRequestRefreshRateMax);
+ gui::DisplayModeSpecs getSpecs;
+ res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
ASSERT_EQ(res, NO_ERROR);
- ASSERT_EQ(defaultConfig, i);
- ASSERT_EQ(allowGroupSwitching, false);
- ASSERT_EQ(primaryRefreshRateMin, modes[i].refreshRate);
- ASSERT_EQ(primaryRefreshRateMax, modes[i].refreshRate);
- ASSERT_EQ(appRequestRefreshRateMin, modes[i].refreshRate);
- ASSERT_EQ(appRequestRefreshRateMax, modes[i].refreshRate);
+ ASSERT_EQ(setSpecs, getSpecs);
}
}
void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) {
- status_t res =
- SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching,
- 0.f, 90.f, 0.f, 90.f);
- ASSERT_EQ(res, NO_ERROR);
- ui::DisplayModeId defaultConfig;
- bool newAllowGroupSwitching;
- float primaryRefreshRateMin;
- float primaryRefreshRateMax;
- float appRequestRefreshRateMin;
- float appRequestRefreshRateMax;
+ gui::DisplayModeSpecs setSpecs;
+ setSpecs.defaultMode = 0;
+ setSpecs.allowGroupSwitching = allowGroupSwitching;
+ setSpecs.primaryRanges.physical.min = 0;
+ setSpecs.primaryRanges.physical.max = 90;
+ setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
+ setSpecs.appRequestRanges = setSpecs.primaryRanges;
- res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig,
- &newAllowGroupSwitching,
- &primaryRefreshRateMin,
- &primaryRefreshRateMax,
- &appRequestRefreshRateMin,
- &appRequestRefreshRateMax);
+ status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
ASSERT_EQ(res, NO_ERROR);
- ASSERT_EQ(defaultConfig, 0);
- ASSERT_EQ(newAllowGroupSwitching, allowGroupSwitching);
- ASSERT_EQ(primaryRefreshRateMin, 0.f);
- ASSERT_EQ(primaryRefreshRateMax, 90.f);
- ASSERT_EQ(appRequestRefreshRateMin, 0.f);
- ASSERT_EQ(appRequestRefreshRateMax, 90.f);
+ gui::DisplayModeSpecs getSpecs;
+ res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
+ ASSERT_EQ(res, NO_ERROR);
+ ASSERT_EQ(setSpecs, getSpecs);
}
TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5daa398..d88da4d 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -150,7 +150,7 @@
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V2-cpp",
+ "android.hardware.power-V4-cpp",
"libaidlcommonsupport",
"libcompositionengine_mocks",
"libcompositionengine",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 7148c11..06b9caa 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -244,8 +244,8 @@
HAL_PIXEL_FORMAT_RGBA_8888, 1,
usage);
- auto future = mFlinger.renderScreenImpl(*renderArea, traverseLayers, mCaptureScreenBuffer,
- forSystem, regionSampling);
+ auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers,
+ mCaptureScreenBuffer, forSystem, regionSampling);
ASSERT_TRUE(future.valid());
const auto fenceResult = future.get();
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index e7ae53c..cedb7eb 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -37,6 +37,7 @@
namespace hal = android::hardware::graphics::composer::hal;
+using Config = RefreshRateSelector::Config;
using LayerRequirement = RefreshRateSelector::LayerRequirement;
using LayerVoteType = RefreshRateSelector::LayerVoteType;
using SetPolicyResult = RefreshRateSelector::SetPolicyResult;
@@ -357,7 +358,7 @@
constexpr bool kAllowGroupSwitching = true;
EXPECT_EQ(SetPolicyResult::Changed,
selector.setDisplayManagerPolicy(
- {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}));
+ {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate());
}
}
@@ -1105,7 +1106,7 @@
TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
const auto refreshRates =
selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
@@ -1126,7 +1127,7 @@
TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt,
RefreshRateOrder::Descending);
@@ -1351,8 +1352,10 @@
getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+ constexpr FpsRange k90 = {90_Hz, 90_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -1373,8 +1376,11 @@
getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+ constexpr FpsRange k60 = {60_Hz, 60_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {k60, k60}, {k60_90, k60_90}}));
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
auto& lr = layers[0];
@@ -1513,8 +1519,11 @@
getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+ constexpr FpsRange k90 = {90_Hz, 90_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
const auto [ranking, signals] = selector.getRankedRefreshRates({}, {});
EXPECT_EQ(ranking.front().modePtr, kMode90);
@@ -1849,8 +1858,11 @@
return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId();
};
+ constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
+ constexpr FpsRange k30_90 = {30_Hz, 90_Hz};
+
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId());
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
@@ -1875,7 +1887,7 @@
getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
@@ -1904,7 +1916,7 @@
};
EXPECT_EQ(SetPolicyResult::Changed,
- selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
// Idle should be lower priority than touch boost.
{
@@ -2023,7 +2035,8 @@
TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride =
+ Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
auto& explicitExactLayer = layers[0];
@@ -2089,7 +2102,8 @@
TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) {
TestableRefreshRateSelector selector(kModes_60_120, kModeId60,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride =
+ Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
auto& explicitExactLayer = layers[0];
@@ -2114,7 +2128,8 @@
TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride =
+ Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
auto& explicitDefaultLayer = layers[0];
@@ -2308,7 +2323,7 @@
TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.weight = 1.f}};
layers[0].name = "Test layer";
@@ -2346,7 +2361,7 @@
TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
{.ownerUid = 5678, .weight = 1.f}};
@@ -2379,7 +2394,7 @@
TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
+ {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
layers[0].name = "Test layer";
@@ -2417,5 +2432,139 @@
EXPECT_TRUE(frameRateOverrides.empty());
}
+TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_Enabled) {
+ RefreshRateSelector selector(kModes_60_120, kModeId120,
+ {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::NoVote;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Min;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Max;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Heuristic;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_F(RefreshRateSelectorTest,
+ getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_EnabledForNativeRefreshRates) {
+ RefreshRateSelector selector(kModes_60_120, kModeId120,
+ {.enableFrameRateOverride =
+ Config::FrameRateOverride::EnabledForNativeRefreshRates});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::NoVote;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Min;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Max;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+
+ layers[0].vote = LayerVoteType::Heuristic;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
+ TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
+ {.enableFrameRateOverride =
+ Config::FrameRateOverride::Enabled});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {60_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ layers[0].name = "30Hz";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {30_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {30_Hz, 30_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ layers[0].name = "60Hz";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+}
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 066083f..ea4666e 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -20,7 +20,6 @@
#include <mutex>
-#include "FakeDisplayInjector.h"
#include "Scheduler/EventThread.h"
#include "Scheduler/RefreshRateSelector.h"
#include "TestableScheduler.h"
@@ -41,7 +40,6 @@
using MockEventThread = android::mock::EventThread;
using MockLayer = android::mock::MockLayer;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
class SchedulerTest : public testing::Test {
protected:
@@ -90,10 +88,6 @@
sp<MockEventThreadConnection> mEventThreadConnection;
TestableSurfaceFlinger mFlinger;
- Hwc2::mock::PowerAdvisor mPowerAdvisor;
- sp<android::mock::NativeWindow> mNativeWindow = sp<android::mock::NativeWindow>::make();
-
- FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
};
SchedulerTest::SchedulerTest() {
@@ -240,14 +234,11 @@
}
TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
- const auto display = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
- },
- {.displayId = kDisplayId1});
+ const auto selectorPtr =
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes, kDisplay1Mode60->getId());
- mScheduler->registerDisplay(display);
- mScheduler->setRefreshRateSelector(display->holdRefreshRateSelector());
+ mScheduler->registerDisplay(kDisplayId1, selectorPtr);
+ mScheduler->setRefreshRateSelector(selectorPtr);
const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
@@ -269,13 +260,9 @@
}
TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
- const auto display = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
- },
- {.displayId = kDisplayId1});
-
- mScheduler->registerDisplay(display);
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
std::vector<RefreshRateSelector::LayerRequirement> layers =
std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
@@ -314,23 +301,16 @@
EXPECT_EQ(choice->get(), DisplayModeChoice(kDisplay1Mode120, globalSignals));
mScheduler->unregisterDisplay(kDisplayId1);
- EXPECT_TRUE(mScheduler->mutableDisplays().empty());
+ EXPECT_FALSE(mScheduler->hasRefreshRateSelectors());
}
TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
- const auto display1 = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(kDisplay1Modes, kDisplay1Mode60->getId());
- },
- {.displayId = kDisplayId1, .hwcDisplayId = 42, .isPrimary = true});
- const auto display2 = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(kDisplay2Modes, kDisplay2Mode60->getId());
- },
- {.displayId = kDisplayId2, .hwcDisplayId = 41, .isPrimary = false});
-
- mScheduler->registerDisplay(display1);
- mScheduler->registerDisplay(display2);
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
TestableScheduler::DisplayModeChoiceMap expectedChoices;
@@ -380,13 +360,10 @@
}
{
// This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
- const auto display3 = mFakeDisplayInjector.injectInternalDisplay(
- [&](FakeDisplayDeviceInjector& injector) {
- injector.setDisplayModes(kDisplay3Modes, kDisplay3Mode60->getId());
- },
- {.displayId = kDisplayId3, .hwcDisplayId = 40, .isPrimary = false});
-
- mScheduler->registerDisplay(display3);
+ mScheduler
+ ->registerDisplay(kDisplayId3,
+ std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+ kDisplay3Mode60->getId()));
const GlobalSignals globalSignals = {.touch = true};
mScheduler->replaceTouchTimer(10);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 4c25463..05d0ebf 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "mock/MockDisplayModeSpecs.h"
#include "mock/MockEventThread.h"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -119,8 +120,9 @@
mFlinger.onActiveDisplayChanged(mDisplay);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+ 120));
ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
@@ -157,8 +159,9 @@
mFlinger.onActiveDisplayChanged(mDisplay);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- true, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
+ 120));
ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
@@ -191,8 +194,9 @@
mFlinger.onActiveDisplayChanged(mDisplay);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+ 120));
const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
EXPECT_CALL(*mComposer,
@@ -202,8 +206,9 @@
mFlinger.commit();
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(),
- false, 0.f, 180.f, 0.f, 180.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId120.value(), false, 0,
+ 180));
ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
@@ -232,8 +237,9 @@
mFlinger.onActiveDisplayChanged(mDisplay);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
+ 120));
ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K);
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 2814d38..ba214d5 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -17,6 +17,7 @@
#pragma once
#include <Scheduler/Scheduler.h>
+#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gui/ISurfaceComposer.h>
@@ -32,15 +33,13 @@
class TestableScheduler : public Scheduler, private ICompositor {
public:
- TestableScheduler(std::shared_ptr<RefreshRateSelector> selectorPtr,
- ISchedulerCallback& callback)
+ TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
: TestableScheduler(std::make_unique<mock::VsyncController>(),
std::make_unique<mock::VSyncTracker>(), std::move(selectorPtr),
callback) {}
TestableScheduler(std::unique_ptr<VsyncController> controller,
- std::unique_ptr<VSyncTracker> tracker,
- std::shared_ptr<RefreshRateSelector> selectorPtr,
+ std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
ISchedulerCallback& callback)
: Scheduler(*this, callback, Feature::kContentDetection) {
mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
@@ -68,16 +67,20 @@
auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
- auto& mutableLayerHistory() { return mLayerHistory; }
+ auto refreshRateSelector() { return holdRefreshRateSelector(); }
+ bool hasRefreshRateSelectors() const { return !mRefreshRateSelectors.empty(); }
- auto& mutableDisplays() { return mDisplays; }
+ void setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ return Scheduler::setRefreshRateSelector(std::move(selectorPtr));
+ }
+
+ auto& mutableLayerHistory() { return mLayerHistory; }
size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
}
- auto refreshRateSelector() { return holdRefreshRateSelector(); }
-
size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
return mLayerHistory.mActiveLayerInfos.size();
}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index ff79ce0..df53f15 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -37,6 +37,7 @@
#include "FrontEnd/LayerHandle.h"
#include "Layer.h"
#include "NativeWindowSurface.h"
+#include "RenderArea.h"
#include "Scheduler/MessageQueue.h"
#include "Scheduler/RefreshRateSelector.h"
#include "StartPropertySetThread.h"
@@ -399,14 +400,14 @@
return mFlinger->setPowerModeInternal(display, mode);
}
- auto renderScreenImpl(const RenderArea& renderArea,
- SurfaceFlinger::TraverseLayersFunction traverseLayers,
- const std::shared_ptr<renderengine::ExternalTexture>& buffer,
- bool forSystem, bool regionSampling) {
+ auto renderScreenImpl(std::unique_ptr<RenderArea> renderArea,
+ SurfaceFlinger::TraverseLayersFunction traverseLayers,
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+ bool forSystem, bool regionSampling) {
ScreenCaptureResults captureResults;
return FTL_FAKE_GUARD(kMainThreadContext,
- mFlinger->renderScreenImpl(renderArea, traverseLayers, buffer,
- forSystem, regionSampling,
+ mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
+ buffer, forSystem, regionSampling,
false /* grayscale */, captureResults));
}
@@ -455,14 +456,9 @@
return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
}
- auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin,
- float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
- return mFlinger->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
- primaryRefreshRateMin, primaryRefreshRateMax,
- appRequestRefreshRateMin,
- appRequestRefreshRateMax);
+ auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
+ return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
}
void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) {
@@ -842,7 +838,8 @@
sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
if (mFlinger.scheduler()) {
- mFlinger.scheduler()->registerDisplay(display);
+ mFlinger.scheduler()->registerDisplay(display->getPhysicalId(),
+ display->holdRefreshRateSelector());
}
DisplayDeviceState state;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 439f6f4..f4ded21 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -23,6 +23,7 @@
using android::binder::Status;
using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
using namespace android::hardware::power;
@@ -40,6 +41,8 @@
MOCK_METHOD(std::string, getInterfaceHash, (), (override));
MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
+ MOCK_METHOD(Status, sendHint, (SessionHint), (override));
+ MOCK_METHOD(Status, setThreads, (const ::std::vector<int32_t>&), (override));
};
} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
new file mode 100644
index 0000000..a71e82c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/gui/DisplayModeSpecs.h>
+
+namespace android::mock {
+
+inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching,
+ float minFps, float maxFps) {
+ gui::DisplayModeSpecs specs;
+ specs.defaultMode = defaultMode;
+ specs.allowGroupSwitching = allowGroupSwitching;
+ specs.primaryRanges.physical.min = minFps;
+ specs.primaryRanges.physical.max = maxFps;
+ specs.primaryRanges.render = specs.primaryRanges.physical;
+ specs.appRequestRanges = specs.primaryRanges;
+ return specs;
+}
+
+} // namespace android::mock
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index abcac3c..87b3a89 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -787,13 +787,14 @@
std::vector<VkSurfaceFormatKHR> all_formats = {
{VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
- // Also allow to use PASS_THROUGH + HAL_DATASPACE_ARBITRARY
- {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT},
- {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT},
};
if (colorspace_ext) {
all_formats.emplace_back(VkSurfaceFormatKHR{
+ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ all_formats.emplace_back(VkSurfaceFormatKHR{
+ VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ all_formats.emplace_back(VkSurfaceFormatKHR{
VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT});
}
@@ -812,16 +813,22 @@
if (AHardwareBuffer_isSupported(&desc)) {
all_formats.emplace_back(VkSurfaceFormatKHR{
VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ if (colorspace_ext) {
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_R5G6B5_UNORM_PACK16,
+ VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ }
}
desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
if (AHardwareBuffer_isSupported(&desc)) {
all_formats.emplace_back(VkSurfaceFormatKHR{
VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
- all_formats.emplace_back(VkSurfaceFormatKHR{
- VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ if (colorspace_ext) {
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
+ VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ }
if (wide_color_support) {
all_formats.emplace_back(
VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
@@ -837,9 +844,11 @@
all_formats.emplace_back(
VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
- all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
- VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ if (colorspace_ext) {
+ all_formats.emplace_back(
+ VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+ VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ }
if (wide_color_support) {
all_formats.emplace_back(
VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
@@ -849,9 +858,10 @@
desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
if (AHardwareBuffer_isSupported(&desc)) {
- all_formats.emplace_back(
- VkSurfaceFormatKHR{VK_FORMAT_R8_UNORM,
- VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ if (colorspace_ext) {
+ all_formats.emplace_back(VkSurfaceFormatKHR{
+ VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+ }
}
// NOTE: Any new formats that are added must be coordinated across different