Merge "Dynamically disable blurs when tunnel mode is used"
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 987adaf..1994e56 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -72,15 +72,18 @@
cc_test {
name: "lshal_test",
test_suites: ["device-tests"],
- defaults: ["lshal_defaults"],
+ defaults: [
+ "libvintf_static_user_defaults",
+ "lshal_defaults"
+ ],
gtest: true,
static_libs: [
"android.hardware.tests.inheritance@1.0",
"libgmock",
+ "libvintf",
],
shared_libs: [
"libhidlbase",
- "libvintf",
],
srcs: [
"test.cpp"
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index f2d223d..d964d25 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -133,6 +133,7 @@
public:
// unlocked objects
bool mRequestingSid = false;
+ bool mInheritRt = false;
sp<IBinder> mExtension;
int mPolicy = SCHED_NORMAL;
int mPriority = 0;
@@ -327,6 +328,27 @@
return e->mPriority;
}
+bool BBinder::isInheritRt() {
+ Extras* e = mExtras.load(std::memory_order_acquire);
+
+ return e && e->mInheritRt;
+}
+
+void BBinder::setInheritRt(bool inheritRt) {
+ Extras* e = mExtras.load(std::memory_order_acquire);
+
+ if (!e) {
+ if (!inheritRt) {
+ return;
+ }
+
+ e = getOrCreateExtras();
+ if (!e) return; // out of memory
+ }
+
+ e->mInheritRt = inheritRt;
+}
+
pid_t BBinder::getDebugPid() {
return getpid();
}
diff --git a/libs/binder/BufferedTextOutput.cpp b/libs/binder/BufferedTextOutput.cpp
index 8cf6097..88c85bf 100644
--- a/libs/binder/BufferedTextOutput.cpp
+++ b/libs/binder/BufferedTextOutput.cpp
@@ -18,7 +18,6 @@
#include <binder/Debug.h>
#include <cutils/atomic.h>
-#include <cutils/threads.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
#include <utils/Vector.h>
@@ -91,22 +90,6 @@
static pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;
-static thread_store_t tls;
-
-BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
-{
- ThreadState* ts = (ThreadState*) thread_store_get( &tls );
- if (ts) return ts;
- ts = new ThreadState;
- thread_store_set( &tls, ts, threadDestructor );
- return ts;
-}
-
-void BufferedTextOutput::threadDestructor(void *st)
-{
- delete ((ThreadState*)st);
-}
-
static volatile int32_t gSequence = 0;
static volatile int32_t gFreeBufferIndex = -1;
@@ -266,16 +249,14 @@
BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
{
if ((mFlags&MULTITHREADED) != 0) {
- ThreadState* ts = getThreadState();
- if (ts) {
- while (ts->states.size() <= (size_t)mIndex) ts->states.add(nullptr);
- BufferState* bs = ts->states[mIndex].get();
- if (bs != nullptr && bs->seq == mSeq) return bs;
-
- ts->states.editItemAt(mIndex) = new BufferState(mIndex);
- bs = ts->states[mIndex].get();
- if (bs != nullptr) return bs;
- }
+ thread_local ThreadState ts;
+ while (ts.states.size() <= (size_t)mIndex) ts.states.add(nullptr);
+ BufferState* bs = ts.states[mIndex].get();
+ if (bs != nullptr && bs->seq == mSeq) return bs;
+
+ ts.states.editItemAt(mIndex) = new BufferState(mIndex);
+ bs = ts.states[mIndex].get();
+ if (bs != nullptr) return bs;
}
return mGlobalState;
diff --git a/libs/binder/BufferedTextOutput.h b/libs/binder/BufferedTextOutput.h
index 1b27bb2..fdd532a 100644
--- a/libs/binder/BufferedTextOutput.h
+++ b/libs/binder/BufferedTextOutput.h
@@ -47,10 +47,7 @@
private:
struct BufferState;
struct ThreadState;
-
- static ThreadState*getThreadState();
- static void threadDestructor(void *st);
-
+
BufferState*getBuffer() const;
uint32_t mFlags;
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 0377075..91e465f 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -164,12 +164,8 @@
ALOGE("Invalid object type 0x%08x", obj.hdr.type);
}
-status_t Parcel::finishFlattenBinder(
- const sp<IBinder>& binder, const flat_binder_object& flat)
+status_t Parcel::finishFlattenBinder(const sp<IBinder>& binder)
{
- status_t status = writeObject(flat, false);
- if (status != OK) return status;
-
internal::Stability::tryMarkCompilationUnit(binder.get());
auto category = internal::Stability::getCategory(binder.get());
return writeInt32(category.repr());
@@ -226,6 +222,9 @@
if (local->isRequestingSid()) {
obj.flags |= FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
}
+ if (local->isInheritRt()) {
+ obj.flags |= FLAT_BINDER_FLAG_INHERIT_RT;
+ }
obj.hdr.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local);
@@ -238,7 +237,10 @@
obj.flags |= schedBits;
- return finishFlattenBinder(binder, obj);
+ status_t status = writeObject(obj, false);
+ if (status != OK) return status;
+
+ return finishFlattenBinder(binder);
}
status_t Parcel::unflattenBinder(sp<IBinder>* out) const
@@ -2056,8 +2058,11 @@
if (size >= 0 && size < INT32_MAX) {
*outLen = size;
const char* str = (const char*)readInplace(size+1);
- if (str != nullptr && str[size] == '\0') {
- return str;
+ if (str != nullptr) {
+ if (str[size] == '\0') {
+ return str;
+ }
+ android_errorWriteLog(0x534e4554, "172655291");
}
}
*outLen = 0;
@@ -2139,8 +2144,11 @@
if (size >= 0 && size < INT32_MAX) {
*outLen = size;
const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));
- if (str != nullptr && str[size] == u'\0') {
- return str;
+ if (str != nullptr) {
+ if (str[size] == u'\0') {
+ return str;
+ }
+ android_errorWriteLog(0x534e4554, "172655291");
}
}
*outLen = 0;
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 83ca687..9aedf28 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -130,7 +130,7 @@
// The root object is special since we get it directly from the driver, it is never
// written by Parcell::writeStrongBinder.
- internal::Stability::tryMarkCompilationUnit(context.get());
+ internal::Stability::markCompilationUnit(context.get());
return context;
}
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1cfb560..97e282e 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -38,6 +38,9 @@
"name": "aidl_lazy_test"
},
{
+ "name": "aidl_integration_test"
+ },
+ {
"name": "libbinderthreadstateutils_test"
},
{
@@ -56,6 +59,9 @@
},
{
"name": "rustBinderTest"
+ },
+ {
+ "name": "binderRustNdkInteropTest"
}
]
}
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index d6da397..7079544 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -87,6 +87,11 @@
int getMinSchedulerPolicy();
int getMinSchedulerPriority();
+ // Whether realtime scheduling policies are inherited.
+ bool isInheritRt();
+ // This must be called before the object is sent to another process. Not thread safe.
+ void setInheritRt(bool inheritRt);
+
pid_t getDebugPid();
protected:
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index cfe1f3a..9f5260a 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -522,8 +522,7 @@
status_t validateReadData(size_t len) const;
void updateWorkSourceRequestHeaderPosition() const;
- status_t finishFlattenBinder(const sp<IBinder>& binder,
- const flat_binder_object& flat);
+ status_t finishFlattenBinder(const sp<IBinder>& binder);
status_t finishUnflattenBinder(const sp<IBinder>& binder, sp<IBinder>* out) const;
status_t flattenBinder(const sp<IBinder>& binder);
status_t unflattenBinder(sp<IBinder>* out) const;
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 037ee95..ed3b9ec 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -21,7 +21,8 @@
use crate::proxy::{DeathRecipient, SpIBinder};
use crate::sys;
-use std::ffi::{c_void, CString};
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
use std::os::unix::io::AsRawFd;
use std::ptr;
@@ -205,6 +206,22 @@
pub(crate) unsafe fn from_ptr(ptr: *const sys::AIBinder_Class) -> InterfaceClass {
InterfaceClass(ptr)
}
+
+ /// Get the interface descriptor string of this class.
+ pub fn get_descriptor(&self) -> String {
+ unsafe {
+ // SAFETY: The descriptor returned by AIBinder_Class_getDescriptor
+ // is always a two-byte null terminated sequence of u16s. Thus, we
+ // can continue reading from the pointer until we hit a null value,
+ // and this pointer can be a valid slice if the slice length is <=
+ // the number of u16 elements before the null terminator.
+
+ let raw_descriptor: *const c_char = sys::AIBinder_Class_getDescriptor(self.0);
+ CStr::from_ptr(raw_descriptor).to_str()
+ .expect("Expected valid UTF-8 string from AIBinder_Class_getDescriptor")
+ .into()
+ }
+ }
}
impl From<InterfaceClass> for *const sys::AIBinder_Class {
@@ -507,12 +524,7 @@
}
fn from_binder(mut binder: $crate::SpIBinder) -> $crate::Result<Self> {
- use $crate::AssociateClass;
- if binder.associate_class(<$native as $crate::Remotable>::get_class()) {
- Ok(Self { binder, $($fname: $finit),* })
- } else {
- Err($crate::StatusCode::BAD_TYPE)
- }
+ Ok(Self { binder, $($fname: $finit),* })
}
}
@@ -567,16 +579,35 @@
impl $crate::FromIBinder for dyn $interface {
fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<Box<dyn $interface>> {
use $crate::AssociateClass;
- if !ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
- return Err($crate::StatusCode::BAD_TYPE.into());
+
+ let existing_class = ibinder.get_class();
+ if let Some(class) = existing_class {
+ if class != <$native as $crate::Remotable>::get_class() &&
+ class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor()
+ {
+ // The binder object's descriptor string matches what we
+ // expect. We still need to treat this local or already
+ // associated object as remote, because we can't cast it
+ // into a Rust service object without a matching class
+ // pointer.
+ return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+ }
}
- let service: $crate::Result<$crate::Binder<$native>> = std::convert::TryFrom::try_from(ibinder.clone());
- if let Ok(service) = service {
- Ok(Box::new(service))
- } else {
- Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?))
+ if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
+ let service: $crate::Result<$crate::Binder<$native>> =
+ std::convert::TryFrom::try_from(ibinder.clone());
+ if let Ok(service) = service {
+ // We were able to associate with our expected class and
+ // the service is local.
+ return Ok(Box::new(service));
+ } else {
+ // Service is remote
+ return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+ }
}
+
+ Err($crate::StatusCode::BAD_TYPE.into())
}
}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 5002fc6..485bb42 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -91,7 +91,7 @@
/// Return the interface class of this binder object, if associated with
/// one.
- pub(crate) fn get_class(&mut self) -> Option<InterfaceClass> {
+ pub fn get_class(&mut self) -> Option<InterfaceClass> {
unsafe {
// Safety: `SpIBinder` guarantees that it always contains a valid
// `AIBinder` pointer. `AIBinder_getClass` returns either a null
diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp
index 3db40ba..5ae9c53 100644
--- a/libs/binder/rust/tests/Android.bp
+++ b/libs/binder/rust/tests/Android.bp
@@ -30,3 +30,52 @@
auto_gen_config: false,
test_suites: ["general-tests"],
}
+
+cc_test {
+ name: "binderRustNdkInteropTest",
+ srcs: [
+ "binderRustNdkInteropTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "IBinderRustNdkInteropTest-ndk_platform",
+ "libbinder_ndk_rust_interop",
+ ],
+ test_suites: ["general-tests"],
+ require_root: true,
+
+ // rustBinderTestService uses a custom config
+ auto_gen_config: true,
+}
+
+aidl_interface {
+ name: "IBinderRustNdkInteropTest",
+ unstable: true,
+ srcs: [
+ "IBinderRustNdkInteropTest.aidl",
+ "IBinderRustNdkInteropTestOther.aidl",
+ ],
+ backend: {
+ ndk: {
+ enabled: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_ffi_static {
+ name: "libbinder_ndk_rust_interop",
+ crate_name: "binder_ndk_rust_interop",
+ srcs: [
+ "ndk_rust_interop.rs",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ "IBinderRustNdkInteropTest-rust",
+ ],
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
new file mode 100644
index 0000000..7f5e837
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+interface IBinderRustNdkInteropTest {
+ @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
new file mode 100644
index 0000000..82a0323
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+interface IBinderRustNdkInteropTestOther {
+ @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/binderRustNdkInteropTest.cpp b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
new file mode 100644
index 0000000..59ca6ed
--- /dev/null
+++ b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/BnBinderRustNdkInteropTest.h>
+#include <aidl/IBinderRustNdkInteropTest.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/Status.h>
+#include <gtest/gtest.h>
+
+using namespace android;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using ::ndk::SpAIBinder;
+
+static const char* kNdkServerName = "NdkServer-BinderRustNdkInteropTest";
+static const char* kRustServerName = "RustServer-BinderRustNdkInteropTest";
+
+extern "C" {
+int rust_call_ndk(const char* service_name);
+int rust_start_service(const char* service_name);
+}
+
+class NdkServer : public aidl::BnBinderRustNdkInteropTest {
+ ScopedAStatus echo(const std::string& in, std::string* out) override {
+ *out = in;
+ return ScopedAStatus::ok();
+ }
+};
+
+TEST(RustNdkInterop, RustCanCallNdk) {
+ ASSERT_EQ(STATUS_OK, rust_call_ndk(kNdkServerName));
+}
+
+TEST(RustNdkInterop, NdkCanCallRust) {
+ ASSERT_EQ(STATUS_OK, rust_start_service(kRustServerName));
+
+ SpAIBinder binder = SpAIBinder(AServiceManager_checkService(kRustServerName));
+ ASSERT_NE(nullptr, binder.get());
+ EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
+
+ auto interface = aidl::IBinderRustNdkInteropTest::fromBinder(binder);
+ // TODO(b/167723746): this test requires that fromBinder allow association
+ // with an already associated local binder by treating it as remote.
+ EXPECT_EQ(interface, nullptr);
+
+ // std::string in("testing");
+ // std::string out;
+ // EXPECT_TRUE(interface->echo(in, &out).isOk());
+ // EXPECT_EQ(in, out);
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // so we can host a client and service concurrently
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+
+ std::shared_ptr<NdkServer> ndkServer = SharedRefBase::make<NdkServer>();
+ EXPECT_EQ(STATUS_OK, AServiceManager_addService(ndkServer->asBinder().get(), kNdkServerName));
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 953d328..bb8c492 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -173,6 +173,30 @@
}
}
+/// Trivial testing binder interface
+pub trait ITestSameDescriptor: Interface {}
+
+declare_binder_interface! {
+ ITestSameDescriptor["android.os.ITest"] {
+ native: BnTestSameDescriptor(on_transact_same_descriptor),
+ proxy: BpTestSameDescriptor,
+ }
+}
+
+fn on_transact_same_descriptor(
+ _service: &dyn ITestSameDescriptor,
+ _code: TransactionCode,
+ _data: &Parcel,
+ _reply: &mut Parcel,
+) -> binder::Result<()> {
+ Ok(())
+}
+
+impl ITestSameDescriptor for BpTestSameDescriptor {}
+
+impl ITestSameDescriptor for Binder<BnTestSameDescriptor> {}
+
+
#[cfg(test)]
mod tests {
use selinux_bindgen as selinux_sys;
@@ -185,9 +209,9 @@
use std::thread;
use std::time::Duration;
- use binder::{DeathRecipient, FromIBinder, IBinder, SpIBinder, StatusCode};
+ use binder::{Binder, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode};
- use super::{ITest, RUST_SERVICE_BINARY};
+ use super::{BnTest, ITest, ITestSameDescriptor, RUST_SERVICE_BINARY, TestService};
pub struct ScopedServiceProcess(Child);
@@ -435,4 +459,40 @@
assert_eq!(extension.test().unwrap(), extension_name);
}
}
+
+ /// Test re-associating a local binder object with a different class.
+ ///
+ /// This is needed because different binder service (e.g. NDK vs Rust)
+ /// implementations are incompatible and must not be interchanged. A local
+ /// service with the same descriptor string but a different class pointer
+ /// may have been created by an NDK service and is therefore incompatible
+ /// with the Rust service implementation. It must be treated as remote and
+ /// all API calls parceled and sent through transactions.
+ ///
+ /// Further tests of this behavior with the C NDK and Rust API are in
+ /// rust_ndk_interop.rs
+ #[test]
+ fn associate_existing_class() {
+ let service = Binder::new(BnTest(Box::new(TestService {
+ s: "testing_service".to_string(),
+ })));
+
+ // This should succeed although we will have to treat the service as
+ // remote.
+ let _interface: Box<dyn ITestSameDescriptor> = FromIBinder::try_from(service.as_binder())
+ .expect("Could not re-interpret service as the ITestSameDescriptor interface");
+ }
+
+ /// Test that we can round-trip a rust service through a generic IBinder
+ #[test]
+ fn reassociate_rust_binder() {
+ let service_name = "testing_service";
+ let service_ibinder = BnTest::new_binder(TestService { s: service_name.to_string() })
+ .as_binder();
+
+ let service: Box<dyn ITest> = service_ibinder.into_interface()
+ .expect("Could not reassociate the generic ibinder");
+
+ assert_eq!(service.test().unwrap(), service_name);
+ }
}
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
new file mode 100644
index 0000000..70a6dc0
--- /dev/null
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Rust Binder NDK interop tests
+
+use std::ffi::CStr;
+use std::os::raw::{c_char, c_int};
+use ::IBinderRustNdkInteropTest::binder::{self, Interface, StatusCode};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTest::{
+ BnBinderRustNdkInteropTest, IBinderRustNdkInteropTest,
+};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTestOther::{
+ IBinderRustNdkInteropTestOther,
+};
+
+/// Look up the provided AIDL service and call its echo method.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_call_ndk(service_name: *const c_char) -> c_int {
+ let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+
+ // The Rust class descriptor pointer will not match the NDK one, but the
+ // descriptor strings match so this needs to still associate.
+ let service: Box<dyn IBinderRustNdkInteropTest> = match binder::get_interface(service_name) {
+ Err(e) => {
+ eprintln!("Could not find Ndk service {}: {:?}", service_name, e);
+ return StatusCode::NAME_NOT_FOUND as c_int;
+ }
+ Ok(service) => service,
+ };
+
+ match service.echo("testing") {
+ Ok(s) => if s != "testing" {
+ return StatusCode::BAD_VALUE as c_int;
+ },
+ Err(e) => return e.into(),
+ }
+
+ // Try using the binder service through the wrong interface type
+ let wrong_service: Result<Box<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
+ binder::get_interface(service_name);
+ match wrong_service {
+ Err(e) if e == StatusCode::BAD_TYPE => {}
+ Err(e) => {
+ eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
+ return e as c_int;
+ }
+ Ok(_) => {
+ eprintln!("We should not be allowed to use a service via the wrong interface");
+ return StatusCode::BAD_TYPE as c_int;
+ }
+ }
+
+ StatusCode::OK as c_int
+}
+
+struct Service;
+
+impl Interface for Service {}
+
+impl IBinderRustNdkInteropTest for Service {
+ fn echo(&self, s: &str) -> binder::Result<String> {
+ Ok(s.to_string())
+ }
+}
+
+/// Start the interop Echo test service with the given service name.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_start_service(service_name: *const c_char) -> c_int {
+ let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+ let service = BnBinderRustNdkInteropTest::new_binder(Service);
+ match binder::add_service(&service_name, service.as_binder()) {
+ Ok(_) => StatusCode::OK as c_int,
+ Err(e) => e as c_int,
+ }
+}
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index ad4729d..0f7d159 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -30,6 +30,7 @@
#include <binder/IServiceManager.h>
#include <private/binder/binder_module.h>
+#include <linux/sched.h>
#include <sys/epoll.h>
#include <sys/prctl.h>
@@ -53,6 +54,7 @@
static constexpr int kSchedPolicy = SCHED_RR;
static constexpr int kSchedPriority = 7;
+static constexpr int kSchedPriorityMore = 8;
static String16 binderLibTestServiceName = String16("test.binderLib");
@@ -1088,6 +1090,25 @@
EXPECT_EQ(kSchedPriority, priority);
}
+TEST_F(BinderLibTest, InheritRt) {
+ sp<IBinder> server = addServer();
+ ASSERT_TRUE(server != nullptr);
+
+ const struct sched_param param {
+ .sched_priority = kSchedPriorityMore,
+ };
+ EXPECT_EQ(0, sched_setscheduler(getpid(), SCHED_RR, ¶m));
+
+ Parcel data, reply;
+ status_t ret = server->transact(BINDER_LIB_TEST_GET_SCHEDULING_POLICY, data, &reply);
+ EXPECT_EQ(NO_ERROR, ret);
+
+ int policy = reply.readInt32();
+ int priority = reply.readInt32();
+
+ EXPECT_EQ(kSchedPolicy, policy & (~SCHED_RESET_ON_FORK));
+ EXPECT_EQ(kSchedPriorityMore, priority);
+}
TEST_F(BinderLibTest, VectorSent) {
Parcel data, reply;
@@ -1460,6 +1481,8 @@
testService->setMinSchedulerPolicy(kSchedPolicy, kSchedPriority);
+ testService->setInheritRt(true);
+
/*
* Normally would also contain functionality as well, but we are only
* testing the extension mechanism.
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 2f9d85e..ffb3ef2 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -36,12 +36,15 @@
#include <optional>
#include <sys/eventfd.h>
+#include <sys/prctl.h>
using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
namespace android {
namespace tests {
+static const String16 kServiceName("SafeInterfaceTest");
+
enum class TestEnum : uint32_t {
INVALID = 0,
INITIAL = 1,
@@ -601,40 +604,13 @@
static constexpr const char* getLogTag() { return "SafeInterfaceTest"; }
sp<ISafeInterfaceTest> getRemoteService() {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wexit-time-destructors"
- static std::mutex sMutex;
- static sp<ISafeInterfaceTest> sService;
- static sp<IBinder> sDeathToken = new BBinder;
-#pragma clang diagnostic pop
+ sp<IBinder> binder = defaultServiceManager()->getService(kServiceName);
+ sp<ISafeInterfaceTest> iface = interface_cast<ISafeInterfaceTest>(binder);
+ EXPECT_TRUE(iface != nullptr);
- std::unique_lock<decltype(sMutex)> lock;
- if (sService == nullptr) {
- ALOG(LOG_INFO, getLogTag(), "Forking remote process");
- pid_t forkPid = fork();
- EXPECT_NE(forkPid, -1);
+ iface->setDeathToken(new BBinder);
- const String16 serviceName("SafeInterfaceTest");
-
- if (forkPid == 0) {
- ALOG(LOG_INFO, getLogTag(), "Remote process checking in");
- sp<ISafeInterfaceTest> nativeService = new BnSafeInterfaceTest;
- defaultServiceManager()->addService(serviceName,
- IInterface::asBinder(nativeService));
- ProcessState::self()->startThreadPool();
- IPCThreadState::self()->joinThreadPool();
- // We shouldn't get to this point
- [&]() { FAIL(); }();
- }
-
- sp<IBinder> binder = defaultServiceManager()->getService(serviceName);
- sService = interface_cast<ISafeInterfaceTest>(binder);
- EXPECT_TRUE(sService != nullptr);
-
- sService->setDeathToken(sDeathToken);
- }
-
- return sService;
+ return iface;
}
};
@@ -840,5 +816,23 @@
ASSERT_EQ(b + 1, bPlusOne);
}
+extern "C" int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ sp<BnSafeInterfaceTest> nativeService = new BnSafeInterfaceTest;
+ status_t status = defaultServiceManager()->addService(kServiceName, nativeService);
+ if (status != OK) {
+ ALOG(LOG_INFO, "SafeInterfaceServer", "could not register");
+ return EXIT_FAILURE;
+ }
+ IPCThreadState::self()->joinThreadPool();
+ return EXIT_FAILURE;
+ }
+
+ return RUN_ALL_TESTS();
+}
+
} // namespace tests
} // namespace android
diff --git a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
index 9ac65bb..69f1b9d 100644
--- a/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/fuzzers/BinderFuzzFunctions.h
@@ -37,8 +37,8 @@
bbinder->isRequestingSid();
},
[](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
- bool request_sid = fdp->ConsumeBool();
- bbinder->setRequestingSid(request_sid);
+ bool requestSid = fdp->ConsumeBool();
+ bbinder->setRequestingSid(requestSid);
},
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getExtension();
@@ -63,6 +63,13 @@
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getMinSchedulerPriority();
},
+ [](FuzzedDataProvider* fdp, const sp<BBinder>& bbinder) -> void {
+ bool inheritRt = fdp->ConsumeBool();
+ bbinder->setInheritRt(inheritRt);
+ },
+ [](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
+ bbinder->isInheritRt();
+ },
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
bbinder->getDebugPid();
}};
diff --git a/libs/cputimeinstate/Android.bp b/libs/cputimeinstate/Android.bp
index b1943a4..e3cd085 100644
--- a/libs/cputimeinstate/Android.bp
+++ b/libs/cputimeinstate/Android.bp
@@ -33,5 +33,6 @@
"-Wall",
"-Wextra",
],
+ require_root: true,
}
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 6058430..4209dc5 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -59,6 +59,7 @@
static unique_fd gTisMapFd;
static unique_fd gConcurrentMapFd;
static unique_fd gUidLastUpdateMapFd;
+static unique_fd gPidTisMapFd;
static std::optional<std::vector<uint32_t>> readNumbersFromFile(const std::string &path) {
std::string data;
@@ -139,6 +140,12 @@
unique_fd{bpf_obj_get(BPF_FS_PATH "map_time_in_state_uid_last_update_map")};
if (gUidLastUpdateMapFd < 0) return false;
+ gPidTisMapFd = unique_fd{mapRetrieveRO(BPF_FS_PATH "map_time_in_state_pid_time_in_state_map")};
+ if (gPidTisMapFd < 0) return false;
+
+ unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+ if (trackedPidMapFd < 0) return false;
+
gInitialized = true;
return true;
}
@@ -222,7 +229,8 @@
}
gTracking = attachTracepointProgram("sched", "sched_switch") &&
- attachTracepointProgram("power", "cpu_frequency");
+ attachTracepointProgram("power", "cpu_frequency") &&
+ attachTracepointProgram("sched", "sched_process_free");
return gTracking;
}
@@ -502,5 +510,106 @@
return true;
}
+bool startTrackingProcessCpuTimes(pid_t pid) {
+ if (!gInitialized && !initGlobals()) return false;
+
+ unique_fd trackedPidHashMapFd(
+ mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_hash_map"));
+ if (trackedPidHashMapFd < 0) return false;
+
+ unique_fd trackedPidMapFd(mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_tracked_map"));
+ if (trackedPidMapFd < 0) return false;
+
+ for (uint32_t index = 0; index < MAX_TRACKED_PIDS; index++) {
+ // Find first available [index, pid] entry in the pid_tracked_hash_map map
+ if (writeToMapEntry(trackedPidHashMapFd, &index, &pid, BPF_NOEXIST) != 0) {
+ if (errno != EEXIST) {
+ return false;
+ }
+ continue; // This index is already taken
+ }
+
+ tracked_pid_t tracked_pid = {.pid = pid, .state = TRACKED_PID_STATE_ACTIVE};
+ if (writeToMapEntry(trackedPidMapFd, &index, &tracked_pid, BPF_ANY) != 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+// Marks the specified task identified by its PID (aka TID) for CPU time-in-state tracking
+// aggregated with other tasks sharing the same TGID and aggregation key.
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+ if (!gInitialized && !initGlobals()) return false;
+
+ unique_fd taskAggregationMapFd(
+ mapRetrieveWO(BPF_FS_PATH "map_time_in_state_pid_task_aggregation_map"));
+ if (taskAggregationMapFd < 0) return false;
+
+ return writeToMapEntry(taskAggregationMapFd, &pid, &aggregationKey, BPF_ANY) == 0;
+}
+
+// Retrieves the times in ns that each thread spent running at each CPU freq, aggregated by
+// aggregation key.
+// Return contains no value on error, otherwise it contains a map from aggregation keys
+// to vectors of vectors using the format:
+// { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
+// aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
+// where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest freq.
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys) {
+ if (!gInitialized && !initGlobals()) return {};
+
+ uint32_t maxFreqCount = 0;
+ std::vector<std::vector<uint64_t>> mapFormat;
+ for (const auto &freqList : gPolicyFreqs) {
+ if (freqList.size() > maxFreqCount) maxFreqCount = freqList.size();
+ mapFormat.emplace_back(freqList.size(), 0);
+ }
+
+ bool dataCollected = false;
+ std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
+ std::vector<tis_val_t> vals(gNCpus);
+ for (uint16_t aggregationKey : aggregationKeys) {
+ map.emplace(aggregationKey, mapFormat);
+
+ aggregated_task_tis_key_t key{.tgid = tgid, .aggregation_key = aggregationKey};
+ for (key.bucket = 0; key.bucket <= (maxFreqCount - 1) / FREQS_PER_ENTRY; ++key.bucket) {
+ if (findMapEntry(gPidTisMapFd, &key, vals.data()) != 0) {
+ if (errno != ENOENT) {
+ return {};
+ }
+ continue;
+ } else {
+ dataCollected = true;
+ }
+
+ // Combine data by aggregating time-in-state data grouped by CPU cluster aka policy.
+ uint32_t offset = key.bucket * FREQS_PER_ENTRY;
+ uint32_t nextOffset = offset + FREQS_PER_ENTRY;
+ for (uint32_t j = 0; j < gNPolicies; ++j) {
+ if (offset >= gPolicyFreqs[j].size()) continue;
+ auto begin = map[key.aggregation_key][j].begin() + offset;
+ auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY
+ : map[key.aggregation_key][j].end();
+ for (const auto &cpu : gPolicyCpus[j]) {
+ std::transform(begin, end, std::begin(vals[cpu].ar), begin,
+ std::plus<uint64_t>());
+ }
+ }
+ }
+ }
+
+ if (!dataCollected) {
+ // Check if eBPF is supported on this device. If it is, gTisMap should not be empty.
+ time_key_t key;
+ if (getFirstMapKey(gTisMapFd, &key) != 0) {
+ return {};
+ }
+ }
+ return map;
+}
+
} // namespace bpf
} // namespace android
diff --git a/libs/cputimeinstate/cputimeinstate.h b/libs/cputimeinstate/cputimeinstate.h
index b7600f5..87a328a 100644
--- a/libs/cputimeinstate/cputimeinstate.h
+++ b/libs/cputimeinstate/cputimeinstate.h
@@ -41,5 +41,10 @@
getUidsUpdatedConcurrentTimes(uint64_t *lastUpdate);
bool clearUidTimes(unsigned int uid);
+bool startTrackingProcessCpuTimes(pid_t pid);
+bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys);
+
} // namespace bpf
} // namespace android
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 0d5f412..519689b 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -19,6 +19,8 @@
#include <sys/sysinfo.h>
+#include <pthread.h>
+#include <semaphore.h>
#include <numeric>
#include <unordered_map>
#include <vector>
@@ -504,5 +506,85 @@
for (size_t i = 0; i < freqs->size(); ++i) EXPECT_EQ((*freqs)[i].size(), (*times)[i].size());
}
+uint64_t timeNanos() {
+ struct timespec spec;
+ clock_gettime(CLOCK_MONOTONIC, &spec);
+ return spec.tv_sec * 1000000000 + spec.tv_nsec;
+}
+
+// Keeps CPU busy with some number crunching
+void useCpu() {
+ long sum = 0;
+ for (int i = 0; i < 100000; i++) {
+ sum *= i;
+ }
+}
+
+sem_t pingsem, pongsem;
+
+void *testThread(void *) {
+ for (int i = 0; i < 10; i++) {
+ sem_wait(&pingsem);
+ useCpu();
+ sem_post(&pongsem);
+ }
+ return nullptr;
+}
+
+TEST(TimeInStateTest, GetAggregatedTaskCpuFreqTimes) {
+ uint64_t startTimeNs = timeNanos();
+
+ sem_init(&pingsem, 0, 1);
+ sem_init(&pongsem, 0, 0);
+
+ pthread_t thread;
+ ASSERT_EQ(pthread_create(&thread, NULL, &testThread, NULL), 0);
+
+ // This process may have been running for some time, so when we start tracking
+ // CPU time, the very first switch may include the accumulated time.
+ // Yield the remainder of this timeslice to the newly created thread.
+ sem_wait(&pongsem);
+ sem_post(&pingsem);
+
+ pid_t tgid = getpid();
+ startTrackingProcessCpuTimes(tgid);
+
+ pid_t tid = pthread_gettid_np(thread);
+ startAggregatingTaskCpuTimes(tid, 42);
+
+ // Play ping-pong with the other thread to ensure that both threads get
+ // some CPU time.
+ for (int i = 0; i < 9; i++) {
+ sem_wait(&pongsem);
+ useCpu();
+ sem_post(&pingsem);
+ }
+
+ pthread_join(thread, NULL);
+
+ std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> optionalMap =
+ getAggregatedTaskCpuFreqTimes(tgid, {0, 42});
+ ASSERT_TRUE(optionalMap);
+
+ std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map = *optionalMap;
+ ASSERT_EQ(map.size(), 2u);
+
+ uint64_t testDurationNs = timeNanos() - startTimeNs;
+ for (auto pair : map) {
+ uint16_t aggregationKey = pair.first;
+ ASSERT_TRUE(aggregationKey == 0 || aggregationKey == 42);
+
+ std::vector<std::vector<uint64_t>> timesInState = pair.second;
+ uint64_t totalCpuTime = 0;
+ for (size_t i = 0; i < timesInState.size(); i++) {
+ for (size_t j = 0; j < timesInState[i].size(); j++) {
+ totalCpuTime += timesInState[i][j];
+ }
+ }
+ ASSERT_GT(totalCpuTime, 0ul);
+ ASSERT_LE(totalCpuTime, testDurationNs);
+ }
+}
+
} // namespace bpf
} // namespace android
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index e6aa02a..b9ab561 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -150,6 +150,16 @@
if (mRequestedSize != newSize) {
mRequestedSize.set(newSize);
mBufferItemConsumer->setDefaultBufferSize(mRequestedSize.width, mRequestedSize.height);
+ if (mLastBufferScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
+ // If the buffer supports scaling, update the frame immediately since the client may
+ // want to scale the existing buffer to the new size.
+ mSize = mRequestedSize;
+ SurfaceComposerClient::Transaction t;
+ t.setFrame(mSurfaceControl,
+ {0, 0, static_cast<int32_t>(mSize.width),
+ static_cast<int32_t>(mSize.height)});
+ t.apply();
+ }
}
}
@@ -276,6 +286,8 @@
// Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
incStrong((void*)transactionCallbackThunk);
+ mLastBufferScalingMode = bufferItem.mScalingMode;
+
t->setBuffer(mSurfaceControl, buffer);
t->setAcquireFence(mSurfaceControl,
bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
@@ -289,6 +301,11 @@
t->setDesiredPresentTime(bufferItem.mTimestamp);
t->setFrameNumber(mSurfaceControl, bufferItem.mFrameNumber);
+ if (!mNextFrameTimelineVsyncIdQueue.empty()) {
+ t->setFrameTimelineVsync(mSurfaceControl, mNextFrameTimelineVsyncIdQueue.front());
+ mNextFrameTimelineVsyncIdQueue.pop();
+ }
+
if (mAutoRefresh != bufferItem.mAutoRefresh) {
t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh);
mAutoRefresh = bufferItem.mAutoRefresh;
@@ -343,7 +360,6 @@
bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
if (item.mScalingMode != NATIVE_WINDOW_SCALING_MODE_FREEZE) {
- mSize = mRequestedSize;
// Only reject buffers if scaling mode is freeze.
return false;
}
@@ -417,10 +433,8 @@
status_t BLASTBufferQueue::setFrameTimelineVsync(int64_t frameTimelineVsyncId) {
std::unique_lock _lock{mMutex};
- SurfaceComposerClient::Transaction t;
-
- return t.setFrameTimelineVsync(mSurfaceControl, frameTimelineVsyncId)
- .apply();
+ mNextFrameTimelineVsyncIdQueue.push(frameTimelineVsyncId);
+ return OK;
}
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 9fb7d6f..1139390 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -28,6 +28,7 @@
#include <system/window.h>
#include <thread>
+#include <queue>
namespace android {
@@ -143,6 +144,13 @@
// should acquire the next frame as soon as it can and not wait for a frame to become available.
// This is only relevant for shared buffer mode.
bool mAutoRefresh GUARDED_BY(mMutex) = false;
+
+ std::queue<int64_t> mNextFrameTimelineVsyncIdQueue GUARDED_BY(mMutex);
+
+ // Last acquired buffer's scaling mode. This is used to check if we should update the blast
+ // layer size immediately or wait until we get the next buffer. This will support scenarios
+ // where the layer can change sizes and the buffer will scale to fit the new size.
+ uint32_t mLastBufferScalingMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
};
} // namespace android
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
new file mode 100644
index 0000000..47daf95
--- /dev/null
+++ b/libs/gui/include/gui/JankInfo.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+
+// Jank information tracked by SurfaceFlinger for the purpose of funneling to telemetry.
+enum JankType {
+ // No Jank
+ None = 0x0,
+ // Jank not related to SurfaceFlinger or the App
+ Display = 0x1,
+ // SF took too long on the CPU
+ SurfaceFlingerDeadlineMissed = 0x2,
+ // SF took too long on the GPU
+ SurfaceFlingerGpuDeadlineMissed = 0x4,
+ // Either App or GPU took too long on the frame
+ AppDeadlineMissed = 0x8,
+ // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
+ // jank
+ // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
+ PredictionExpired = 0x10,
+ // Latching a buffer early might cause an early present of the frame
+ SurfaceFlingerEarlyLatch = 0x20,
+};
+
+} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index a0660fb..8dc0c68 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -57,8 +57,6 @@
#include "filters/BlurFilter.h"
#include "filters/LinearEffect.h"
-extern "C" EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
-
bool checkGlError(const char* op, int lineNumber);
namespace android {
@@ -155,16 +153,16 @@
LOG_ALWAYS_FATAL("failed to initialize EGL");
}
- const auto eglVersion = eglQueryStringImplementationANDROID(display, EGL_VERSION);
+ const auto eglVersion = eglQueryString(display, EGL_VERSION);
if (!eglVersion) {
checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryStringImplementationANDROID(EGL_VERSION) failed");
+ LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
}
- const auto eglExtensions = eglQueryStringImplementationANDROID(display, EGL_EXTENSIONS);
+ const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
if (!eglExtensions) {
checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryStringImplementationANDROID(EGL_EXTENSIONS) failed");
+ LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
}
auto& extensions = gl::GLExtensions::getInstance();
@@ -494,8 +492,7 @@
// displays might have different scaling when compared to the physical screen.
canvas->clipRect(getSkRect(display.physicalDisplay));
- SkMatrix screenTransform;
- screenTransform.setTranslate(display.physicalDisplay.left, display.physicalDisplay.top);
+ canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);
const auto clipWidth = display.clip.width();
const auto clipHeight = display.clip.height();
@@ -509,31 +506,33 @@
static_cast<SkScalar>(rotatedClipWidth);
const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
static_cast<SkScalar>(rotatedClipHeight);
- screenTransform.preScale(scaleX, scaleY);
+ canvas->scale(scaleX, scaleY);
// Canvas rotation is done by centering the clip window at the origin, rotating, translating
// back so that the top left corner of the clip is at (0, 0).
- screenTransform.preTranslate(rotatedClipWidth / 2, rotatedClipHeight / 2);
- screenTransform.preRotate(toDegrees(display.orientation));
- screenTransform.preTranslate(-clipWidth / 2, -clipHeight / 2);
- screenTransform.preTranslate(-display.clip.left, -display.clip.top);
+ canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
+ canvas->rotate(toDegrees(display.orientation));
+ canvas->translate(-clipWidth / 2, -clipHeight / 2);
+ canvas->translate(-display.clip.left, -display.clip.top);
for (const auto& layer : layers) {
- const SkMatrix drawTransform = getDrawTransform(layer, screenTransform);
+ canvas->save();
+
+ // Layers have a local transform that should be applied to them
+ canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
SkPaint paint;
const auto& bounds = layer->geometry.boundaries;
const auto dest = getSkRect(bounds);
+ const auto layerRect = canvas->getTotalMatrix().mapRect(dest);
std::unordered_map<uint32_t, sk_sp<SkSurface>> cachedBlurs;
-
if (mBlurFilter) {
- const auto layerRect = drawTransform.mapRect(dest);
if (layer->backgroundBlurRadius > 0) {
ATRACE_NAME("BackgroundBlur");
auto blurredSurface = mBlurFilter->generate(canvas, surface,
layer->backgroundBlurRadius, layerRect);
cachedBlurs[layer->backgroundBlurRadius] = blurredSurface;
- drawBlurRegion(canvas, getBlurRegion(layer), drawTransform, blurredSurface);
+ drawBlurRegion(canvas, getBlurRegion(layer), layerRect, blurredSurface);
}
if (layer->blurRegions.size() > 0) {
for (auto region : layer->blurRegions) {
@@ -679,12 +678,9 @@
paint.setColorFilter(SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform)));
for (const auto effectRegion : layer->blurRegions) {
- drawBlurRegion(canvas, effectRegion, drawTransform,
- cachedBlurs[effectRegion.blurRadius]);
+ drawBlurRegion(canvas, effectRegion, layerRect, cachedBlurs[effectRegion.blurRadius]);
}
- canvas->save();
- canvas->concat(drawTransform);
if (layer->shadow.length > 0) {
const auto rect = layer->geometry.roundedCornersRadius > 0
? getSkRect(layer->geometry.roundedCornersCrop)
@@ -740,7 +736,7 @@
}
inline SkRRect SkiaGLRenderEngine::getRoundedRect(const LayerSettings* layer) {
- const auto rect = getSkRect(layer->geometry.roundedCornersCrop);
+ const auto rect = getSkRect(layer->geometry.boundaries);
const auto cornerRadius = layer->geometry.roundedCornersRadius;
return SkRRect::MakeRectXY(rect, cornerRadius, cornerRadius);
}
@@ -771,13 +767,6 @@
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);
}
-inline SkMatrix SkiaGLRenderEngine::getDrawTransform(const LayerSettings* layer,
- const SkMatrix& screenTransform) {
- // Layers have a local transform matrix that should be applied to them.
- const auto layerTransform = getSkM44(layer->geometry.positionTransform).asM33();
- return SkMatrix::Concat(screenTransform, layerTransform);
-}
-
inline SkPoint3 SkiaGLRenderEngine::getSkPoint3(const vec3& vector) {
return SkPoint3::Make(vector.x, vector.y, vector.z);
}
@@ -807,18 +796,20 @@
}
void SkiaGLRenderEngine::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
- const SkMatrix& drawTransform,
- sk_sp<SkSurface> blurredSurface) {
+ const SkRect& layerRect, sk_sp<SkSurface> blurredSurface) {
ATRACE_CALL();
SkPaint paint;
paint.setAlpha(static_cast<int>(effectRegion.alpha * 255));
- const auto matrix = mBlurFilter->getShaderMatrix();
- paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(matrix));
+ const auto matrix = getBlurShaderTransform(canvas, layerRect);
+ paint.setShader(blurredSurface->makeImageSnapshot()->makeShader(
+ SkTileMode::kClamp,
+ SkTileMode::kClamp,
+ SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+ &matrix));
auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
effectRegion.bottom);
- drawTransform.mapRect(&rect);
if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
@@ -835,6 +826,24 @@
}
}
+SkMatrix SkiaGLRenderEngine::getBlurShaderTransform(const SkCanvas* canvas,
+ const SkRect& layerRect) {
+ // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
+ auto matrix = mBlurFilter->getShaderMatrix();
+ // 2. Since the blurred surface has the size of the layer, we align it with the
+ // top left corner of the layer position.
+ matrix.postConcat(SkMatrix::Translate(layerRect.fLeft, layerRect.fTop));
+ // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
+ // original surface orientation. The inverse matrix has to be applied to align the blur
+ // surface with the current orientation/position of the canvas.
+ SkMatrix drawInverse;
+ if (canvas->getTotalMatrix().invert(&drawInverse)) {
+ matrix.postConcat(drawInverse);
+ }
+
+ return matrix;
+}
+
EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
EGLContext shareContext, bool useContextPriority,
Protection protection) {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index f5eed1e..ef06bfa 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -77,15 +77,15 @@
inline BlurRegion getBlurRegion(const LayerSettings* layer);
inline SkColor getSkColor(const vec4& color);
inline SkM44 getSkM44(const mat4& matrix);
- inline SkMatrix getDrawTransform(const LayerSettings* layer, const SkMatrix& screenTransform);
inline SkPoint3 getSkPoint3(const vec3& vector);
base::unique_fd flush();
bool waitFence(base::unique_fd fenceFd);
void drawShadow(SkCanvas* canvas, const SkRect& casterRect, float casterCornerRadius,
const ShadowSettings& shadowSettings);
- void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion,
- const SkMatrix& drawTransform, sk_sp<SkSurface> blurrendSurface);
+ void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& layerRect,
+ sk_sp<SkSurface> blurredSurface);
+ SkMatrix getBlurShaderTransform(const SkCanvas* canvas, const SkRect& layerRect);
EGLDisplay mEGLDisplay;
EGLContext mEGLContext;
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index f19b64a..8927be8 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -66,11 +66,10 @@
float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
float radiusByPasses = tmpRadius / (float)numberOfPasses;
- SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale,
- (float)input->height() * kInputScale);
+ SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)rect.width() * kInputScale,
+ (float)rect.height() * kInputScale);
+ SkRect scaledRect = SkRect::MakeWH(scaledInfo.width(), scaledInfo.height());
- SkRect scaledRect = {rect.fLeft * kInputScale, rect.fTop * kInputScale,
- rect.fRight * kInputScale, rect.fBottom * kInputScale};
auto drawSurface = canvas->makeSurface(scaledInfo);
const float stepX = radiusByPasses;
@@ -80,7 +79,8 @@
SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
blurBuilder.child("input") =
- input->makeImageSnapshot()->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+ input->makeImageSnapshot(rect.round())
+ ->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
blurBuilder.uniform("in_inverseScale") = kInverseInputScale;
blurBuilder.uniform("in_blurOffset") =
SkV2{stepX * kInverseInputScale, stepY * kInverseInputScale};
@@ -127,10 +127,6 @@
lastDrawTarget = readSurface;
}
- {
- ATRACE_NAME("Flush Offscreen Surfaces");
- lastDrawTarget->flushAndSubmit();
- }
return lastDrawTarget;
}
diff --git a/libs/ui/include/ui/FatVector.h b/libs/ui/include/ui/FatVector.h
index 25fe3a0..cb61e6a 100644
--- a/libs/ui/include/ui/FatVector.h
+++ b/libs/ui/include/ui/FatVector.h
@@ -82,6 +82,12 @@
this->reserve(SIZE);
}
+ FatVector(std::initializer_list<T> init)
+ : std::vector<T, InlineStdAllocator<T, SIZE>>(init,
+ InlineStdAllocator<T, SIZE>(mAllocation)) {
+ this->reserve(SIZE);
+ }
+
explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); }
private:
diff --git a/libs/vibrator/OWNERS b/libs/vibrator/OWNERS
new file mode 100644
index 0000000..0997e9f
--- /dev/null
+++ b/libs/vibrator/OWNERS
@@ -0,0 +1,2 @@
+lsandrade@google.com
+michaelwr@google.com
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index c5210b5..9671dec 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -475,25 +475,25 @@
}
InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->identifier : InputDeviceIdentifier();
}
Flags<InputDeviceClass> EventHub::getDeviceClasses(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->classes : Flags<InputDeviceClass>(0);
}
int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->controllerNumber : 0;
}
void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->configuration) {
*outConfiguration = *device->configuration;
@@ -507,7 +507,7 @@
outAxisInfo->clear();
if (axis >= 0 && axis <= ABS_MAX) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -534,7 +534,7 @@
bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
if (axis >= 0 && axis <= REL_MAX) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
return device != nullptr ? device->relBitmask.test(axis) : false;
}
@@ -542,7 +542,7 @@
}
bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
return property >= 0 && property <= INPUT_PROP_MAX && device != nullptr
@@ -552,7 +552,7 @@
int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
if (scanCode >= 0 && scanCode <= KEY_MAX) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->keyBitmask.test(scanCode)) {
@@ -565,7 +565,7 @@
}
int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
@@ -588,7 +588,7 @@
int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
if (sw >= 0 && sw <= SW_MAX) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->swBitmask.test(sw)) {
@@ -604,7 +604,7 @@
*outValue = 0;
if (axis >= 0 && axis <= ABS_MAX) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
@@ -624,7 +624,7 @@
bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -652,7 +652,7 @@
status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
status_t status = NAME_NOT_FOUND;
@@ -692,7 +692,7 @@
}
status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->keyMap.haveKeyLayout()) {
@@ -706,13 +706,13 @@
}
void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
mExcludedDevices = devices;
}
bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && scanCode >= 0 && scanCode <= KEY_MAX) {
return device->keyBitmask.test(scanCode);
@@ -721,7 +721,7 @@
}
bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
int32_t sc;
if (device != nullptr && device->mapLed(led, &sc) == NO_ERROR) {
@@ -731,7 +731,7 @@
}
void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
device->setLedStateLocked(led, on);
@@ -742,7 +742,7 @@
std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
outVirtualKeys.clear();
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->virtualKeyMap) {
const std::vector<VirtualKeyDefinition> virtualKeys =
@@ -752,7 +752,7 @@
}
const std::shared_ptr<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr) {
return device->getKeyCharacterMap();
@@ -761,7 +761,7 @@
}
bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && map != nullptr && device->keyMap.keyCharacterMap != nullptr) {
device->keyMap.keyCharacterMap->combine(*map);
@@ -822,7 +822,7 @@
}
void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
ff_effect effect;
@@ -857,7 +857,7 @@
}
void EventHub::cancelVibrate(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device != nullptr && device->hasValidFd()) {
if (device->ffEffectPlaying) {
@@ -930,7 +930,7 @@
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
ALOG_ASSERT(bufferSize >= 1);
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
struct input_event readBuffer[bufferSize];
@@ -1184,7 +1184,7 @@
}
std::vector<TouchVideoFrame> EventHub::getVideoFrames(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr || !device->videoDevice) {
@@ -1593,7 +1593,7 @@
}
bool EventHub::isDeviceEnabled(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1603,7 +1603,7 @@
}
status_t EventHub::enableDevice(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1625,7 +1625,7 @@
}
status_t EventHub::disableDevice(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
Device* device = getDeviceLocked(deviceId);
if (device == nullptr) {
ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__);
@@ -1809,7 +1809,7 @@
void EventHub::requestReopenDevices() {
ALOGV("requestReopenDevices() called");
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
mNeedToReopenDevices = true;
}
@@ -1817,7 +1817,7 @@
dump += "Event Hub State:\n";
{ // acquire lock
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
dump += StringPrintf(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
@@ -1868,8 +1868,7 @@
void EventHub::monitor() {
// Acquire and release the lock to ensure that the event hub has not deadlocked.
- mLock.lock();
- mLock.unlock();
+ std::unique_lock<std::mutex> lock(mLock);
}
}; // namespace android
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index e263f01..6216d78 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -56,7 +56,7 @@
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
refreshConfigurationLocked(0);
updateGlobalMetaStateLocked();
@@ -87,8 +87,9 @@
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
+ std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
@@ -107,8 +108,8 @@
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
- AutoMutex _l(mLock);
- mReaderIsAliveCondition.broadcast();
+ std::lock_guard<std::mutex> lock(mLock);
+ mReaderIsAliveCondition.notify_all();
if (count) {
processEventsLocked(mEventBuffer, count);
@@ -127,12 +128,13 @@
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
+ inputDevices = getInputDevicesLocked();
}
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
- mPolicy->notifyInputDevicesChanged(getInputDevicesLocked());
+ mPolicy->notifyInputDevicesChanged(inputDevices);
}
// Flush queued events out to the listener.
@@ -216,7 +218,7 @@
bumpGenerationLocked();
if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
- notifyExternalStylusPresenceChanged();
+ notifyExternalStylusPresenceChangedLocked();
}
}
@@ -256,7 +258,7 @@
device->removeEventHubDevice(eventHubId);
if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
- notifyExternalStylusPresenceChanged();
+ notifyExternalStylusPresenceChangedLocked();
}
if (device->hasEventHubDevices()) {
@@ -301,7 +303,7 @@
device->process(rawEvents, count);
}
-InputDevice* InputReader::findInputDevice(int32_t deviceId) {
+InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) {
auto deviceIt =
std::find_if(mDevices.begin(), mDevices.end(), [deviceId](const auto& devicePair) {
return devicePair.second->getId() == deviceId;
@@ -389,7 +391,7 @@
return mLedMetaState;
}
-void InputReader::notifyExternalStylusPresenceChanged() {
+void InputReader::notifyExternalStylusPresenceChangedLocked() {
refreshConfigurationLocked(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
}
@@ -405,6 +407,7 @@
}
void InputReader::dispatchExternalStylusState(const StylusState& state) {
+ std::lock_guard<std::mutex> lock(mLock);
for (auto& devicePair : mDevices) {
std::shared_ptr<InputDevice>& device = devicePair.second;
device->updateExternalStylusState(state);
@@ -479,7 +482,7 @@
}
std::vector<InputDeviceInfo> InputReader::getInputDevices() const {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
return getInputDevicesLocked();
}
@@ -498,19 +501,19 @@
}
int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
}
int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
}
int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
}
@@ -519,7 +522,7 @@
GetStateFunc getStateFunc) {
int32_t result = AKEY_STATE_UNKNOWN;
if (deviceId >= 0) {
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
result = (device->*getStateFunc)(sourceMask, code);
}
@@ -542,7 +545,8 @@
}
void InputReader::toggleCapsLockState(int32_t deviceId) {
- InputDevice* device = findInputDevice(deviceId);
+ std::lock_guard<std::mutex> lock(mLock);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (!device) {
ALOGW("Ignoring toggleCapsLock for unknown deviceId %" PRId32 ".", deviceId);
return;
@@ -557,7 +561,7 @@
bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
memset(outFlags, 0, numCodes);
return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
@@ -568,7 +572,7 @@
uint8_t* outFlags) {
bool result = false;
if (deviceId >= 0) {
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device && !device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
result = device->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
}
@@ -584,7 +588,7 @@
}
void InputReader::requestRefreshConfiguration(uint32_t changes) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
if (changes) {
bool needWake = !mConfigurationChangesToRefresh;
@@ -598,26 +602,26 @@
void InputReader::vibrate(int32_t deviceId, const std::vector<VibrationElement>& pattern,
ssize_t repeat, int32_t token) {
- AutoMutex _l(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ std::lock_guard<std::mutex> lock(mLock);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
device->vibrate(pattern, repeat, token);
}
}
void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
device->cancelVibrate(token);
}
}
bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
return device->isEnabled();
}
@@ -626,9 +630,9 @@
}
bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
- InputDevice* device = findInputDevice(deviceId);
+ InputDevice* device = findInputDeviceLocked(deviceId);
if (!device) {
ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
return false;
@@ -654,7 +658,7 @@
}
void InputReader::dump(std::string& dump) {
- AutoMutex _l(mLock);
+ std::lock_guard<std::mutex> lock(mLock);
mEventHub->dump(dump);
dump += "\n";
@@ -729,11 +733,9 @@
void InputReader::monitor() {
// Acquire and release the lock to ensure that the reader has not deadlocked.
- mLock.lock();
+ std::unique_lock<std::mutex> lock(mLock);
mEventHub->wake();
- mReaderIsAliveCondition.wait(mLock);
- mLock.unlock();
-
+ mReaderIsAliveCondition.wait(lock);
// Check the EventHub
mEventHub->monitor();
}
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index edb82d3..a3f881e 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -502,7 +502,7 @@
void releaseControllerNumberLocked(int32_t num);
// Protect all internal state.
- mutable Mutex mLock;
+ mutable std::mutex mLock;
// The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
// EventHub remaps the built-in keyboard to id 0 externally as required by the API.
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 563018a..9b07681d 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -18,6 +18,7 @@
#define _UI_INPUTREADER_INPUT_READER_H
#include <PointerControllerInterface.h>
+#include <android-base/thread_annotations.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
@@ -84,8 +85,9 @@
protected:
// These members are protected so they can be instrumented by test cases.
- virtual std::shared_ptr<InputDevice> createDeviceLocked(
- int32_t deviceId, const InputDeviceIdentifier& identifier);
+ virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier)
+ REQUIRES(mLock);
// With each iteration of the loop, InputReader reads and processes one incoming message from
// the EventHub.
@@ -97,33 +99,37 @@
public:
explicit ContextImpl(InputReader* reader);
-
- void updateGlobalMetaState() override;
- int32_t getGlobalMetaState() override;
- void disableVirtualKeysUntil(nsecs_t time) override;
- bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override;
- void fadePointer() override;
- std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override;
- void requestTimeoutAtTime(nsecs_t when) override;
- int32_t bumpGeneration() override;
- void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override;
- void dispatchExternalStylusState(const StylusState& outState) override;
- InputReaderPolicyInterface* getPolicy() override;
- InputListenerInterface* getListener() override;
- EventHubInterface* getEventHub() override;
- int32_t getNextId() override;
- void updateLedMetaState(int32_t metaState) override;
- int32_t getLedMetaState() override;
+ // lock is already held by the input loop
+ void updateGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t getGlobalMetaState() NO_THREAD_SAFETY_ANALYSIS override;
+ void disableVirtualKeysUntil(nsecs_t time) NO_THREAD_SAFETY_ANALYSIS override;
+ bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode,
+ int32_t scanCode) NO_THREAD_SAFETY_ANALYSIS override;
+ void fadePointer() NO_THREAD_SAFETY_ANALYSIS override;
+ std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ void requestTimeoutAtTime(nsecs_t when) NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
+ void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ void dispatchExternalStylusState(const StylusState& outState)
+ NO_THREAD_SAFETY_ANALYSIS override;
+ InputReaderPolicyInterface* getPolicy() NO_THREAD_SAFETY_ANALYSIS override;
+ InputListenerInterface* getListener() NO_THREAD_SAFETY_ANALYSIS override;
+ EventHubInterface* getEventHub() NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override;
+ void updateLedMetaState(int32_t metaState) NO_THREAD_SAFETY_ANALYSIS override;
+ int32_t getLedMetaState() NO_THREAD_SAFETY_ANALYSIS override;
} mContext;
friend class ContextImpl;
+ // Test cases need to override the locked functions
+ mutable std::mutex mLock;
private:
std::unique_ptr<InputThread> mThread;
- mutable Mutex mLock;
-
- Condition mReaderIsAliveCondition;
+ std::condition_variable mReaderIsAliveCondition;
// This could be unique_ptr, but due to the way InputReader tests are written,
// it is made shared_ptr here. In the tests, an EventHub reference is retained by the test
@@ -132,76 +138,79 @@
sp<InputReaderPolicyInterface> mPolicy;
sp<QueuedInputListener> mQueuedListener;
- InputReaderConfiguration mConfig;
+ InputReaderConfiguration mConfig GUARDED_BY(mLock);
// The event queue.
static const int EVENT_BUFFER_SIZE = 256;
- RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+ RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);
// An input device can represent a collection of EventHub devices. This map provides a way
// to lookup the input device instance from the EventHub device id.
- std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices;
+ std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices
+ GUARDED_BY(mLock);
// An input device contains one or more eventHubId, this map provides a way to lookup the
// EventHubIds contained in the input device from the input device instance.
std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
- mDeviceToEventHubIdsMap;
+ mDeviceToEventHubIdsMap GUARDED_BY(mLock);
// low-level input event decoding and device management
- void processEventsLocked(const RawEvent* rawEvents, size_t count);
+ void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock);
- void addDeviceLocked(nsecs_t when, int32_t eventHubId);
- void removeDeviceLocked(nsecs_t when, int32_t eventHubId);
- void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count);
- void timeoutExpiredLocked(nsecs_t when);
+ void addDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+ void removeDeviceLocked(nsecs_t when, int32_t eventHubId) REQUIRES(mLock);
+ void processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents, size_t count)
+ REQUIRES(mLock);
+ void timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock);
- void handleConfigurationChangedLocked(nsecs_t when);
+ void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock);
- int32_t mGlobalMetaState;
- void updateGlobalMetaStateLocked();
- int32_t getGlobalMetaStateLocked();
+ int32_t mGlobalMetaState GUARDED_BY(mLock);
+ void updateGlobalMetaStateLocked() REQUIRES(mLock);
+ int32_t getGlobalMetaStateLocked() REQUIRES(mLock);
- int32_t mLedMetaState;
- void updateLedMetaStateLocked(int32_t metaState);
- int32_t getLedMetaStateLocked();
+ int32_t mLedMetaState GUARDED_BY(mLock);
+ void updateLedMetaStateLocked(int32_t metaState) REQUIRES(mLock);
+ int32_t getLedMetaStateLocked() REQUIRES(mLock);
- void notifyExternalStylusPresenceChanged();
- void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices);
+ void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock);
+ void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) REQUIRES(mLock);
void dispatchExternalStylusState(const StylusState& state);
// The PointerController that is shared among all the input devices that need it.
std::weak_ptr<PointerControllerInterface> mPointerController;
- std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId);
- void updatePointerDisplayLocked();
- void fadePointerLocked();
+ std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
+ REQUIRES(mLock);
+ void updatePointerDisplayLocked() REQUIRES(mLock);
+ void fadePointerLocked() REQUIRES(mLock);
- int32_t mGeneration;
- int32_t bumpGenerationLocked();
+ int32_t mGeneration GUARDED_BY(mLock);
+ int32_t bumpGenerationLocked() REQUIRES(mLock);
- int32_t mNextInputDeviceId;
- int32_t nextInputDeviceIdLocked();
+ int32_t mNextInputDeviceId GUARDED_BY(mLock);
+ int32_t nextInputDeviceIdLocked() REQUIRES(mLock);
- std::vector<InputDeviceInfo> getInputDevicesLocked() const;
+ std::vector<InputDeviceInfo> getInputDevicesLocked() const REQUIRES(mLock);
- nsecs_t mDisableVirtualKeysTimeout;
- void disableVirtualKeysUntilLocked(nsecs_t time);
- bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode);
+ nsecs_t mDisableVirtualKeysTimeout GUARDED_BY(mLock);
+ void disableVirtualKeysUntilLocked(nsecs_t time) REQUIRES(mLock);
+ bool shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32_t scanCode) REQUIRES(mLock);
- nsecs_t mNextTimeout;
- void requestTimeoutAtTimeLocked(nsecs_t when);
+ nsecs_t mNextTimeout GUARDED_BY(mLock);
+ void requestTimeoutAtTimeLocked(nsecs_t when) REQUIRES(mLock);
- uint32_t mConfigurationChangesToRefresh;
- void refreshConfigurationLocked(uint32_t changes);
+ uint32_t mConfigurationChangesToRefresh GUARDED_BY(mLock);
+ void refreshConfigurationLocked(uint32_t changes) REQUIRES(mLock);
// state queries
typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
- GetStateFunc getStateFunc);
+ GetStateFunc getStateFunc) REQUIRES(mLock);
bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
- const int32_t* keyCodes, uint8_t* outFlags);
+ const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock);
// find an InputDevice from an InputDevice id
- InputDevice* findInputDevice(int32_t deviceId);
+ InputDevice* findInputDeviceLocked(int32_t deviceId) REQUIRES(mLock);
};
} // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 99eaac6..5a4c881 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1030,8 +1030,9 @@
using InputReader::loopOnce;
protected:
- virtual std::shared_ptr<InputDevice> createDeviceLocked(
- int32_t eventHubId, const InputDeviceIdentifier& identifier) {
+ virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
+ const InputDeviceIdentifier& identifier)
+ REQUIRES(mLock) {
if (!mNextDevices.empty()) {
std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
mNextDevices.pop();
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index ed0d75b..fdb8aaf 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -156,9 +156,7 @@
"Scheduler/EventThread.cpp",
"Scheduler/OneShotTimer.cpp",
"Scheduler/LayerHistory.cpp",
- "Scheduler/LayerHistoryV2.cpp",
"Scheduler/LayerInfo.cpp",
- "Scheduler/LayerInfoV2.cpp",
"Scheduler/MessageQueue.cpp",
"Scheduler/RefreshRateConfigs.cpp",
"Scheduler/Scheduler.cpp",
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index e075d3e..1e6d21e 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -14,5 +14,8 @@
"libui",
"libutils",
],
+ static_libs: [
+ "libperfetto_client_experimental",
+ ],
export_include_dirs: ["."],
}
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index bd87482..b45c213 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -29,6 +29,7 @@
namespace android::frametimeline::impl {
using base::StringAppendF;
+using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
@@ -93,19 +94,19 @@
}
}
-std::string toString(TimeStats::JankType jankType) {
+std::string toString(JankType jankType) {
switch (jankType) {
- case TimeStats::JankType::None:
+ case JankType::None:
return "None";
- case TimeStats::JankType::Display:
+ case JankType::Display:
return "Composer/Display - outside SF and App";
- case TimeStats::JankType::SurfaceFlingerDeadlineMissed:
+ case JankType::SurfaceFlingerDeadlineMissed:
return "SurfaceFlinger Deadline Missed";
- case TimeStats::JankType::AppDeadlineMissed:
+ case JankType::AppDeadlineMissed:
return "App Deadline Missed";
- case TimeStats::JankType::PredictionExpired:
+ case JankType::PredictionExpired:
return "Prediction Expired";
- case TimeStats::JankType::SurfaceFlingerEarlyLatch:
+ case JankType::SurfaceFlingerEarlyLatch:
return "SurfaceFlinger Early Latch";
default:
return "Unclassified";
@@ -143,6 +144,32 @@
});
}
+FrameTimelineEvent::PresentType presentTypeToProto(int32_t jankMetadata) {
+ if (jankMetadata & EarlyPresent) {
+ return FrameTimelineEvent::PRESENT_EARLY;
+ }
+ if (jankMetadata & LatePresent) {
+ return FrameTimelineEvent::PRESENT_LATE;
+ }
+ return FrameTimelineEvent::PRESENT_ON_TIME;
+}
+
+FrameTimelineEvent::JankType JankTypeToProto(JankType jankType) {
+ switch (jankType) {
+ case JankType::None:
+ return FrameTimelineEvent::JANK_NONE;
+ case JankType::Display:
+ return FrameTimelineEvent::JANK_DISPLAY_HAL;
+ case JankType::SurfaceFlingerDeadlineMissed:
+ return FrameTimelineEvent::JANK_SF_DEADLINE_MISSED;
+ case JankType::AppDeadlineMissed:
+ case JankType::PredictionExpired:
+ return FrameTimelineEvent::JANK_APP_DEADLINE_MISSED;
+ default:
+ return FrameTimelineEvent::JANK_UNKNOWN;
+ }
+}
+
int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
ATRACE_CALL();
std::lock_guard<std::mutex> lock(mMutex);
@@ -177,10 +204,11 @@
}
}
-SurfaceFrame::SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName,
+SurfaceFrame::SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
std::string debugName, PredictionState predictionState,
frametimeline::TimelineItem&& predictions)
- : mOwnerPid(ownerPid),
+ : mToken(token),
+ mOwnerPid(ownerPid),
mOwnerUid(ownerUid),
mLayerName(std::move(layerName)),
mDebugName(std::move(debugName)),
@@ -189,7 +217,7 @@
mPredictions(predictions),
mActuals({0, 0, 0}),
mActualQueueTime(0),
- mJankType(TimeStats::JankType::None),
+ mJankType(JankType::None),
mJankMetadata(0) {}
void SurfaceFrame::setPresentState(PresentState state) {
@@ -231,13 +259,13 @@
mActuals.presentTime = presentTime;
}
-void SurfaceFrame::setJankInfo(TimeStats::JankType jankType, int32_t jankMetadata) {
+void SurfaceFrame::setJankInfo(JankType jankType, int32_t jankMetadata) {
std::lock_guard<std::mutex> lock(mMutex);
mJankType = jankType;
mJankMetadata = jankMetadata;
}
-TimeStats::JankType SurfaceFrame::getJankType() const {
+JankType SurfaceFrame::getJankType() const {
std::lock_guard<std::mutex> lock(mMutex);
return mJankType;
}
@@ -272,7 +300,7 @@
std::lock_guard<std::mutex> lock(mMutex);
StringAppendF(&result, "%s", indent.c_str());
StringAppendF(&result, "Layer - %s", mDebugName.c_str());
- if (mJankType != TimeStats::JankType::None) {
+ if (mJankType != JankType::None) {
// Easily identify a janky Surface Frame in the dump
StringAppendF(&result, " [*] ");
}
@@ -291,17 +319,70 @@
dumpTable(result, mPredictions, mActuals, indent, mPredictionState, baseTime);
}
+void SurfaceFrame::traceSurfaceFrame(int64_t displayFrameToken) {
+ using FrameTimelineDataSource = FrameTimeline::FrameTimelineDataSource;
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace SurfaceFrame - %s with invalid token", mLayerName.c_str());
+ return;
+ } else if (displayFrameToken == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace SurfaceFrame - %s with invalid displayFrameToken",
+ mLayerName.c_str());
+ return;
+ }
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(systemTime()));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* surfaceFrameEvent = event->set_surface_frame();
+
+ surfaceFrameEvent->set_token(mToken);
+ surfaceFrameEvent->set_display_frame_token(displayFrameToken);
+
+ if (mPresentState == PresentState::Dropped) {
+ surfaceFrameEvent->set_present_type(FrameTimelineEvent::PRESENT_DROPPED);
+ } else if (mPresentState == PresentState::Unknown) {
+ surfaceFrameEvent->set_present_type(FrameTimelineEvent::PRESENT_UNSPECIFIED);
+ } else {
+ surfaceFrameEvent->set_present_type(presentTypeToProto(mJankMetadata));
+ }
+ surfaceFrameEvent->set_on_time_finish(!(mJankMetadata & LateFinish));
+ surfaceFrameEvent->set_gpu_composition(mJankMetadata & GpuComposition);
+ surfaceFrameEvent->set_jank_type(JankTypeToProto(mJankType));
+
+ surfaceFrameEvent->set_expected_start_ns(mPredictions.startTime);
+ surfaceFrameEvent->set_expected_end_ns(mPredictions.endTime);
+
+ surfaceFrameEvent->set_actual_start_ns(mActuals.startTime);
+ surfaceFrameEvent->set_actual_end_ns(mActuals.endTime);
+
+ surfaceFrameEvent->set_layer_name(mDebugName);
+ surfaceFrameEvent->set_pid(mOwnerPid);
+ });
+}
+
FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
: mCurrentDisplayFrame(std::make_shared<DisplayFrame>()),
mMaxDisplayFrames(kDefaultMaxDisplayFrames),
mTimeStats(std::move(timeStats)) {}
+void FrameTimeline::onBootFinished() {
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+ registerDataSource();
+}
+
+void FrameTimeline::registerDataSource() {
+ perfetto::DataSourceDescriptor dsd;
+ dsd.set_name(kFrameTimelineDataSource);
+ FrameTimelineDataSource::Register(dsd);
+}
+
FrameTimeline::DisplayFrame::DisplayFrame()
- : surfaceFlingerPredictions(TimelineItem()),
- surfaceFlingerActuals(TimelineItem()),
- predictionState(PredictionState::None),
- jankType(TimeStats::JankType::None),
- jankMetadata(0) {
+ : surfaceFlingerPredictions(TimelineItem()), surfaceFlingerActuals(TimelineItem()) {
this->surfaceFrames.reserve(kNumSurfaceFramesInitial);
}
@@ -310,17 +391,19 @@
std::optional<int64_t> token) {
ATRACE_CALL();
if (!token) {
- return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
+ return std::make_unique<impl::SurfaceFrame>(ISurfaceComposer::INVALID_VSYNC_ID, ownerPid,
+ ownerUid, std::move(layerName),
std::move(debugName), PredictionState::None,
TimelineItem());
}
std::optional<TimelineItem> predictions = mTokenManager.getPredictionsForToken(*token);
if (predictions) {
- return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
- std::move(debugName), PredictionState::Valid,
+ return std::make_unique<impl::SurfaceFrame>(*token, ownerPid, ownerUid,
+ std::move(layerName), std::move(debugName),
+ PredictionState::Valid,
std::move(*predictions));
}
- return std::make_unique<impl::SurfaceFrame>(ownerPid, ownerUid, std::move(layerName),
+ return std::make_unique<impl::SurfaceFrame>(*token, ownerPid, ownerUid, std::move(layerName),
std::move(debugName), PredictionState::Expired,
TimelineItem());
}
@@ -340,6 +423,7 @@
ATRACE_CALL();
const std::optional<TimelineItem> prediction = mTokenManager.getPredictionsForToken(token);
std::lock_guard<std::mutex> lock(mMutex);
+ mCurrentDisplayFrame->token = token;
if (!prediction) {
mCurrentDisplayFrame->predictionState = PredictionState::Expired;
} else {
@@ -370,7 +454,7 @@
}
}
if (signalTime != Fence::SIGNAL_TIME_INVALID) {
- int32_t totalJankReasons = TimeStats::JankType::None;
+ int32_t totalJankReasons = JankType::None;
auto& displayFrame = pendingPresentFence.second;
displayFrame->surfaceFlingerActuals.presentTime = signalTime;
@@ -391,14 +475,14 @@
if ((displayFrame->jankMetadata & EarlyFinish) &&
(displayFrame->jankMetadata & EarlyPresent)) {
- displayFrame->jankType = TimeStats::JankType::SurfaceFlingerEarlyLatch;
+ displayFrame->jankType = JankType::SurfaceFlingerEarlyLatch;
} else if ((displayFrame->jankMetadata & LateFinish) &&
(displayFrame->jankMetadata & LatePresent)) {
- displayFrame->jankType = TimeStats::JankType::SurfaceFlingerDeadlineMissed;
+ displayFrame->jankType = JankType::SurfaceFlingerDeadlineMissed;
} else if (displayFrame->jankMetadata & EarlyPresent ||
displayFrame->jankMetadata & LatePresent) {
// Cases where SF finished early but frame was presented late and vice versa
- displayFrame->jankType = TimeStats::JankType::Display;
+ displayFrame->jankType = JankType::Display;
}
}
@@ -408,6 +492,7 @@
}
totalJankReasons |= displayFrame->jankType;
+ traceDisplayFrame(*displayFrame);
for (auto& surfaceFrame : displayFrame->surfaceFrames) {
if (surfaceFrame->getPresentState() == SurfaceFrame::PresentState::Presented) {
@@ -418,13 +503,12 @@
const auto& predictionState = surfaceFrame->getPredictionState();
if (predictionState == PredictionState::Expired) {
// Jank analysis cannot be done on apps that don't use predictions
- surfaceFrame->setJankInfo(TimeStats::JankType::PredictionExpired, 0);
- continue;
+ surfaceFrame->setJankInfo(JankType::PredictionExpired, 0);
} else if (predictionState == PredictionState::Valid) {
const auto& actuals = surfaceFrame->getActuals();
const auto& predictions = surfaceFrame->getPredictions();
int32_t jankMetadata = 0;
- TimeStats::JankType jankType = TimeStats::JankType::None;
+ JankType jankType = JankType::None;
if (std::abs(actuals.endTime - predictions.endTime) > kDeadlineThreshold) {
jankMetadata |= actuals.endTime > predictions.endTime ? LateFinish
: EarlyFinish;
@@ -436,13 +520,13 @@
: EarlyPresent;
}
if (jankMetadata & EarlyPresent) {
- jankType = TimeStats::JankType::SurfaceFlingerEarlyLatch;
+ jankType = JankType::SurfaceFlingerEarlyLatch;
} else if (jankMetadata & LatePresent) {
if (jankMetadata & EarlyFinish) {
// TODO(b/169890654): Classify this properly
- jankType = TimeStats::JankType::Display;
+ jankType = JankType::Display;
} else {
- jankType = TimeStats::JankType::AppDeadlineMissed;
+ jankType = JankType::AppDeadlineMissed;
}
}
@@ -453,6 +537,7 @@
surfaceFrame->setJankInfo(jankType, jankMetadata);
}
}
+ surfaceFrame->traceSurfaceFrame(displayFrame->token);
}
mTimeStats->incrementJankyFrames(totalJankReasons);
@@ -491,7 +576,7 @@
void FrameTimeline::dumpDisplayFrame(std::string& result,
const std::shared_ptr<DisplayFrame>& displayFrame,
nsecs_t baseTime) {
- if (displayFrame->jankType != TimeStats::JankType::None) {
+ if (displayFrame->jankType != JankType::None) {
// Easily identify a janky Display Frame in the dump
StringAppendF(&result, " [*] ");
}
@@ -525,11 +610,11 @@
nsecs_t baseTime = (mDisplayFrames.empty()) ? 0 : findBaseTime(mDisplayFrames[0]);
for (size_t i = 0; i < mDisplayFrames.size(); i++) {
const auto& displayFrame = mDisplayFrames[i];
- if (displayFrame->jankType == TimeStats::JankType::None) {
+ if (displayFrame->jankType == JankType::None) {
// Check if any Surface Frame has been janky
bool isJanky = false;
for (const auto& surfaceFrame : displayFrame->surfaceFrames) {
- if (surfaceFrame->getJankType() != TimeStats::JankType::None) {
+ if (surfaceFrame->getJankType() != JankType::None) {
isJanky = true;
break;
}
@@ -569,4 +654,31 @@
setMaxDisplayFrames(kDefaultMaxDisplayFrames);
}
+void FrameTimeline::traceDisplayFrame(const DisplayFrame& displayFrame) {
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ if (displayFrame.token == ISurfaceComposer::INVALID_VSYNC_ID) {
+ ALOGD("Cannot trace DisplayFrame with invalid token");
+ return;
+ }
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ packet->set_timestamp(static_cast<uint64_t>(systemTime()));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* displayFrameEvent = event->set_display_frame();
+
+ displayFrameEvent->set_token(displayFrame.token);
+ displayFrameEvent->set_present_type(presentTypeToProto(displayFrame.jankMetadata));
+ displayFrameEvent->set_on_time_finish(!(displayFrame.jankMetadata & LateFinish));
+ displayFrameEvent->set_gpu_composition(displayFrame.jankMetadata & GpuComposition);
+ displayFrameEvent->set_jank_type(JankTypeToProto(displayFrame.jankType));
+
+ displayFrameEvent->set_expected_start_ns(displayFrame.surfaceFlingerPredictions.startTime);
+ displayFrameEvent->set_expected_end_ns(displayFrame.surfaceFlingerPredictions.endTime);
+
+ displayFrameEvent->set_actual_start_ns(displayFrame.surfaceFlingerActuals.startTime);
+ displayFrameEvent->set_actual_end_ns(displayFrame.surfaceFlingerActuals.endTime);
+ });
+}
+
} // namespace android::frametimeline::impl
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index e61567e..fe83ebf 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -18,6 +18,9 @@
#include <../TimeStats/TimeStats.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/JankInfo.h>
+#include <perfetto/trace/android/frame_timeline_event.pbzero.h>
+#include <perfetto/tracing.h>
#include <ui/FenceTime.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
@@ -128,6 +131,10 @@
virtual ~FrameTimeline() = default;
virtual TokenManager* getTokenManager() = 0;
+ // Initializes the Perfetto DataSource that emits DisplayFrame and SurfaceFrame events. Test
+ // classes can avoid double registration by mocking this function.
+ virtual void onBootFinished() = 0;
+
// Create a new surface frame, set the predictions based on a token and return it to the caller.
// Sets the PredictionState of SurfaceFrame.
// Debug name is the human-readable debugging string for dumpsys.
@@ -191,8 +198,9 @@
class SurfaceFrame : public android::frametimeline::SurfaceFrame {
public:
- SurfaceFrame(pid_t ownerPid, uid_t ownerUid, std::string layerName, std::string debugName,
- PredictionState predictionState, TimelineItem&& predictions);
+ SurfaceFrame(int64_t token, pid_t ownerPid, uid_t ownerUid, std::string layerName,
+ std::string debugName, PredictionState predictionState,
+ TimelineItem&& predictions);
~SurfaceFrame() = default;
TimelineItem getPredictions() const override { return mPredictions; };
@@ -201,7 +209,8 @@
PresentState getPresentState() const override;
PredictionState getPredictionState() const override { return mPredictionState; };
pid_t getOwnerPid() const override { return mOwnerPid; };
- TimeStats::JankType getJankType() const;
+ JankType getJankType() const;
+ int64_t getToken() const { return mToken; };
nsecs_t getBaseTime() const;
uid_t getOwnerUid() const { return mOwnerUid; };
const std::string& getName() const { return mLayerName; };
@@ -211,12 +220,18 @@
void setAcquireFenceTime(nsecs_t acquireFenceTime) override;
void setPresentState(PresentState state) override;
void setActualPresentTime(nsecs_t presentTime);
- void setJankInfo(TimeStats::JankType jankType, int32_t jankMetadata);
+ void setJankInfo(JankType jankType, int32_t jankMetadata);
// All the timestamps are dumped relative to the baseTime
void dump(std::string& result, const std::string& indent, nsecs_t baseTime);
+ // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
+ // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
+ // DisplayFrame at the trace processor side.
+ void traceSurfaceFrame(int64_t displayFrameToken);
+
private:
+ const int64_t mToken;
const pid_t mOwnerPid;
const uid_t mOwnerUid;
const std::string mLayerName;
@@ -227,12 +242,18 @@
TimelineItem mActuals GUARDED_BY(mMutex);
nsecs_t mActualQueueTime GUARDED_BY(mMutex);
mutable std::mutex mMutex;
- TimeStats::JankType mJankType GUARDED_BY(mMutex); // Enum for the type of jank
+ JankType mJankType GUARDED_BY(mMutex); // Enum for the type of jank
int32_t mJankMetadata GUARDED_BY(mMutex); // Additional details about the jank
};
class FrameTimeline : public android::frametimeline::FrameTimeline {
public:
+ class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
+ void OnSetup(const SetupArgs&) override{};
+ void OnStart(const StartArgs&) override{};
+ void OnStop(const StopArgs&) override{};
+ };
+
FrameTimeline(std::shared_ptr<TimeStats> timeStats);
~FrameTimeline() = default;
@@ -249,6 +270,14 @@
void setMaxDisplayFrames(uint32_t size) override;
void reset() override;
+ // Sets up the perfetto tracing backend and data source.
+ void onBootFinished() override;
+ // Registers the data source with the perfetto backend. Called as part of onBootFinished()
+ // and should not be called manually outside of tests.
+ void registerDataSource();
+
+ static constexpr char kFrameTimelineDataSource[] = "android.surfaceflinger.frametimeline";
+
private:
// Friend class for testing
friend class android::frametimeline::FrameTimelineTest;
@@ -259,6 +288,8 @@
struct DisplayFrame {
DisplayFrame();
+ int64_t token = ISurfaceComposer::INVALID_VSYNC_ID;
+
/* Usage of TimelineItem w.r.t SurfaceFlinger
* startTime Time when SurfaceFlinger wakes up to handle transactions and buffer updates
* endTime Time when SurfaceFlinger sends a composited frame to Display
@@ -270,9 +301,9 @@
// Collection of predictions and actual values sent over by Layers
std::vector<std::unique_ptr<SurfaceFrame>> surfaceFrames;
- PredictionState predictionState;
- TimeStats::JankType jankType = TimeStats::JankType::None; // Enum for the type of jank
- int32_t jankMetadata = 0x0; // Additional details about the jank
+ PredictionState predictionState = PredictionState::None;
+ JankType jankType = JankType::None; // Enum for the type of jank
+ int32_t jankMetadata = 0x0; // Additional details about the jank
};
void flushPendingPresentFences() REQUIRES(mMutex);
@@ -285,6 +316,10 @@
void dumpAll(std::string& result);
void dumpJank(std::string& result);
+ // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
+ // enabled.
+ void traceDisplayFrame(const DisplayFrame& displayFrame) REQUIRES(mMutex);
+
// Sliding window of display frames. TODO(b/168072834): compare perf with fixed size array
std::deque<std::shared_ptr<DisplayFrame>> mDisplayFrames GUARDED_BY(mMutex);
std::vector<std::pair<std::shared_ptr<FenceTime>, std::shared_ptr<DisplayFrame>>>
@@ -295,10 +330,10 @@
uint32_t mMaxDisplayFrames;
std::shared_ptr<TimeStats> mTimeStats;
static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
- // The initial container size for the vector<SurfaceFrames> inside display frame. Although this
- // number doesn't represent any bounds on the number of surface frames that can go in a display
- // frame, this is a good starting size for the vector so that we can avoid the internal vector
- // resizing that happens with push_back.
+ // The initial container size for the vector<SurfaceFrames> inside display frame. Although
+ // this number doesn't represent any bounds on the number of surface frames that can go in a
+ // display frame, this is a good starting size for the vector so that we can avoid the
+ // internal vector resizing that happens with push_back.
static constexpr uint32_t kNumSurfaceFramesInitial = 10;
// The various thresholds for App and SF. If the actual timestamp falls within the threshold
// compared to prediction, we don't treat it as a jank.
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 359ee26..c0d00f3 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,21 +35,21 @@
#include "LayerInfo.h"
#include "SchedulerUtils.h"
-namespace android::scheduler::impl {
+namespace android::scheduler {
namespace {
bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
+ // Layers with an explicit vote are always kept active
if (layer.getFrameRateForLayerTree().rate > 0) {
- return layer.isVisible();
+ return true;
}
+
return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
}
bool traceEnabled() {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.sf.layer_history_trace", value, "0");
- return atoi(value);
+ return property_get_bool("debug.sf.layer_history_trace", false);
}
bool useFrameRatePriority() {
@@ -58,30 +58,44 @@
return atoi(value);
}
-void trace(const wp<Layer>& weak, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfo& info, LayerHistory::LayerVoteType type,
+ int fps) {
const auto layer = weak.promote();
if (!layer) return;
- const auto& name = layer->getName();
- const auto tag = "LFPS " + name;
- ATRACE_INT(tag.c_str(), fps);
- ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+ const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+ ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+ };
+
+ traceType(LayerHistory::LayerVoteType::NoVote, 1);
+ traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+ traceType(LayerHistory::LayerVoteType::Min, 1);
+ traceType(LayerHistory::LayerVoteType::Max, 1);
+
+ ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
}
} // namespace
-LayerHistory::LayerHistory()
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistory::LayerHistory(const RefreshRateConfigs& refreshRateConfigs)
+ : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+ LayerInfo::setTraceEnabled(mTraceEnabled);
+ LayerInfo::setRefreshRateConfigs(refreshRateConfigs);
+}
+
LayerHistory::~LayerHistory() = default;
-void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate,
- LayerVoteType /*type*/) {
- auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
+void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
+ LayerVoteType type) {
+ const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+ auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
std::lock_guard lock(mLock);
mLayerInfos.emplace_back(layer, std::move(info));
}
void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
- LayerUpdateType /*updateType*/) {
+ LayerUpdateType updateType) {
std::lock_guard lock(mLock);
const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -89,7 +103,7 @@
LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
const auto& info = it->second;
- info->setLastPresentTime(presentTime, now);
+ info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
// Activate layer if inactive.
if (const auto end = activeLayers().end(); it >= end) {
@@ -99,54 +113,42 @@
}
LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
- ATRACE_CALL();
+ LayerHistory::Summary summary;
+
std::lock_guard lock(mLock);
partitionLayers(now);
- LayerHistory::Summary summary;
- for (const auto& [weakLayer, info] : activeLayers()) {
- const bool recent = info->isRecentlyActive(now);
- auto layer = weakLayer.promote();
- // Only use the layer if the reference still exists.
- if (layer || CC_UNLIKELY(mTraceEnabled)) {
- const auto layerFocused =
- Layer::isLayerFocusedBasedOnPriority(layer->getFrameRateSelectionPriority());
- // Check if frame rate was set on layer.
- const auto frameRate = layer->getFrameRateForLayerTree();
- if (frameRate.rate > 0.f) {
- const auto voteType = [&]() {
- switch (frameRate.type) {
- case Layer::FrameRateCompatibility::Default:
- return LayerVoteType::ExplicitDefault;
- case Layer::FrameRateCompatibility::ExactOrMultiple:
- return LayerVoteType::ExplicitExactOrMultiple;
- case Layer::FrameRateCompatibility::NoVote:
- return LayerVoteType::NoVote;
- }
- }();
- summary.push_back(
- RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
- .vote = voteType,
- .desiredRefreshRate = frameRate.rate,
- .seamlessness = frameRate.seamlessness,
- .weight = 1.0f,
- .focused = layerFocused});
- } else if (recent) {
- summary.push_back(
- RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
- .vote = LayerVoteType::Heuristic,
- .desiredRefreshRate =
- info->getRefreshRate(now),
- .seamlessness =
- Seamlessness::OnlySeamless,
- .weight = 1.0f,
- .focused = layerFocused});
- }
+ for (const auto& [layer, info] : activeLayers()) {
+ const auto strong = layer.promote();
+ if (!strong) {
+ continue;
+ }
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weakLayer, round<int>(frameRate.rate));
- }
+ const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
+ const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
+ ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
+ frameRateSelectionPriority, layerFocused ? "" : "not");
+
+ const auto vote = info->getRefreshRateVote(now);
+ // Skip NoVote layer as those don't have any requirements
+ if (vote.type == LayerHistory::LayerVoteType::NoVote) {
+ continue;
+ }
+
+ // Compute the layer's position on the screen
+ const Rect bounds = Rect(strong->getBounds());
+ const ui::Transform transform = strong->getTransform();
+ constexpr bool roundOutwards = true;
+ Rect transformed = transform.transform(bounds, roundOutwards);
+
+ const float layerArea = transformed.getWidth() * transformed.getHeight();
+ float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+ summary.push_back(
+ {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
+
+ if (CC_UNLIKELY(mTraceEnabled)) {
+ trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
}
}
@@ -162,14 +164,33 @@
auto& [weak, info] = mLayerInfos[i];
if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
i++;
+ // Set layer vote if set
+ const auto frameRate = layer->getFrameRateForLayerTree();
+ const auto voteType = [&]() {
+ switch (frameRate.type) {
+ case Layer::FrameRateCompatibility::Default:
+ return LayerVoteType::ExplicitDefault;
+ case Layer::FrameRateCompatibility::ExactOrMultiple:
+ return LayerVoteType::ExplicitExactOrMultiple;
+ case Layer::FrameRateCompatibility::NoVote:
+ return LayerVoteType::NoVote;
+ }
+ }();
+
+ if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+ const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
+ info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
+ } else {
+ info->resetLayerVote();
+ }
continue;
}
if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weak, 0);
+ trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
}
- info->clearHistory();
+ info->onLayerInactive(now);
std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
}
@@ -190,10 +211,8 @@
std::lock_guard lock(mLock);
for (const auto& [layer, info] : activeLayers()) {
- info->clearHistory();
+ info->clearHistory(systemTime());
}
-
- mActiveLayersEnd = 0;
}
std::string LayerHistory::dump() const {
@@ -202,4 +221,4 @@
mActiveLayersEnd);
}
-} // namespace android::scheduler::impl
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 128699b..507ccc6 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -36,25 +36,23 @@
namespace scheduler {
class LayerHistoryTest;
-class LayerHistoryTestV2;
class LayerInfo;
-class LayerInfoV2;
class LayerHistory {
public:
using LayerVoteType = RefreshRateConfigs::LayerVoteType;
- virtual ~LayerHistory() = default;
+ LayerHistory(const RefreshRateConfigs&);
+ ~LayerHistory();
// Layers are unregistered when the weak reference expires.
- virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) = 0;
+ void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate, LayerVoteType type);
// Sets the display size. Client is responsible for synchronization.
- virtual void setDisplayArea(uint32_t displayArea) = 0;
+ void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
// Sets whether a config change is pending to be applied
- virtual void setConfigChangePending(bool pending) = 0;
+ void setConfigChangePending(bool pending) { mConfigChangePending = pending; }
// Represents which layer activity is recorded
enum class LayerUpdateType {
@@ -64,45 +62,18 @@
};
// Marks the layer as active, and records the given state to its history.
- virtual void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) = 0;
+ void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType);
using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
// Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- virtual Summary summarize(nsecs_t now) = 0;
+ Summary summarize(nsecs_t now);
- virtual void clear() = 0;
- virtual std::string dump() const = 0;
-};
-
-namespace impl {
-// Records per-layer history of scheduling-related information (primarily present time),
-// heuristically categorizes layers as active or inactive, and summarizes stats about
-// active layers (primarily maximum refresh rate). See go/content-fps-detection-in-scheduler.
-class LayerHistory : public android::scheduler::LayerHistory {
-public:
- LayerHistory();
- virtual ~LayerHistory();
-
- // Layers are unregistered when the weak reference expires.
- void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) override;
-
- void setDisplayArea(uint32_t /*displayArea*/) override {}
-
- void setConfigChangePending(bool /*pending*/) override {}
-
- // Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
- // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- android::scheduler::LayerHistory::Summary summarize(nsecs_t now) override;
-
- void clear() override;
- std::string dump() const override;
+ void clear();
+ std::string dump() const;
private:
- friend class android::scheduler::LayerHistoryTest;
+ friend LayerHistoryTest;
friend TestableScheduler;
using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfo>>;
@@ -130,65 +101,6 @@
LayerInfos mLayerInfos GUARDED_BY(mLock);
size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
- // Whether to emit systrace output and debug logs.
- const bool mTraceEnabled;
-
- // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
- const bool mUseFrameRatePriority;
-};
-
-class LayerHistoryV2 : public android::scheduler::LayerHistory {
-public:
- LayerHistoryV2(const scheduler::RefreshRateConfigs&);
- virtual ~LayerHistoryV2();
-
- // Layers are unregistered when the weak reference expires.
- void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) override;
-
- // Sets the display size. Client is responsible for synchronization.
- void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
-
- void setConfigChangePending(bool pending) override { mConfigChangePending = pending; }
-
- // Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
- // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
-
- void clear() override;
- std::string dump() const override;
-
-private:
- friend android::scheduler::LayerHistoryTestV2;
- friend TestableScheduler;
-
- using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfoV2>>;
- using LayerInfos = std::vector<LayerPair>;
-
- struct ActiveLayers {
- LayerInfos& infos;
- const size_t index;
-
- auto begin() { return infos.begin(); }
- auto end() { return begin() + static_cast<long>(index); }
- };
-
- ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
-
- // Iterates over layers in a single pass, swapping pairs such that active layers precede
- // inactive layers, and inactive layers precede expired layers. Removes expired layers by
- // truncating after inactive layers.
- void partitionLayers(nsecs_t now) REQUIRES(mLock);
-
- mutable std::mutex mLock;
-
- // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
- // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
- LayerInfos mLayerInfos GUARDED_BY(mLock);
- size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
-
uint32_t mDisplayArea = 0;
// Whether to emit systrace output and debug logs.
@@ -201,6 +113,5 @@
std::atomic<bool> mConfigChangePending = false;
};
-} // namespace impl
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
deleted file mode 100644
index e919d1b..0000000
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerHistory.h"
-
-#include <android-base/stringprintf.h>
-#include <cutils/properties.h>
-#include <utils/Log.h>
-#include <utils/Timers.h>
-#include <utils/Trace.h>
-
-#include <algorithm>
-#include <cmath>
-#include <string>
-#include <utility>
-
-#include "../Layer.h"
-#include "LayerInfoV2.h"
-#include "SchedulerUtils.h"
-
-namespace android::scheduler::impl {
-
-namespace {
-
-bool isLayerActive(const Layer& layer, const LayerInfoV2& info, nsecs_t threshold) {
- // Layers with an explicit vote are always kept active
- if (layer.getFrameRateForLayerTree().rate > 0) {
- return true;
- }
-
- return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
-}
-
-bool traceEnabled() {
- return property_get_bool("debug.sf.layer_history_trace", false);
-}
-
-bool useFrameRatePriority() {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.sf.use_frame_rate_priority", value, "1");
- return atoi(value);
-}
-
-void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
- int fps) {
- const auto layer = weak.promote();
- if (!layer) return;
-
- const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
- ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
- };
-
- traceType(LayerHistory::LayerVoteType::NoVote, 1);
- traceType(LayerHistory::LayerVoteType::Heuristic, fps);
- traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
- traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
- traceType(LayerHistory::LayerVoteType::Min, 1);
- traceType(LayerHistory::LayerVoteType::Max, 1);
-
- ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
-}
-} // namespace
-
-LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
- LayerInfoV2::setTraceEnabled(mTraceEnabled);
- LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
-}
-
-LayerHistoryV2::~LayerHistoryV2() = default;
-
-void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
- LayerVoteType type) {
- const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
- auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
- std::lock_guard lock(mLock);
- mLayerInfos.emplace_back(layer, std::move(info));
-}
-
-void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
- LayerUpdateType updateType) {
- std::lock_guard lock(mLock);
-
- const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
- [layer](const auto& pair) { return pair.first == layer; });
- LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
-
- const auto& info = it->second;
- info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
-
- // Activate layer if inactive.
- if (const auto end = activeLayers().end(); it >= end) {
- std::iter_swap(it, end);
- mActiveLayersEnd++;
- }
-}
-
-LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
- LayerHistory::Summary summary;
-
- std::lock_guard lock(mLock);
-
- partitionLayers(now);
-
- for (const auto& [layer, info] : activeLayers()) {
- const auto strong = layer.promote();
- if (!strong) {
- continue;
- }
-
- const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
- const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
- ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
- frameRateSelectionPriority, layerFocused ? "" : "not");
-
- const auto vote = info->getRefreshRateVote(now);
- // Skip NoVote layer as those don't have any requirements
- if (vote.type == LayerHistory::LayerVoteType::NoVote) {
- continue;
- }
-
- // Compute the layer's position on the screen
- const Rect bounds = Rect(strong->getBounds());
- const ui::Transform transform = strong->getTransform();
- constexpr bool roundOutwards = true;
- Rect transformed = transform.transform(bounds, roundOutwards);
-
- const float layerArea = transformed.getWidth() * transformed.getHeight();
- float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
- summary.push_back(
- {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
-
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
- }
- }
-
- return summary;
-}
-
-void LayerHistoryV2::partitionLayers(nsecs_t now) {
- const nsecs_t threshold = getActiveLayerThreshold(now);
-
- // Collect expired and inactive layers after active layers.
- size_t i = 0;
- while (i < mActiveLayersEnd) {
- auto& [weak, info] = mLayerInfos[i];
- if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
- i++;
- // Set layer vote if set
- const auto frameRate = layer->getFrameRateForLayerTree();
- const auto voteType = [&]() {
- switch (frameRate.type) {
- case Layer::FrameRateCompatibility::Default:
- return LayerVoteType::ExplicitDefault;
- case Layer::FrameRateCompatibility::ExactOrMultiple:
- return LayerVoteType::ExplicitExactOrMultiple;
- case Layer::FrameRateCompatibility::NoVote:
- return LayerVoteType::NoVote;
- }
- }();
-
- if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
- const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
- info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
- } else {
- info->resetLayerVote();
- }
- continue;
- }
-
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
- }
-
- info->onLayerInactive(now);
- std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
- }
-
- // Collect expired layers after inactive layers.
- size_t end = mLayerInfos.size();
- while (i < end) {
- if (mLayerInfos[i].first.promote()) {
- i++;
- } else {
- std::swap(mLayerInfos[i], mLayerInfos[--end]);
- }
- }
-
- mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
-}
-
-void LayerHistoryV2::clear() {
- std::lock_guard lock(mLock);
-
- for (const auto& [layer, info] : activeLayers()) {
- info->clearHistory(systemTime());
- }
-}
-
-std::string LayerHistoryV2::dump() const {
- std::lock_guard lock(mLock);
- return base::StringPrintf("LayerHistoryV2{size=%zu, active=%zu}", mLayerInfos.size(),
- mActiveLayersEnd);
-}
-
-} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 6d9dd43..66ac98a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,36 +14,284 @@
* limitations under the License.
*/
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include "LayerInfo.h"
#include <algorithm>
#include <utility>
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfo"
+
namespace android::scheduler {
-LayerInfo::LayerInfo(float lowRefreshRate, float highRefreshRate)
- : mLowRefreshRate(lowRefreshRate), mHighRefreshRate(highRefreshRate) {}
+const RefreshRateConfigs* LayerInfo::sRefreshRateConfigs = nullptr;
+bool LayerInfo::sTraceEnabled = false;
-void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+LayerInfo::LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote)
+ : mName(name),
+ mHighRefreshRatePeriod(highRefreshRatePeriod),
+ mDefaultVote(defaultVote),
+ mLayerVote({defaultVote, 0.0f}),
+ mRefreshRateHistory(name) {}
+
+void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+ bool pendingConfigChange) {
lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
- // Buffers can come with a present time far in the future. That keeps them relevant.
mLastUpdatedTime = std::max(lastPresentTime, now);
- mPresentTimeHistory.insertPresentTime(mLastUpdatedTime);
+ switch (updateType) {
+ case LayerUpdateType::AnimationTX:
+ mLastAnimationTime = std::max(lastPresentTime, now);
+ break;
+ case LayerUpdateType::SetFrameRate:
+ case LayerUpdateType::Buffer:
+ FrameTimeData frameTime = {.presetTime = lastPresentTime,
+ .queueTime = mLastUpdatedTime,
+ .pendingConfigChange = pendingConfigChange};
+ mFrameTimes.push_back(frameTime);
+ if (mFrameTimes.size() > HISTORY_SIZE) {
+ mFrameTimes.pop_front();
+ }
+ break;
+ }
+}
- if (mLastPresentTime == 0) {
- // First frame
- mLastPresentTime = lastPresentTime;
- return;
+bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
+ return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
+ mFrameTimeValidSince.time_since_epoch())
+ .count();
+}
+
+bool LayerInfo::isFrequent(nsecs_t now) const {
+ // If we know nothing about this layer we consider it as frequent as it might be the start
+ // of an animation.
+ if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+ return true;
}
- const nsecs_t period = lastPresentTime - mLastPresentTime;
- mLastPresentTime = lastPresentTime;
- // Ignore time diff that are too high - those are stale values
- if (period > MAX_ACTIVE_LAYER_PERIOD_NS.count()) return;
+ // Find the first active frame
+ auto it = mFrameTimes.begin();
+ for (; it != mFrameTimes.end(); ++it) {
+ if (it->queueTime >= getActiveLayerThreshold(now)) {
+ break;
+ }
+ }
- const float fps = std::min(1e9f / period, mHighRefreshRate);
- mRefreshRateHistory.insertRefreshRate(fps);
+ const auto numFrames = std::distance(it, mFrameTimes.end());
+ if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
+ return false;
+ }
+
+ // Layer is considered frequent if the average frame rate is higher than the threshold
+ const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
+ return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
+}
+
+bool LayerInfo::isAnimating(nsecs_t now) const {
+ return mLastAnimationTime >= getActiveLayerThreshold(now);
+}
+
+bool LayerInfo::hasEnoughDataForHeuristic() const {
+ // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
+ if (mFrameTimes.size() < 2) {
+ ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+ return false;
+ }
+
+ if (!isFrameTimeValid(mFrameTimes.front())) {
+ ALOGV("stale frames still captured");
+ return false;
+ }
+
+ const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+ if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+ ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+ totalDuration / 1e9f);
+ return false;
+ }
+
+ return true;
+}
+
+std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
+ nsecs_t totalPresentTimeDeltas = 0;
+ nsecs_t totalQueueTimeDeltas = 0;
+ bool missingPresentTime = false;
+ int numFrames = 0;
+ for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+ // Ignore frames captured during a config change
+ if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+ return std::nullopt;
+ }
+
+ totalQueueTimeDeltas +=
+ std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+ numFrames++;
+
+ if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
+ missingPresentTime = true;
+ // If there are no presentation timestamps and we haven't calculated
+ // one in the past then we can't calculate the refresh rate
+ if (mLastRefreshRate.reported == 0) {
+ return std::nullopt;
+ }
+ continue;
+ }
+
+ totalPresentTimeDeltas +=
+ std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+ }
+
+ // Calculate the average frame time based on presentation timestamps. If those
+ // doesn't exist, we look at the time the buffer was queued only. We can do that only if
+ // we calculated a refresh rate based on presentation timestamps in the past. The reason
+ // we look at the queue time is to handle cases where hwui attaches presentation timestamps
+ // when implementing render ahead for specific refresh rates. When hwui no longer provides
+ // presentation timestamps we look at the queue time to see if the current refresh rate still
+ // matches the content.
+
+ const auto averageFrameTime =
+ static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
+ numFrames;
+ return static_cast<nsecs_t>(averageFrameTime);
+}
+
+std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+ static constexpr float MARGIN = 1.0f; // 1Hz
+ if (!hasEnoughDataForHeuristic()) {
+ ALOGV("Not enough data");
+ return std::nullopt;
+ }
+
+ const auto averageFrameTime = calculateAverageFrameTime();
+ if (averageFrameTime.has_value()) {
+ const auto refreshRate = 1e9f / *averageFrameTime;
+ const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+ if (refreshRateConsistent) {
+ const auto knownRefreshRate =
+ sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
+
+ // To avoid oscillation, use the last calculated refresh rate if it is
+ // close enough
+ if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+ mLastRefreshRate.reported != knownRefreshRate) {
+ mLastRefreshRate.calculated = refreshRate;
+ mLastRefreshRate.reported = knownRefreshRate;
+ }
+
+ ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
+ } else {
+ ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
+ }
+ }
+
+ return mLastRefreshRate.reported == 0 ? std::nullopt
+ : std::make_optional(mLastRefreshRate.reported);
+}
+
+LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
+ if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+ ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
+ return mLayerVote;
+ }
+
+ if (isAnimating(now)) {
+ ALOGV("%s is animating", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
+ return {LayerHistory::LayerVoteType::Max, 0};
+ }
+
+ if (!isFrequent(now)) {
+ ALOGV("%s is infrequent", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
+ return {LayerHistory::LayerVoteType::Min, 0};
+ }
+
+ // If the layer was previously tagged as animating or infrequent, we clear
+ // the history as it is likely the layer just changed its behavior
+ // and we should not look at stale data
+ if (mLastRefreshRate.animatingOrInfrequent) {
+ clearHistory(now);
+ }
+
+ auto refreshRate = calculateRefreshRateIfPossible(now);
+ if (refreshRate.has_value()) {
+ ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+ return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+ }
+
+ ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
+ return {LayerHistory::LayerVoteType::Max, 0};
+}
+
+const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+ if (mTraceTags.count(type) == 0) {
+ const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+ mTraceTags.emplace(type, tag);
+ }
+
+ return mTraceTags.at(type).c_str();
+}
+
+LayerInfo::RefreshRateHistory::HeuristicTraceTagData
+LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
+ const std::string prefix = "LFPS ";
+ const std::string suffix = "Heuristic ";
+ return {.min = prefix + mName + suffix + "min",
+ .max = prefix + mName + suffix + "max",
+ .consistent = prefix + mName + suffix + "consistent",
+ .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfo::RefreshRateHistory::clear() {
+ mRefreshRates.clear();
+}
+
+bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+ mRefreshRates.push_back({refreshRate, now});
+ while (mRefreshRates.size() >= HISTORY_SIZE ||
+ now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+ mRefreshRates.pop_front();
+ }
+
+ if (CC_UNLIKELY(sTraceEnabled)) {
+ if (!mHeuristicTraceTagData.has_value()) {
+ mHeuristicTraceTagData = makeHeuristicTraceTagData();
+ }
+
+ ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+ }
+
+ return isConsistent();
+}
+
+bool LayerInfo::RefreshRateHistory::isConsistent() const {
+ if (mRefreshRates.empty()) return true;
+
+ const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
+ const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
+ const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+
+ if (CC_UNLIKELY(sTraceEnabled)) {
+ if (!mHeuristicTraceTagData.has_value()) {
+ mHeuristicTraceTagData = makeHeuristicTraceTagData();
+ }
+
+ ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+ }
+
+ return consistent;
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 820624b..e434670 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,9 @@
#include <chrono>
#include <deque>
+#include "LayerHistory.h"
+#include "RefreshRateConfigs.h"
+#include "Scheduler/Seamlessness.h"
#include "SchedulerUtils.h"
namespace android {
@@ -41,129 +44,179 @@
// Stores history of present times and refresh rates for a layer.
class LayerInfo {
+ using LayerUpdateType = LayerHistory::LayerUpdateType;
+
// Layer is considered frequent if the earliest value in the window of most recent present times
// is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
// favor of a low refresh rate.
static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
-
- /**
- * Struct that keeps the information about the refresh rate for last
- * HISTORY_SIZE frames. This is used to better determine the refresh rate
- * for individual layers.
- */
- class RefreshRateHistory {
- public:
- explicit RefreshRateHistory(float highRefreshRate) : mHighRefreshRate(highRefreshRate) {}
-
- void insertRefreshRate(float refreshRate) {
- mElements.push_back(refreshRate);
- if (mElements.size() > HISTORY_SIZE) {
- mElements.pop_front();
- }
- }
-
- float getRefreshRateAvg() const {
- return mElements.empty() ? mHighRefreshRate : calculate_mean(mElements);
- }
-
- void clearHistory() { mElements.clear(); }
-
- private:
- const float mHighRefreshRate;
-
- static constexpr size_t HISTORY_SIZE = 30;
- std::deque<float> mElements;
- };
-
- /**
- * Struct that keeps the information about the present time for last
- * HISTORY_SIZE frames. This is used to better determine whether the given layer
- * is still relevant and it's refresh rate should be considered.
- */
- class PresentTimeHistory {
- public:
- static constexpr size_t HISTORY_SIZE = 90;
-
- void insertPresentTime(nsecs_t presentTime) {
- mElements.push_back(presentTime);
- if (mElements.size() > HISTORY_SIZE) {
- mElements.pop_front();
- }
- }
-
- // Returns whether the earliest present time is within the active threshold.
- bool isRecentlyActive(nsecs_t now) const {
- if (mElements.size() < 2) {
- return false;
- }
-
- // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
- if (mElements.size() < HISTORY_SIZE &&
- mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
- return false;
- }
-
- return mElements.back() >= getActiveLayerThreshold(now);
- }
-
- bool isFrequent(nsecs_t now) const {
- // Assume layer is infrequent if too few present times have been recorded.
- if (mElements.size() < FREQUENT_LAYER_WINDOW_SIZE) {
- return false;
- }
-
- // Layer is frequent if the earliest value in the window of most recent present times is
- // within threshold.
- const auto it = mElements.end() - FREQUENT_LAYER_WINDOW_SIZE;
- const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
- return *it >= threshold;
- }
-
- void clearHistory() { mElements.clear(); }
-
- private:
- std::deque<nsecs_t> mElements;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
- };
+ static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+ static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
+ std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
friend class LayerHistoryTest;
public:
- LayerInfo(float lowRefreshRate, float highRefreshRate);
+ // Holds information about the layer vote
+ struct LayerVote {
+ LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+ float fps = 0.0f;
+ Seamlessness seamlessness = Seamlessness::Default;
+ };
+
+ static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
+ static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+ sRefreshRateConfigs = &refreshRateConfigs;
+ }
+
+ LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote);
LayerInfo(const LayerInfo&) = delete;
LayerInfo& operator=(const LayerInfo&) = delete;
- // Records the last requested oresent time. It also stores information about when
+ // Records the last requested present time. It also stores information about when
// the layer was last updated. If the present time is farther in the future than the
// updated time, the updated time is the present time.
- void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+ void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+ bool pendingConfigChange);
- bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
- bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
+ // Sets an explicit layer vote. This usually comes directly from the application via
+ // ANativeWindow_setFrameRate API
+ void setLayerVote(LayerVote vote) { mLayerVote = vote; }
- float getRefreshRate(nsecs_t now) const {
- return isFrequent(now) ? mRefreshRateHistory.getRefreshRateAvg() : mLowRefreshRate;
- }
+ // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
+ // This is used for layers that called to setLayerVote() and then removed the vote, so that the
+ // layer can go back to whatever vote it had before the app voted for it.
+ void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+
+ // Resets the layer vote to its default.
+ void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+
+ LayerVote getRefreshRateVote(nsecs_t now);
// Return the last updated time. If the present time is farther in the future than the
// updated time, the updated time is the present time.
nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
- void clearHistory() {
- mRefreshRateHistory.clearHistory();
- mPresentTimeHistory.clearHistory();
+ // Returns a C string for tracing a vote
+ const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
+ void onLayerInactive(nsecs_t now) {
+ // Mark mFrameTimeValidSince to now to ignore all previous frame times.
+ // We are not deleting the old frame to keep track of whether we should treat the first
+ // buffer as Max as we don't know anything about this layer or Min as this layer is
+ // posting infrequent updates.
+ const auto timePoint = std::chrono::nanoseconds(now);
+ mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
+ mLastRefreshRate = {};
+ mRefreshRateHistory.clear();
+ }
+
+ void clearHistory(nsecs_t now) {
+ onLayerInactive(now);
+ mFrameTimes.clear();
}
private:
- const float mLowRefreshRate;
- const float mHighRefreshRate;
+ // Used to store the layer timestamps
+ struct FrameTimeData {
+ nsecs_t presetTime; // desiredPresentTime, if provided
+ nsecs_t queueTime; // buffer queue time
+ bool pendingConfigChange;
+ };
+
+ // Holds information about the calculated and reported refresh rate
+ struct RefreshRateHeuristicData {
+ // Rate calculated on the layer
+ float calculated = 0.0f;
+ // Last reported rate for LayerInfo::getRefreshRate()
+ float reported = 0.0f;
+ // Whether the last reported rate for LayerInfo::getRefreshRate()
+ // was due to animation or infrequent updates
+ bool animatingOrInfrequent = false;
+ };
+
+ // Class to store past calculated refresh rate and determine whether
+ // the refresh rate calculated is consistent with past values
+ class RefreshRateHistory {
+ public:
+ static constexpr auto HISTORY_SIZE = 90;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+ RefreshRateHistory(const std::string& name) : mName(name) {}
+
+ // Clears History
+ void clear();
+
+ // Adds a new refresh rate and returns true if it is consistent
+ bool add(float refreshRate, nsecs_t now);
+
+ private:
+ friend class LayerHistoryTest;
+
+ // Holds the refresh rate when it was calculated
+ struct RefreshRateData {
+ float refreshRate = 0.0f;
+ nsecs_t timestamp = 0;
+
+ bool operator<(const RefreshRateData& other) const {
+ return refreshRate < other.refreshRate;
+ }
+ };
+
+ // Holds tracing strings
+ struct HeuristicTraceTagData {
+ std::string min;
+ std::string max;
+ std::string consistent;
+ std::string average;
+ };
+
+ bool isConsistent() const;
+ HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+ const std::string mName;
+ mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+ std::deque<RefreshRateData> mRefreshRates;
+ static constexpr float MARGIN_FPS = 1.0;
+ };
+
+ bool isFrequent(nsecs_t now) const;
+ bool isAnimating(nsecs_t now) const;
+ bool hasEnoughDataForHeuristic() const;
+ std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+ std::optional<nsecs_t> calculateAverageFrameTime() const;
+ bool isFrameTimeValid(const FrameTimeData&) const;
+
+ const std::string mName;
+
+ // Used for sanitizing the heuristic data
+ const nsecs_t mHighRefreshRatePeriod;
+ LayerHistory::LayerVoteType mDefaultVote;
+
+ LayerVote mLayerVote;
nsecs_t mLastUpdatedTime = 0;
- nsecs_t mLastPresentTime = 0;
- RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
- PresentTimeHistory mPresentTimeHistory;
+
+ nsecs_t mLastAnimationTime = 0;
+
+ RefreshRateHeuristicData mLastRefreshRate;
+
+ std::deque<FrameTimeData> mFrameTimes;
+ std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
+ std::chrono::steady_clock::now();
+ static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+ RefreshRateHistory mRefreshRateHistory;
+
+ mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
+
+ // Shared for all LayerInfo instances
+ static const RefreshRateConfigs* sRefreshRateConfigs;
+ static bool sTraceEnabled;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
deleted file mode 100644
index 94e7e20..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// #define LOG_NDEBUG 0
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerInfoV2.h"
-
-#include <algorithm>
-#include <utility>
-
-#include <cutils/compiler.h>
-#include <cutils/trace.h>
-
-#undef LOG_TAG
-#define LOG_TAG "LayerInfoV2"
-
-namespace android::scheduler {
-
-const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
-bool LayerInfoV2::sTraceEnabled = false;
-
-LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote)
- : mName(name),
- mHighRefreshRatePeriod(highRefreshRatePeriod),
- mDefaultVote(defaultVote),
- mLayerVote({defaultVote, 0.0f}),
- mRefreshRateHistory(name) {}
-
-void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
- LayerUpdateType updateType, bool pendingConfigChange) {
- lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
-
- mLastUpdatedTime = std::max(lastPresentTime, now);
- switch (updateType) {
- case LayerUpdateType::AnimationTX:
- mLastAnimationTime = std::max(lastPresentTime, now);
- break;
- case LayerUpdateType::SetFrameRate:
- case LayerUpdateType::Buffer:
- FrameTimeData frameTime = {.presetTime = lastPresentTime,
- .queueTime = mLastUpdatedTime,
- .pendingConfigChange = pendingConfigChange};
- mFrameTimes.push_back(frameTime);
- if (mFrameTimes.size() > HISTORY_SIZE) {
- mFrameTimes.pop_front();
- }
- break;
- }
-}
-
-bool LayerInfoV2::isFrameTimeValid(const FrameTimeData& frameTime) const {
- return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
- mFrameTimeValidSince.time_since_epoch())
- .count();
-}
-
-bool LayerInfoV2::isFrequent(nsecs_t now) const {
- // If we know nothing about this layer we consider it as frequent as it might be the start
- // of an animation.
- if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
- return true;
- }
-
- // Find the first active frame
- auto it = mFrameTimes.begin();
- for (; it != mFrameTimes.end(); ++it) {
- if (it->queueTime >= getActiveLayerThreshold(now)) {
- break;
- }
- }
-
- const auto numFrames = std::distance(it, mFrameTimes.end());
- if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
- return false;
- }
-
- // Layer is considered frequent if the average frame rate is higher than the threshold
- const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
- return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
-}
-
-bool LayerInfoV2::isAnimating(nsecs_t now) const {
- return mLastAnimationTime >= getActiveLayerThreshold(now);
-}
-
-bool LayerInfoV2::hasEnoughDataForHeuristic() const {
- // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
- if (mFrameTimes.size() < 2) {
- ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
- return false;
- }
-
- if (!isFrameTimeValid(mFrameTimes.front())) {
- ALOGV("stale frames still captured");
- return false;
- }
-
- const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
- if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
- ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
- totalDuration / 1e9f);
- return false;
- }
-
- return true;
-}
-
-std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
- nsecs_t totalPresentTimeDeltas = 0;
- nsecs_t totalQueueTimeDeltas = 0;
- bool missingPresentTime = false;
- int numFrames = 0;
- for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
- // Ignore frames captured during a config change
- if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
- return std::nullopt;
- }
-
- totalQueueTimeDeltas +=
- std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
- numFrames++;
-
- if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
- missingPresentTime = true;
- // If there are no presentation timestamps and we haven't calculated
- // one in the past then we can't calculate the refresh rate
- if (mLastRefreshRate.reported == 0) {
- return std::nullopt;
- }
- continue;
- }
-
- totalPresentTimeDeltas +=
- std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
- }
-
- // Calculate the average frame time based on presentation timestamps. If those
- // doesn't exist, we look at the time the buffer was queued only. We can do that only if
- // we calculated a refresh rate based on presentation timestamps in the past. The reason
- // we look at the queue time is to handle cases where hwui attaches presentation timestamps
- // when implementing render ahead for specific refresh rates. When hwui no longer provides
- // presentation timestamps we look at the queue time to see if the current refresh rate still
- // matches the content.
-
- const auto averageFrameTime =
- static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
- numFrames;
- return static_cast<nsecs_t>(averageFrameTime);
-}
-
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
- static constexpr float MARGIN = 1.0f; // 1Hz
- if (!hasEnoughDataForHeuristic()) {
- ALOGV("Not enough data");
- return std::nullopt;
- }
-
- const auto averageFrameTime = calculateAverageFrameTime();
- if (averageFrameTime.has_value()) {
- const auto refreshRate = 1e9f / *averageFrameTime;
- const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
- if (refreshRateConsistent) {
- const auto knownRefreshRate =
- sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
-
- // To avoid oscillation, use the last calculated refresh rate if it is
- // close enough
- if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
- mLastRefreshRate.reported != knownRefreshRate) {
- mLastRefreshRate.calculated = refreshRate;
- mLastRefreshRate.reported = knownRefreshRate;
- }
-
- ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
- } else {
- ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
- }
- }
-
- return mLastRefreshRate.reported == 0 ? std::nullopt
- : std::make_optional(mLastRefreshRate.reported);
-}
-
-LayerInfoV2::LayerVote LayerInfoV2::getRefreshRateVote(nsecs_t now) {
- if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
- ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
- return mLayerVote;
- }
-
- if (isAnimating(now)) {
- ALOGV("%s is animating", mName.c_str());
- mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Max, 0};
- }
-
- if (!isFrequent(now)) {
- ALOGV("%s is infrequent", mName.c_str());
- mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Min, 0};
- }
-
- // If the layer was previously tagged as animating or infrequent, we clear
- // the history as it is likely the layer just changed its behavior
- // and we should not look at stale data
- if (mLastRefreshRate.animatingOrInfrequent) {
- clearHistory(now);
- }
-
- auto refreshRate = calculateRefreshRateIfPossible(now);
- if (refreshRate.has_value()) {
- ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
- return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
- }
-
- ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
- return {LayerHistory::LayerVoteType::Max, 0};
-}
-
-const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
- if (mTraceTags.count(type) == 0) {
- const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
- mTraceTags.emplace(type, tag);
- }
-
- return mTraceTags.at(type).c_str();
-}
-
-LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
-LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
- const std::string prefix = "LFPS ";
- const std::string suffix = "Heuristic ";
- return {.min = prefix + mName + suffix + "min",
- .max = prefix + mName + suffix + "max",
- .consistent = prefix + mName + suffix + "consistent",
- .average = prefix + mName + suffix + "average"};
-}
-
-void LayerInfoV2::RefreshRateHistory::clear() {
- mRefreshRates.clear();
-}
-
-bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
- mRefreshRates.push_back({refreshRate, now});
- while (mRefreshRates.size() >= HISTORY_SIZE ||
- now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
- mRefreshRates.pop_front();
- }
-
- if (CC_UNLIKELY(sTraceEnabled)) {
- if (!mHeuristicTraceTagData.has_value()) {
- mHeuristicTraceTagData = makeHeuristicTraceTagData();
- }
-
- ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
- }
-
- return isConsistent();
-}
-
-bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
- if (mRefreshRates.empty()) return true;
-
- const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
- const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
- const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
-
- if (CC_UNLIKELY(sTraceEnabled)) {
- if (!mHeuristicTraceTagData.has_value()) {
- mHeuristicTraceTagData = makeHeuristicTraceTagData();
- }
-
- ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
- ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
- ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
- }
-
- return consistent;
-}
-
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
deleted file mode 100644
index f94f4ab..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/Timers.h>
-
-#include <chrono>
-#include <deque>
-
-#include "LayerHistory.h"
-#include "RefreshRateConfigs.h"
-#include "Scheduler/Seamlessness.h"
-#include "SchedulerUtils.h"
-
-namespace android {
-
-class Layer;
-
-namespace scheduler {
-
-using namespace std::chrono_literals;
-
-// Maximum period between presents for a layer to be considered active.
-constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
-
-// Earliest present time for a layer to be considered active.
-constexpr nsecs_t getActiveLayerThreshold(nsecs_t now) {
- return now - MAX_ACTIVE_LAYER_PERIOD_NS.count();
-}
-
-// Stores history of present times and refresh rates for a layer.
-class LayerInfoV2 {
- using LayerUpdateType = LayerHistory::LayerUpdateType;
-
- // Layer is considered frequent if the earliest value in the window of most recent present times
- // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
- // favor of a low refresh rate.
- static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
- std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
-
- friend class LayerHistoryTestV2;
-
-public:
- // Holds information about the layer vote
- struct LayerVote {
- LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
- float fps = 0.0f;
- Seamlessness seamlessness = Seamlessness::Default;
- };
-
- static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
-
- static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
- sRefreshRateConfigs = &refreshRateConfigs;
- }
-
- LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote);
-
- LayerInfoV2(const LayerInfo&) = delete;
- LayerInfoV2& operator=(const LayerInfoV2&) = delete;
-
- // Records the last requested present time. It also stores information about when
- // the layer was last updated. If the present time is farther in the future than the
- // updated time, the updated time is the present time.
- void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
- bool pendingConfigChange);
-
- // Sets an explicit layer vote. This usually comes directly from the application via
- // ANativeWindow_setFrameRate API
- void setLayerVote(LayerVote vote) { mLayerVote = vote; }
-
- // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
- // This is used for layers that called to setLayerVote() and then removed the vote, so that the
- // layer can go back to whatever vote it had before the app voted for it.
- void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
-
- // Resets the layer vote to its default.
- void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
-
- LayerVote getRefreshRateVote(nsecs_t now);
-
- // Return the last updated time. If the present time is farther in the future than the
- // updated time, the updated time is the present time.
- nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
-
- // Returns a C string for tracing a vote
- const char* getTraceTag(LayerHistory::LayerVoteType type) const;
-
- void onLayerInactive(nsecs_t now) {
- // Mark mFrameTimeValidSince to now to ignore all previous frame times.
- // We are not deleting the old frame to keep track of whether we should treat the first
- // buffer as Max as we don't know anything about this layer or Min as this layer is
- // posting infrequent updates.
- const auto timePoint = std::chrono::nanoseconds(now);
- mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
- mLastRefreshRate = {};
- mRefreshRateHistory.clear();
- }
-
- void clearHistory(nsecs_t now) {
- onLayerInactive(now);
- mFrameTimes.clear();
- }
-
-private:
- // Used to store the layer timestamps
- struct FrameTimeData {
- nsecs_t presetTime; // desiredPresentTime, if provided
- nsecs_t queueTime; // buffer queue time
- bool pendingConfigChange;
- };
-
- // Holds information about the calculated and reported refresh rate
- struct RefreshRateHeuristicData {
- // Rate calculated on the layer
- float calculated = 0.0f;
- // Last reported rate for LayerInfoV2::getRefreshRate()
- float reported = 0.0f;
- // Whether the last reported rate for LayerInfoV2::getRefreshRate()
- // was due to animation or infrequent updates
- bool animatingOrInfrequent = false;
- };
-
- // Class to store past calculated refresh rate and determine whether
- // the refresh rate calculated is consistent with past values
- class RefreshRateHistory {
- public:
- static constexpr auto HISTORY_SIZE = 90;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
-
- RefreshRateHistory(const std::string& name) : mName(name) {}
-
- // Clears History
- void clear();
-
- // Adds a new refresh rate and returns true if it is consistent
- bool add(float refreshRate, nsecs_t now);
-
- private:
- friend class LayerHistoryTestV2;
-
- // Holds the refresh rate when it was calculated
- struct RefreshRateData {
- float refreshRate = 0.0f;
- nsecs_t timestamp = 0;
-
- bool operator<(const RefreshRateData& other) const {
- return refreshRate < other.refreshRate;
- }
- };
-
- // Holds tracing strings
- struct HeuristicTraceTagData {
- std::string min;
- std::string max;
- std::string consistent;
- std::string average;
- };
-
- bool isConsistent() const;
- HeuristicTraceTagData makeHeuristicTraceTagData() const;
-
- const std::string mName;
- mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
- std::deque<RefreshRateData> mRefreshRates;
- static constexpr float MARGIN_FPS = 1.0;
- };
-
- bool isFrequent(nsecs_t now) const;
- bool isAnimating(nsecs_t now) const;
- bool hasEnoughDataForHeuristic() const;
- std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
- std::optional<nsecs_t> calculateAverageFrameTime() const;
- bool isFrameTimeValid(const FrameTimeData&) const;
-
- const std::string mName;
-
- // Used for sanitizing the heuristic data
- const nsecs_t mHighRefreshRatePeriod;
- LayerHistory::LayerVoteType mDefaultVote;
-
- LayerVote mLayerVote;
-
- nsecs_t mLastUpdatedTime = 0;
-
- nsecs_t mLastAnimationTime = 0;
-
- RefreshRateHeuristicData mLastRefreshRate;
-
- std::deque<FrameTimeData> mFrameTimes;
- std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
- std::chrono::steady_clock::now();
- static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
-
- RefreshRateHistory mRefreshRateHistory;
-
- mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
-
- // Shared for all LayerInfo instances
- static const RefreshRateConfigs* sRefreshRateConfigs;
- static bool sTraceEnabled;
-};
-
-} // namespace scheduler
-} // namespace android
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index b14e482..83fa20e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -69,60 +69,6 @@
primaryRange.max, appRequestRange.min, appRequestRange.max);
}
-const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
- const std::vector<LayerRequirement>& layers) const {
- std::lock_guard lock(mLock);
- int contentFramerate = 0;
- int explicitContentFramerate = 0;
- for (const auto& layer : layers) {
- const auto desiredRefreshRateRound = round<int>(layer.desiredRefreshRate);
- if (layer.vote == LayerVoteType::ExplicitDefault ||
- layer.vote == LayerVoteType::ExplicitExactOrMultiple) {
- if (desiredRefreshRateRound > explicitContentFramerate) {
- explicitContentFramerate = desiredRefreshRateRound;
- }
- } else {
- if (desiredRefreshRateRound > contentFramerate) {
- contentFramerate = desiredRefreshRateRound;
- }
- }
- }
-
- if (explicitContentFramerate != 0) {
- contentFramerate = explicitContentFramerate;
- } else if (contentFramerate == 0) {
- contentFramerate = round<int>(mMaxSupportedRefreshRate->getFps());
- }
- ATRACE_INT("ContentFPS", contentFramerate);
-
- // Find the appropriate refresh rate with minimal error
- auto iter = min_element(mPrimaryRefreshRates.cbegin(), mPrimaryRefreshRates.cend(),
- [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
- return std::abs(lhs->fps - contentFramerate) <
- std::abs(rhs->fps - contentFramerate);
- });
-
- // Some content aligns better on higher refresh rate. For example for 45fps we should choose
- // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
- // align well with both
- const RefreshRate* bestSoFar = *iter;
- constexpr float MARGIN = 0.05f;
- float ratio = (*iter)->fps / contentFramerate;
- if (std::abs(std::round(ratio) - ratio) > MARGIN) {
- while (iter != mPrimaryRefreshRates.cend()) {
- ratio = (*iter)->fps / contentFramerate;
-
- if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
- bestSoFar = *iter;
- break;
- }
- ++iter;
- }
- }
-
- return *bestSoFar;
-}
-
std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
nsecs_t displayPeriod) const {
auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index e84091e..6e0c0d3 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -232,17 +232,14 @@
bool operator==(const LayerRequirement& other) const {
return name == other.name && vote == other.vote &&
- desiredRefreshRate == other.desiredRefreshRate && weight == other.weight &&
+ desiredRefreshRate == other.desiredRefreshRate &&
+ seamlessness == other.seamlessness && weight == other.weight &&
focused == other.focused;
}
bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
};
- // Returns the refresh rate that fits best to the given layers.
- const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
- EXCLUDES(mLock);
-
// Global state describing signals that affect refresh rate choice.
struct GlobalSignals {
// Whether the user touched the screen recently. Used to apply touch boost.
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f6d712e..52bf483 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -89,9 +89,8 @@
timerSlack.count(), vsyncMoveThreshold.count());
}
-const char* toContentDetectionString(bool useContentDetection, bool useContentDetectionV2) {
- if (!useContentDetection) return "off";
- return useContentDetectionV2 ? "V2" : "V1";
+const char* toContentDetectionString(bool useContentDetection) {
+ return useContentDetection ? "on" : "off";
}
} // namespace
@@ -119,14 +118,13 @@
Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: Scheduler(configs, callback,
{.supportKernelTimer = sysprop::support_kernel_idle_timer(false),
- .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false),
- .useContentDetectionV2 =
- base::GetBoolProperty("debug.sf.use_content_detection_v2"s, true)}) {}
+ .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
+}
Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
Options options)
: Scheduler(createVsyncSchedule(options.supportKernelTimer), configs, callback,
- createLayerHistory(configs, options.useContentDetectionV2), options) {
+ createLayerHistory(configs), options) {
using namespace sysprop;
const int setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
@@ -195,14 +193,10 @@
}
std::unique_ptr<LayerHistory> Scheduler::createLayerHistory(
- const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2) {
+ const scheduler::RefreshRateConfigs& configs) {
if (!configs.canSwitch()) return nullptr;
- if (useContentDetectionV2) {
- return std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
- }
-
- return std::make_unique<scheduler::impl::LayerHistory>();
+ return std::make_unique<scheduler::LayerHistory>(configs);
}
std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
@@ -528,14 +522,6 @@
// still need to be registered.
mLayerHistory->registerLayer(layer, minFps, maxFps,
scheduler::LayerHistory::LayerVoteType::Max);
- } else if (!mOptions.useContentDetectionV2) {
- // In V1 of content detection, all layers are registered as Heuristic (unless it's
- // wallpaper).
- const auto highFps =
- layer->getWindowType() == InputWindowInfo::Type::WALLPAPER ? minFps : maxFps;
-
- mLayerHistory->registerLayer(layer, minFps, highFps,
- scheduler::LayerHistory::LayerVoteType::Heuristic);
} else {
if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
// Running Wallpaper at Min is considered as part of content detection.
@@ -574,8 +560,6 @@
return;
}
mFeatures.contentRequirements = summary;
- mFeatures.contentDetectionV1 =
- !summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
@@ -682,8 +666,7 @@
StringAppendF(&result, "+ Touch timer: %s\n",
mTouchTimer ? mTouchTimer->dump().c_str() : "off");
StringAppendF(&result, "+ Content detection: %s %s\n\n",
- toContentDetectionString(mOptions.useContentDetection,
- mOptions.useContentDetectionV2),
+ toContentDetectionString(mOptions.useContentDetection),
mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
}
@@ -740,29 +723,6 @@
const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
const bool idle = mIdleTimer && mFeatures.idleTimer == TimerState::Expired;
- if (!mOptions.useContentDetectionV2) {
- // As long as touch is active we want to be in performance mode.
- if (touchActive) {
- return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
- }
-
- // If timer has expired as it means there is no new content on the screen.
- if (idle) {
- if (consideredSignals) consideredSignals->idle = true;
- return mRefreshRateConfigs.getMinRefreshRateByPolicy().getConfigId();
- }
-
- // If content detection is off we choose performance as we don't know the content fps.
- if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
- // NOTE: V1 always calls this, but this is not a default behavior for V2.
- return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
- }
-
- // Content detection is on, find the appropriate refresh rate with minimal error
- return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements)
- .getConfigId();
- }
-
return mRefreshRateConfigs
.getBestRefreshRate(mFeatures.contentRequirements, {.touch = touchActive, .idle = idle},
consideredSignals)
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 76e8f57..f16e1f9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -183,8 +183,6 @@
bool supportKernelTimer;
// Whether to use content detection at all.
bool useContentDetection;
- // Whether to use improved content detection.
- bool useContentDetectionV2;
};
struct VsyncSchedule {
@@ -201,8 +199,7 @@
std::unique_ptr<LayerHistory>, Options);
static VsyncSchedule createVsyncSchedule(bool supportKernelIdleTimer);
- static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&,
- bool useContentDetectionV2);
+ static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&);
// Create a connection on the given EventThread.
ConnectionHandle createConnection(std::unique_ptr<EventThread>);
@@ -270,7 +267,6 @@
std::mutex mFeatureStateLock;
struct {
- ContentDetectionState contentDetectionV1 = ContentDetectionState::Off;
TimerState idleTimer = TimerState::Reset;
TouchState touch = TouchState::Inactive;
TimerState displayPowerTimer = TimerState::Expired;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 45f389b..95c9982 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -331,7 +331,7 @@
mInterceptor(mFactory.createSurfaceInterceptor()),
mTimeStats(std::make_shared<impl::TimeStats>()),
mFrameTracer(mFactory.createFrameTracer()),
- mFrameTimeline(std::make_unique<frametimeline::impl::FrameTimeline>(mTimeStats)),
+ mFrameTimeline(mFactory.createFrameTimeline(mTimeStats)),
mEventQueue(mFactory.createMessageQueue()),
mCompositionEngine(mFactory.createCompositionEngine()),
mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
@@ -621,6 +621,7 @@
mFrameTracer->initialize();
mTimeStats->onBootFinished();
+ mFrameTimeline->onBootFinished();
// wait patiently for the window manager death
const String16 name("window");
@@ -2110,7 +2111,7 @@
ALOGV("postComposition");
nsecs_t dequeueReadyTime = systemTime();
- for (auto layer : mLayersWithQueuedFrames) {
+ for (const auto& layer : mLayersWithQueuedFrames) {
layer->releasePendingBuffer(dequeueReadyTime);
}
@@ -3055,7 +3056,7 @@
// writes to Layer current state. See also b/119481871
Mutex::Autolock lock(mStateLock);
- for (auto& layer : mLayersWithQueuedFrames) {
+ for (const auto& layer : mLayersWithQueuedFrames) {
if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
mLayersPendingRefresh.push_back(layer);
}
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a909ad8..0509247 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1091,9 +1091,8 @@
bool mAnimCompositionPending = false;
// Tracks layers that have pending frames which are candidates for being
- // latched. Because this contains a set of raw layer pointers, can only be
- // mutated on the main thread.
- std::unordered_set<Layer*> mLayersWithQueuedFrames;
+ // latched.
+ std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> mLayersWithQueuedFrames;
// Tracks layers that need to update a display's dirty region.
std::vector<sp<Layer>> mLayersPendingRefresh;
std::array<sp<Fence>, 2> mPreviousPresentFences = {Fence::NO_FENCE, Fence::NO_FENCE};
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index a93f5f6..bc487ac 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -134,6 +134,12 @@
std::unique_ptr<FrameTracer> DefaultFactory::createFrameTracer() {
return std::make_unique<FrameTracer>();
}
+
+std::unique_ptr<frametimeline::FrameTimeline> DefaultFactory::createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats) {
+ return std::make_unique<frametimeline::impl::FrameTimeline>(timeStats);
+}
+
} // namespace android::surfaceflinger
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 90032d4..fc45fa9 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -55,6 +55,8 @@
sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) override;
sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) override;
std::unique_ptr<FrameTracer> createFrameTracer() override;
+ std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats) override;
};
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 69e9c3c..deb1b52 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -45,6 +45,7 @@
class StartPropertySetThread;
class SurfaceFlinger;
class SurfaceInterceptor;
+class TimeStats;
struct DisplayDeviceCreationArgs;
struct ISchedulerCallback;
@@ -60,6 +61,10 @@
class RefreshRateConfigs;
} // namespace scheduler
+namespace frametimeline {
+class FrameTimeline;
+} // namespace frametimeline
+
namespace surfaceflinger {
class NativeWindowSurface;
@@ -102,6 +107,8 @@
virtual sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) = 0;
virtual sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) = 0;
virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
+ virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats) = 0;
protected:
~Factory() = default;
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 28a3a81..2405884 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -681,21 +681,21 @@
t.jankPayload.totalFrames++;
static const constexpr int32_t kValidJankyReason =
- TimeStats::JankType::SurfaceFlingerDeadlineMissed |
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed |
- TimeStats::JankType::AppDeadlineMissed | TimeStats::JankType::Display;
+ JankType::SurfaceFlingerDeadlineMissed |
+ JankType::SurfaceFlingerGpuDeadlineMissed |
+ JankType::AppDeadlineMissed | JankType::Display;
if (reasons & kValidJankyReason) {
t.jankPayload.totalJankyFrames++;
- if ((reasons & TimeStats::JankType::SurfaceFlingerDeadlineMissed) != 0) {
+ if ((reasons & JankType::SurfaceFlingerDeadlineMissed) != 0) {
t.jankPayload.totalSFLongCpu++;
}
- if ((reasons & TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed) != 0) {
+ if ((reasons & JankType::SurfaceFlingerGpuDeadlineMissed) != 0) {
t.jankPayload.totalSFLongGpu++;
}
- if ((reasons & TimeStats::JankType::Display) != 0) {
+ if ((reasons & JankType::Display) != 0) {
t.jankPayload.totalSFUnattributed++;
}
- if ((reasons & TimeStats::JankType::AppDeadlineMissed) != 0) {
+ if ((reasons & JankType::AppDeadlineMissed) != 0) {
t.jankPayload.totalAppUnattributed++;
}
}
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 4fa0a02..a83b2bf 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -31,6 +31,7 @@
#include <statslog.h>
#include <timestatsproto/TimeStatsHelper.h>
#include <timestatsproto/TimeStatsProtoHeader.h>
+#include <gui/JankInfo.h>
#include <ui/FenceTime.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -110,26 +111,6 @@
virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
const std::shared_ptr<FenceTime>& presentFence) = 0;
- // Subset of jank metadata tracked by FrameTimeline for the purpose of funneling to telemetry.
- enum JankType {
- // No Jank
- None = 0x0,
- // Jank not related to SurfaceFlinger or the App
- Display = 0x1,
- // SF took too long on the CPU
- SurfaceFlingerDeadlineMissed = 0x2,
- // SF took too long on the GPU
- SurfaceFlingerGpuDeadlineMissed = 0x4,
- // Either App or GPU took too long on the frame
- AppDeadlineMissed = 0x8,
- // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
- // jank
- // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
- PredictionExpired = 0x10,
- // Latching a buffer early might cause an early present of the frame
- SurfaceFlingerEarlyLatch = 0x20,
- };
-
// Increments janky frames, tracked globally. Because FrameTimeline is the infrastructure
// responsible for computing jank in the system, this is expected to be called from
// FrameTimeline, rather than directly from SurfaceFlinger or individual layers. If there are no
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 18f3745..871222c 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -48,7 +48,6 @@
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
- "LayerHistoryTestV2.cpp",
"LayerMetadataTest.cpp",
"MessageQueueTest.cpp",
"PromiseTest.cpp",
@@ -84,6 +83,7 @@
"mock/DisplayHardware/MockDisplay.cpp",
"mock/DisplayHardware/MockPowerAdvisor.cpp",
"mock/MockEventThread.cpp",
+ "mock/MockFrameTimeline.cpp",
"mock/MockFrameTracer.cpp",
"mock/MockMessageQueue.cpp",
"mock/MockNativeWindowSurface.cpp",
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 03c6f70..411e780 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -22,10 +22,16 @@
#include <FrameTimeline/FrameTimeline.h>
#include <gtest/gtest.h>
#include <log/log.h>
+#include <perfetto/trace/trace.pb.h>
#include <cinttypes>
using namespace std::chrono_literals;
using testing::Contains;
+using FrameTimelineEvent = perfetto::protos::FrameTimelineEvent;
+using ProtoDisplayFrame = perfetto::protos::FrameTimelineEvent_DisplayFrame;
+using ProtoSurfaceFrame = perfetto::protos::FrameTimelineEvent_SurfaceFrame;
+using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
+using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
MATCHER_P(HasBit, bit, "") {
return (arg & bit) != 0;
@@ -47,14 +53,56 @@
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
+ static void SetUpTestSuite() {
+ // Need to initialize tracing in process for testing, and only once per test suite.
+ perfetto::TracingInitArgs args;
+ args.backends = perfetto::kInProcessBackend;
+ perfetto::Tracing::Initialize(args);
+ }
+
void SetUp() override {
mTimeStats = std::make_shared<mock::TimeStats>();
mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats);
+ mFrameTimeline->registerDataSource();
mTokenManager = &mFrameTimeline->mTokenManager;
maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
maxTokenRetentionTime = mTokenManager->kMaxRetentionTime;
}
+ // Each tracing session can be used for a single block of Start -> Stop.
+ static std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest() {
+ perfetto::TraceConfig cfg;
+ cfg.set_duration_ms(500);
+ cfg.add_buffers()->set_size_kb(1024);
+ auto* ds_cfg = cfg.add_data_sources()->mutable_config();
+ ds_cfg->set_name(impl::FrameTimeline::kFrameTimelineDataSource);
+
+ auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+ tracingSession->Setup(cfg);
+ return tracingSession;
+ }
+
+ std::vector<perfetto::protos::TracePacket> readFrameTimelinePacketsBlocking(
+ perfetto::TracingSession* tracingSession) {
+ std::vector<char> raw_trace = tracingSession->ReadTraceBlocking();
+ perfetto::protos::Trace trace;
+ EXPECT_TRUE(trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())));
+
+ std::vector<perfetto::protos::TracePacket> packets;
+ for (const auto& packet : trace.packet()) {
+ if (!packet.has_frame_timeline_event()) {
+ continue;
+ }
+ packets.emplace_back(packet);
+ }
+ return packets;
+ }
+
+ void addEmptyDisplayFrame() {
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ mFrameTimeline->setSfPresent(2500, presentFence1);
+ }
+
void flushTokens(nsecs_t flushTime) {
std::lock_guard<std::mutex> lock(mTokenManager->mMutex);
mTokenManager->flushTokens(flushTime);
@@ -331,9 +379,9 @@
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(sUidOne, sLayerNameOne,
- HasBit(TimeStats::JankType::SurfaceFlingerDeadlineMissed)));
+ HasBit(JankType::SurfaceFlingerDeadlineMissed)));
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(HasBit(TimeStats::JankType::SurfaceFlingerDeadlineMissed)));
+ incrementJankyFrames(HasBit(JankType::SurfaceFlingerDeadlineMissed)));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
@@ -359,8 +407,8 @@
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(sUidOne, sLayerNameOne, HasBit(TimeStats::JankType::Display)));
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(TimeStats::JankType::Display)));
+ incrementJankyFrames(sUidOne, sLayerNameOne, HasBit(JankType::Display)));
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(JankType::Display)));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
@@ -386,8 +434,8 @@
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(sUidOne, sLayerNameOne,
- HasBit(TimeStats::JankType::AppDeadlineMissed)));
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(TimeStats::JankType::AppDeadlineMissed)));
+ HasBit(JankType::AppDeadlineMissed)));
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(HasBit(JankType::AppDeadlineMissed)));
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
{std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count(),
@@ -413,4 +461,308 @@
presentFence1);
}
+/*
+ * Tracing Tests
+ *
+ * Trace packets are flushed all the way only when the next packet is traced.
+ * For example: trace<Display/Surface>Frame() will create a TracePacket but not flush it. Only when
+ * another TracePacket is created, the previous one is guaranteed to be flushed. The following tests
+ * will have additional empty frames created for this reason.
+ */
+TEST_F(FrameTimelineTest, tracing_noPacketsSentWithoutTraceStart) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+ sLayerNameOne, token1);
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(token1, 20);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->setSfPresent(25, presentFence1);
+ presentFence1->signalForTest(30);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, tracing_sanityTest) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+ sLayerNameOne, token1);
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(token2, 20);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
+ SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfPresent(25, presentFence1);
+ presentFence1->signalForTest(30);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ // The SurfaceFrame packet from the first frame is emitted, but not flushed yet. Emitting a new
+ // packet will flush it. To emit a new packet, we'll need to call flushPendingPresentFences()
+ // again, which is done by setSfPresent().
+ addEmptyDisplayFrame();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has two packets - DisplayFrame and a SurfaceFrame.
+ // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+ // flushed through traced, so this is not counted.
+ EXPECT_EQ(packets.size(), 2);
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_invalidTokenDoesNotEmitTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(-1, 20);
+ mFrameTimeline->setSfPresent(25, presentFence1);
+ presentFence1->signalForTest(30);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(token1, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(60);
+
+ addEmptyDisplayFrame();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has no packets.
+ // Display Frame 2 has one packet - DisplayFrame. However, this packet has
+ // been emitted but not flushed through traced, so this is not counted.
+ EXPECT_EQ(packets.size(), 0);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_invalidTokenDoesNotEmitTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+ auto surfaceFrame1 = mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+ sLayerNameOne, std::nullopt);
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(token1, 20);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1), SurfaceFrame::PresentState::Dropped);
+ mFrameTimeline->setSfPresent(25, presentFence1);
+ presentFence1->signalForTest(30);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(token2, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(60);
+
+ addEmptyDisplayFrame();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has one packet - DisplayFrame (SurfaceFrame shouldn't be traced since it has
+ // an invalid token).
+ // Display Frame 2 has one packet - DisplayFrame. However, this packet has
+ // been emitted but not flushed through traced, so this is not counted.
+ EXPECT_EQ(packets.size(), 1);
+}
+
+void validateDisplayFrameEvent(const ProtoDisplayFrame& received, const ProtoDisplayFrame& source) {
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_present_type());
+ EXPECT_EQ(received.present_type(), source.present_type());
+ ASSERT_TRUE(received.has_on_time_finish());
+ EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+ ASSERT_TRUE(received.has_gpu_composition());
+ EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
+ ASSERT_TRUE(received.has_jank_type());
+ EXPECT_EQ(received.jank_type(), source.jank_type());
+
+ ASSERT_TRUE(received.has_expected_start_ns());
+ EXPECT_EQ(received.expected_start_ns(), source.expected_start_ns());
+ ASSERT_TRUE(received.has_expected_end_ns());
+ EXPECT_EQ(received.expected_end_ns(), source.expected_end_ns());
+
+ ASSERT_TRUE(received.has_actual_start_ns());
+ EXPECT_EQ(received.actual_start_ns(), source.actual_start_ns());
+ ASSERT_TRUE(received.has_actual_end_ns());
+ EXPECT_EQ(received.actual_end_ns(), source.actual_end_ns());
+}
+
+void validateSurfaceFrameEvent(const ProtoSurfaceFrame& received, const ProtoSurfaceFrame& source) {
+ ASSERT_TRUE(received.has_token());
+ EXPECT_EQ(received.token(), source.token());
+
+ ASSERT_TRUE(received.has_display_frame_token());
+ EXPECT_EQ(received.display_frame_token(), source.display_frame_token());
+
+ ASSERT_TRUE(received.has_present_type());
+ EXPECT_EQ(received.present_type(), source.present_type());
+ ASSERT_TRUE(received.has_on_time_finish());
+ EXPECT_EQ(received.on_time_finish(), source.on_time_finish());
+ ASSERT_TRUE(received.has_gpu_composition());
+ EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
+ ASSERT_TRUE(received.has_jank_type());
+ EXPECT_EQ(received.jank_type(), source.jank_type());
+
+ ASSERT_TRUE(received.has_expected_start_ns());
+ EXPECT_EQ(received.expected_start_ns(), source.expected_start_ns());
+ ASSERT_TRUE(received.has_expected_end_ns());
+ EXPECT_EQ(received.expected_end_ns(), source.expected_end_ns());
+
+ ASSERT_TRUE(received.has_actual_start_ns());
+ EXPECT_EQ(received.actual_start_ns(), source.actual_start_ns());
+ ASSERT_TRUE(received.has_actual_end_ns());
+ EXPECT_EQ(received.actual_end_ns(), source.actual_end_ns());
+
+ ASSERT_TRUE(received.has_layer_name());
+ EXPECT_EQ(received.layer_name(), source.layer_name());
+ ASSERT_TRUE(received.has_pid());
+ EXPECT_EQ(received.pid(), source.pid());
+}
+
+TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
+ int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ presentFence1->signalForTest(31);
+
+ ProtoDisplayFrame protoDisplayFrame;
+ protoDisplayFrame.set_token(displayFrameToken1);
+ protoDisplayFrame.set_present_type(ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+ protoDisplayFrame.set_on_time_finish(true);
+ protoDisplayFrame.set_gpu_composition(false);
+ protoDisplayFrame.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+ protoDisplayFrame.set_expected_start_ns(10);
+ protoDisplayFrame.set_expected_end_ns(25);
+ protoDisplayFrame.set_actual_start_ns(20);
+ protoDisplayFrame.set_actual_end_ns(26);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ addEmptyDisplayFrame();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has one packet - DisplayFrame.
+ // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+ // flushed through traced, so this is not counted.
+ EXPECT_EQ(packets.size(), 1);
+
+ const auto& packet = packets[0];
+ ASSERT_TRUE(packet.has_timestamp());
+ ASSERT_TRUE(packet.has_frame_timeline_event());
+
+ const auto& event = packet.frame_timeline_event();
+ ASSERT_TRUE(event.has_display_frame());
+ ASSERT_FALSE(event.has_surface_frame());
+ const auto& displayFrameEvent = event.display_frame();
+ validateDisplayFrameEvent(displayFrameEvent, protoDisplayFrame);
+}
+
+TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
+ auto tracingSession = getTracingSessionForTest();
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ tracingSession->StartBlocking();
+ int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 25, 40});
+ int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({30, 35, 40});
+ int64_t displayFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(sPidOne, sUidOne, sLayerNameOne,
+ sLayerNameOne, surfaceFrameToken);
+ surfaceFrame1->setActualStartTime(0);
+ surfaceFrame1->setActualQueueTime(15);
+ surfaceFrame1->setAcquireFenceTime(20);
+
+ ProtoSurfaceFrame protoSurfaceFrame;
+ protoSurfaceFrame.set_token(surfaceFrameToken);
+ protoSurfaceFrame.set_display_frame_token(displayFrameToken1);
+ protoSurfaceFrame.set_present_type(ProtoPresentType(FrameTimelineEvent::PRESENT_ON_TIME));
+ protoSurfaceFrame.set_on_time_finish(true);
+ protoSurfaceFrame.set_gpu_composition(false);
+ protoSurfaceFrame.set_jank_type(ProtoJankType(FrameTimelineEvent::JANK_NONE));
+ protoSurfaceFrame.set_expected_start_ns(10);
+ protoSurfaceFrame.set_expected_end_ns(25);
+ protoSurfaceFrame.set_actual_start_ns(0);
+ protoSurfaceFrame.set_actual_end_ns(20);
+ protoSurfaceFrame.set_layer_name(sLayerNameOne);
+ protoSurfaceFrame.set_pid(sPidOne);
+
+ // Set up the display frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame1),
+ SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->setSfPresent(26, presentFence1);
+ presentFence1->signalForTest(31);
+
+ // Trigger a flushPresentFence (which will call trace function) by calling setSfPresent for the
+ // next frame
+ mFrameTimeline->setSfWakeUp(displayFrameToken2, 50);
+ mFrameTimeline->setSfPresent(55, presentFence2);
+ presentFence2->signalForTest(55);
+
+ addEmptyDisplayFrame();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // Display Frame 1 has one packet - DisplayFrame and a SurfaceFrame.
+ // Display Frame 2 has one packet - DisplayFrame. However, this packet has been emitted but not
+ // flushed through traced, so this is not counted.
+ EXPECT_EQ(packets.size(), 2);
+
+ const auto& packet = packets[1];
+ ASSERT_TRUE(packet.has_timestamp());
+ ASSERT_TRUE(packet.has_frame_timeline_event());
+
+ const auto& event = packet.frame_timeline_event();
+ ASSERT_TRUE(!event.has_display_frame());
+ ASSERT_TRUE(event.has_surface_frame());
+ const auto& surfaceFrameEvent = event.surface_frame();
+ validateSurfaceFrameEvent(surfaceFrameEvent, protoSurfaceFrame);
+}
+
} // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 0fbe8ec..fbb4637 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
#undef LOG_TAG
#define LOG_TAG "LayerHistoryTest"
+#include <Layer.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
@@ -39,32 +36,74 @@
class LayerHistoryTest : public testing::Test {
protected:
- static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::PresentTimeHistory::HISTORY_SIZE;
+ static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::MAX_FREQUENT_LAYER_PERIOD_NS;
+ static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::FREQUENT_LAYER_WINDOW_SIZE;
+ static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
+ static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+ LayerInfo::RefreshRateHistory::HISTORY_DURATION;
static constexpr float LO_FPS = 30.f;
- static constexpr nsecs_t LO_FPS_PERIOD = 33'333'333;
+ static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
static constexpr float HI_FPS = 90.f;
- static constexpr nsecs_t HI_FPS_PERIOD = 11'111'111;
+ static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
- impl::LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
- const impl::LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
+ LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
+ const LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
size_t layerCount() const { return mScheduler->layerHistorySize(); }
size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
- size_t frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(), infos.begin() + history().mActiveLayersEnd,
+ return std::count_if(infos.begin(),
+ infos.begin() + static_cast<long>(history().mActiveLayersEnd),
[now](const auto& pair) { return pair.second->isFrequent(now); });
}
+ auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mLayerInfos;
+ return std::count_if(infos.begin(),
+ infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+ [now](const auto& pair) { return pair.second->isAnimating(now); });
+ }
+
+ void setDefaultLayerVote(Layer* layer,
+ LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+ for (auto& [weak, info] : history().mLayerInfos) {
+ if (auto strong = weak.promote(); strong && strong.get() == layer) {
+ info->setDefaultLayerVote(vote);
+ return;
+ }
+ }
+ }
+
auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+ auto createLayer(std::string name) {
+ return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
+ }
+
+ void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
+ float desiredRefreshRate, int numFrames) {
+ const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+ LayerHistory::Summary summary;
+ for (int i = 0; i < numFrames; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += framePeriod;
+
+ summary = history().summarize(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+ << "Frame rate is " << frameRate;
+ }
Hwc2::mock::Display mDisplay;
RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
@@ -78,14 +117,10 @@
HwcConfigIndexType(0)};
mock::NoOpSchedulerCallback mSchedulerCallback;
- static constexpr bool kUseContentDetectionV2 = false;
- TestableScheduler* const mScheduler =
- new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
+ TestableScheduler* const mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback);
TestableSurfaceFlinger mFlinger;
-
- const nsecs_t mTime = systemTime();
};
namespace {
@@ -93,83 +128,259 @@
TEST_F(LayerHistoryTest, oneLayer) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
- // no layers are returned if no layers are active.
- ASSERT_TRUE(history().summarize(mTime).empty());
+ const nsecs_t time = systemTime();
+
+ // No layers returned if no layers are active.
+ EXPECT_TRUE(history().summarize(time).empty());
EXPECT_EQ(0, activeLayerCount());
- // no layers are returned if active layers have insufficient history.
+ // Max returned if active layers have insufficient history.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_TRUE(history().summarize(mTime).empty());
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
}
- // High FPS is returned once enough history has been recorded.
+ // Max is returned since we have enough history but there is no timestamp votes.
for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(mTime).size());
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
}
}
+TEST_F(LayerHistoryTest, oneInvisibleLayer) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ auto summary = history().summarize(time);
+ ASSERT_EQ(1, history().summarize(time).size());
+ // Layer is still considered inactive so we expect to get Min
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+ summary = history().summarize(time);
+ EXPECT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+}
+
TEST_F(LayerHistoryTest, explicitTimestamp) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
- nsecs_t time = mTime;
+ nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
- ASSERT_EQ(1, history().summarize(mTime).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, oneLayerNoVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMinVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMaxVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += LO_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVote) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
TEST_F(LayerHistoryTest, multipleLayers) {
auto layer1 = createLayer();
auto layer2 = createLayer();
auto layer3 = createLayer();
EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer1, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer2, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer3, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
- nsecs_t time = mTime;
+
+ nsecs_t time = systemTime();
EXPECT_EQ(3, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
+ LayerHistory::Summary summary;
+
// layer1 is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
@@ -177,26 +388,30 @@
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
// layer1 is still active but infrequent.
history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer1 is no longer active.
// layer2 is frequent and has low refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -210,26 +425,36 @@
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer3 becomes recently active.
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ summary = history().summarize(time);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer1 expires.
layer1.clear();
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ summary = history().summarize(time);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, layerCount());
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -239,42 +464,290 @@
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer2 expires.
layer2.clear();
- ASSERT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
// layer3 becomes active and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, layerCount());
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer3 expires.
layer3.clear();
- ASSERT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(0, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, inactiveLayers) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ // the very first updates makes the layer frequent
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // advance the time for the previous frame to be inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+ // Now event if we post a quick few frame we should stay infrequent
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ }
+
+ // More quick frames will get us to frequent again
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
+ auto explicitVisiblelayer = createLayer();
+ auto explicitInvisiblelayer = createLayer();
+
+ EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ nsecs_t time = systemTime();
+
+ // Post a buffer to the layers to make them active
+ history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitInvisiblelayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+
+ EXPECT_EQ(2, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(2, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // another update with the same cadence keep in infrequent
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // an update as animation will immediately vote for Max
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+ for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+ recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ }
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60_30Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+class LayerHistoryTestParameterized : public LayerHistoryTest,
+ public testing::WithParamInterface<std::chrono::nanoseconds> {
+};
+
+TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
+ std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+ auto heuristicLayer = createLayer("HeuristicLayer");
+
+ EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ auto infrequentLayer = createLayer("InfrequentLayer");
+ EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ const nsecs_t startTime = systemTime();
+
+ const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+ history().record(heuristicLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(infrequentLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+
+ nsecs_t time = startTime;
+ nsecs_t lastInfrequentUpdate = startTime;
+ const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+ int infrequentLayerUpdates = 0;
+ while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+ time += heuristicUpdateDelta.count();
+ history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+
+ if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+ ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
+ totalInfrequentLayerUpdates);
+ lastInfrequentUpdate = time;
+ history().record(infrequentLayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ infrequentLayerUpdates++;
+ }
+
+ if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+ ASSERT_NE(0, history().summarize(time).size());
+ ASSERT_GE(2, history().summarize(time).size());
+
+ bool max = false;
+ bool min = false;
+ float heuristic = 0;
+ for (const auto& layer : history().summarize(time)) {
+ if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+ heuristic = layer.desiredRefreshRate;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+ max = true;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+ min = true;
+ }
+ }
+
+ if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+ EXPECT_FLOAT_EQ(24.0f, heuristic);
+ EXPECT_FALSE(max);
+ if (history().summarize(time).size() == 2) {
+ EXPECT_TRUE(min);
+ }
+ }
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestParameterized,
+ ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
} // namespace
} // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
deleted file mode 100644
index 7bfec9a..0000000
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryTestV2"
-
-#include <Layer.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#include "Scheduler/LayerHistory.h"
-#include "Scheduler/LayerInfoV2.h"
-#include "TestableScheduler.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/MockLayer.h"
-#include "mock/MockSchedulerCallback.h"
-
-using testing::_;
-using testing::Return;
-
-namespace android::scheduler {
-
-class LayerHistoryTestV2 : public testing::Test {
-protected:
- static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
- static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
- static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
- static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
- LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
-
- static constexpr float LO_FPS = 30.f;
- static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
-
- static constexpr float HI_FPS = 90.f;
- static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
-
- LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
-
- void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
-
- impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
- const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
-
- size_t layerCount() const { return mScheduler->layerHistorySize(); }
- size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
-
- auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
- const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(),
- infos.begin() + static_cast<long>(history().mActiveLayersEnd),
- [now](const auto& pair) { return pair.second->isFrequent(now); });
- }
-
- auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
- const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(),
- infos.begin() + static_cast<long>(history().mActiveLayersEnd),
- [now](const auto& pair) { return pair.second->isAnimating(now); });
- }
-
- void setDefaultLayerVote(Layer* layer,
- LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
- for (auto& [weak, info] : history().mLayerInfos) {
- if (auto strong = weak.promote(); strong && strong.get() == layer) {
- info->setDefaultLayerVote(vote);
- return;
- }
- }
- }
-
- auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
- auto createLayer(std::string name) {
- return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
- }
-
- void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
- float desiredRefreshRate, int numFrames) {
- const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
- impl::LayerHistoryV2::Summary summary;
- for (int i = 0; i < numFrames; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += framePeriod;
-
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
- << "Frame rate is " << frameRate;
- }
-
- Hwc2::mock::Display mDisplay;
- RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
- .setVsyncPeriod(int32_t(LO_FPS_PERIOD))
- .setConfigGroup(0)
- .build(),
- HWC2::Display::Config::Builder(mDisplay, 1)
- .setVsyncPeriod(int32_t(HI_FPS_PERIOD))
- .setConfigGroup(0)
- .build()},
- HwcConfigIndexType(0)};
-
- mock::NoOpSchedulerCallback mSchedulerCallback;
- static constexpr bool kUseContentDetectionV2 = true;
-
- TestableScheduler* const mScheduler =
- new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
-
- TestableSurfaceFlinger mFlinger;
-};
-
-namespace {
-
-TEST_F(LayerHistoryTestV2, oneLayer) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- const nsecs_t time = systemTime();
-
- // No layers returned if no layers are active.
- EXPECT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
-
- // Max returned if active layers have insufficient history.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- }
-
- // Max is returned since we have enough history but there is no timestamp votes.
- for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- }
-}
-
-TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
-
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- auto summary = history().summarize(time);
- ASSERT_EQ(1, history().summarize(time).size());
- // Layer is still considered inactive so we expect to get Min
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-
- summary = history().summarize(time);
- EXPECT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTestV2, explicitTimestamp) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerNoVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMinVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMaxVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitVote) {
- auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree())
- .WillRepeatedly(
- Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive, but the vote stays
- setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitExactVote) {
- auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive, but the vote stays
- setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, multipleLayers) {
- auto layer1 = createLayer();
- auto layer2 = createLayer();
- auto layer3 = createLayer();
-
- EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- EXPECT_EQ(3, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- impl::LayerHistoryV2::Summary summary;
-
- // layer1 is active but infrequent.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // layer2 is frequent and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- // layer1 is still active but infrequent.
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
- ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer1 is no longer active.
- // layer2 is frequent and has low refresh rate.
- for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer2 still has low refresh rate.
- // layer3 has high refresh rate but not enough history.
- constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- if (i % RATIO == 0) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- }
-
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer3 becomes recently active.
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- summary = history().summarize(time);
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer1 expires.
- layer1.clear();
- summary = history().summarize(time);
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
- EXPECT_EQ(2, layerCount());
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer2 still has low refresh rate.
- // layer3 becomes inactive.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer2 expires.
- layer2.clear();
- summary = history().summarize(time);
- EXPECT_TRUE(summary.empty());
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // layer3 becomes active and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer3 expires.
- layer3.clear();
- summary = history().summarize(time);
- EXPECT_TRUE(summary.empty());
- EXPECT_EQ(0, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, inactiveLayers) {
- auto layer = createLayer();
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- // the very first updates makes the layer frequent
- for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
- }
-
- // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // advance the time for the previous frame to be inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-
- // Now event if we post a quick few frame we should stay infrequent
- for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- }
-
- // More quick frames will get us to frequent again
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, invisibleExplicitLayer) {
- auto explicitVisiblelayer = createLayer();
- auto explicitInvisiblelayer = createLayer();
-
- EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
- EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- nsecs_t time = systemTime();
-
- // Post a buffer to the layers to make them active
- history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- history().record(explicitInvisiblelayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
-
- EXPECT_EQ(2, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, infrequentAnimatingLayer) {
- auto layer = createLayer();
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // layer is active but infrequent.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // another update with the same cadence keep in infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // an update as animation will immediately vote for Max
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(1, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
- for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
- recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- }
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-class LayerHistoryTestV2Parameterized
- : public LayerHistoryTestV2,
- public testing::WithParamInterface<std::chrono::nanoseconds> {};
-
-TEST_P(LayerHistoryTestV2Parameterized, HeuristicLayerWithInfrequentLayer) {
- std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
- auto heuristicLayer = createLayer("HeuristicLayer");
-
- EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(Layer::FrameRate()));
-
- auto infrequentLayer = createLayer("InfrequentLayer");
- EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(Layer::FrameRate()));
-
- const nsecs_t startTime = systemTime();
-
- const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
- history().record(heuristicLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
- history().record(infrequentLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
-
- nsecs_t time = startTime;
- nsecs_t lastInfrequentUpdate = startTime;
- const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
- int infrequentLayerUpdates = 0;
- while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
- time += heuristicUpdateDelta.count();
- history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
- if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
- ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
- totalInfrequentLayerUpdates);
- lastInfrequentUpdate = time;
- history().record(infrequentLayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
- infrequentLayerUpdates++;
- }
-
- if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
- ASSERT_NE(0, history().summarize(time).size());
- ASSERT_GE(2, history().summarize(time).size());
-
- bool max = false;
- bool min = false;
- float heuristic = 0;
- for (const auto& layer : history().summarize(time)) {
- if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
- heuristic = layer.desiredRefreshRate;
- } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
- max = true;
- } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
- min = true;
- }
- }
-
- if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
- EXPECT_FLOAT_EQ(24.0f, heuristic);
- EXPECT_FALSE(max);
- if (history().summarize(time).size() == 2) {
- EXPECT_TRUE(min);
- }
- }
- }
- }
-}
-
-INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestV2Parameterized,
- ::testing::Values(1s, 2s, 3s, 4s, 5s));
-
-} // namespace
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 338e015..f2b7191 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -304,63 +304,6 @@
}
}
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent) {
- auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m60_90Device,
- /*currentConfigId=*/HWC_CONFIG_ID_60);
-
- const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
- return {{"testLayer", LayerVoteType::Heuristic, refreshRate, Seamlessness::OnlySeamless,
- /*weight*/ 1.0f, /*focused*/ false}};
- };
-
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-}
-
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
@@ -847,29 +790,6 @@
}
}
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent_Explicit) {
- auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m60_90Device,
- /*currentConfigId=*/HWC_CONFIG_ID_60);
-
- auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
- LayerRequirement{.weight = 1.0f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 60.0f;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 90.0f;
- EXPECT_EQ(mExpected90Config, refreshRateConfigs->getRefreshRateForContent(layers));
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 90.0f;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60.0f;
- EXPECT_EQ(mExpected60Config, refreshRateConfigs->getRefreshRateForContent(layers));
-}
-
TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getBestRefreshRate_Explicit) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 647689b..fb02a33 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -68,8 +68,7 @@
}
} mExpectDisableVsync{mSchedulerCallback};
- static constexpr bool kUseContentDetectionV2 = false;
- TestableScheduler mScheduler{mConfigs, mSchedulerCallback, kUseContentDetectionV2};
+ TestableScheduler mScheduler{mConfigs, mSchedulerCallback};
Scheduler::ConnectionHandle mConnectionHandle;
mock::EventThread* mEventThread;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1b6e9ed..2192977 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,21 +32,16 @@
class TestableScheduler : public Scheduler {
public:
- TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
- bool useContentDetectionV2)
+ TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: TestableScheduler(std::make_unique<mock::VsyncController>(),
- std::make_unique<mock::VSyncTracker>(), configs, callback,
- useContentDetectionV2) {}
+ std::make_unique<mock::VSyncTracker>(), configs, callback) {}
TestableScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
- const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
- bool useContentDetectionV2)
+ const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: Scheduler({std::move(vsyncController), std::move(vsyncTracker), nullptr}, configs,
- callback, createLayerHistory(configs, useContentDetectionV2),
- {.supportKernelTimer = false,
- .useContentDetection = true,
- .useContentDetectionV2 = useContentDetectionV2}) {}
+ callback, createLayerHistory(configs),
+ {.supportKernelTimer = false, .useContentDetection = true}) {}
// Used to inject mock event thread.
ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -62,20 +57,11 @@
bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
- auto* mutableLayerHistory() {
- LOG_ALWAYS_FATAL_IF(mOptions.useContentDetectionV2);
- return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
- }
-
- auto* mutableLayerHistoryV2() {
- LOG_ALWAYS_FATAL_IF(!mOptions.useContentDetectionV2);
- return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
- }
+ auto* mutableLayerHistory() { return mLayerHistory.get(); }
size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
if (!mLayerHistory) return 0;
- return mOptions.useContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
- : mutableLayerHistory()->mLayerInfos.size();
+ return mutableLayerHistory()->mLayerInfos.size();
}
void replaceTouchTimer(int64_t millis) {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index eba3f8e..c0de465 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -42,6 +42,7 @@
#include "TestableScheduler.h"
#include "mock/DisplayHardware/MockDisplay.h"
#include "mock/MockDisplayIdGenerator.h"
+#include "mock/MockFrameTimeline.h"
#include "mock/MockFrameTracer.h"
namespace android {
@@ -154,6 +155,11 @@
return std::make_unique<mock::FrameTracer>();
}
+ std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+ std::shared_ptr<TimeStats> timeStats) override {
+ return std::make_unique<mock::FrameTimeline>(timeStats);
+ }
+
using CreateBufferQueueFunction =
std::function<void(sp<IGraphicBufferProducer>* /* outProducer */,
sp<IGraphicBufferConsumer>* /* outConsumer */,
@@ -227,10 +233,8 @@
mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
- constexpr bool kUseContentDetectionV2 = false;
mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
- *mFlinger->mRefreshRateConfigs, *(callback ?: this),
- kUseContentDetectionV2);
+ *mFlinger->mRefreshRateConfigs, *(callback ?: this));
mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index a90f424..bbcc0c9 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -356,11 +356,11 @@
EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::Display);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::None);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::Display);
+ mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::None);
const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
std::string expectedResult = "totalTimelineFrames = " + std::to_string(5);
@@ -383,13 +383,13 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
const std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
std::string expectedResult = "totalTimelineFrames = " + std::to_string(5);
@@ -848,13 +848,13 @@
std::chrono::duration_cast<std::chrono::nanoseconds>(1ms).count()));
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
EXPECT_TRUE(inputCommand(InputCommand::CLEAR, FMT_STRING).empty());
@@ -987,11 +987,11 @@
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::Display);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(TimeStats::JankType::None);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::Display);
+ mTimeStats->incrementJankyFrames(JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(JankType::None);
EXPECT_THAT(mDelegate->mAtomTags,
UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
@@ -1062,13 +1062,13 @@
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerDeadlineMissed);
+ JankType::SurfaceFlingerDeadlineMissed);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::SurfaceFlingerGpuDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::Display);
+ JankType::SurfaceFlingerGpuDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::Display);
mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0),
- TimeStats::JankType::AppDeadlineMissed);
- mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), TimeStats::JankType::None);
+ JankType::AppDeadlineMissed);
+ mTimeStats->incrementJankyFrames(UID_0, genLayerName(LAYER_ID_0), JankType::None);
EXPECT_THAT(mDelegate->mAtomTags,
UnorderedElementsAre(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
new file mode 100644
index 0000000..f784df3
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock/MockFrameTimeline.h"
+
+namespace android::mock {
+
+// Explicit default instantiation is recommended.
+FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats)
+ : android::frametimeline::impl::FrameTimeline(timeStats) {}
+FrameTimeline::~FrameTimeline() = default;
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
new file mode 100644
index 0000000..81c32fe
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "FrameTimeline/FrameTimeline.h"
+
+namespace android::mock {
+
+class FrameTimeline : public android::frametimeline::impl::FrameTimeline {
+ // No need to create mocks for SurfaceFrame and TokenManager yet. They are very small components
+ // and do not have external dependencies like perfetto.
+public:
+ FrameTimeline(std::shared_ptr<TimeStats> timeStats);
+ ~FrameTimeline();
+
+ MOCK_METHOD0(onBootFinished, void());
+ MOCK_METHOD2(addSurfaceFrame,
+ void(std::unique_ptr<frametimeline::SurfaceFrame>,
+ frametimeline::SurfaceFrame::PresentState));
+ MOCK_METHOD2(setSfWakeUp, void(int64_t, nsecs_t));
+ MOCK_METHOD2(setSfPresent, void(nsecs_t, const std::shared_ptr<FenceTime>&));
+};
+
+} // namespace android::mock
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index fa742c5..75228d6 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -35,7 +35,7 @@
"libhidlbase",
"liblog",
"libutils",
- "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator-unstable-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index e8606ca..bcd9957 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -46,7 +46,7 @@
// -------------------------------------------------------------------------------------------------
-std::shared_ptr<HalWrapper> HalConnector::connect(std::shared_ptr<CallbackScheduler> scheduler) {
+std::shared_ptr<HalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
static bool gHalExists = true;
if (!gHalExists) {
// We already tried to connect to all of the vibrator HAL versions and none was available.
@@ -106,7 +106,7 @@
std::lock_guard<std::mutex> lock(mConnectedHalMutex);
if (mConnectedHal == nullptr) {
// Init was never called, so connect to HAL for the first time during this call.
- mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+ mConnectedHal = mConnector(mCallbackScheduler);
if (mConnectedHal == nullptr) {
ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
@@ -129,7 +129,7 @@
bool HalController::init() {
std::lock_guard<std::mutex> lock(mConnectedHalMutex);
if (mConnectedHal == nullptr) {
- mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+ mConnectedHal = mConnector(mCallbackScheduler);
}
return mConnectedHal != nullptr;
}
@@ -142,7 +142,7 @@
void HalController::tryReconnect() {
std::lock_guard<std::mutex> lock(mConnectedHalMutex);
if (mConnectedHal == nullptr) {
- mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+ mConnectedHal = mConnector(mCallbackScheduler);
} else {
mConnectedHal->tryReconnect();
}
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 9672644..7fee82f 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "VibratorHalWrapper"
#include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
#include <android/hardware/vibrator/IVibrator.h>
#include <hardware/vibrator.h>
@@ -73,17 +72,6 @@
"android::hardware::vibrator::V1_0::Status = ";
template <typename T>
-HalResult<T> HalResult<T>::fromStatus(binder::Status status, T data) {
- if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
- return HalResult<T>::unsupported();
- }
- if (status.isOk()) {
- return HalResult<T>::ok(data);
- }
- return HalResult<T>::failed(std::string(status.toString8().c_str()));
-}
-
-template <typename T>
HalResult<T> HalResult<T>::fromStatus(V1_0::Status status, T data) {
switch (status) {
case V1_0::Status::OK:
@@ -145,28 +133,16 @@
// -------------------------------------------------------------------------------------------------
-class HalCallbackWrapper : public Aidl::BnVibratorCallback {
-public:
- HalCallbackWrapper(std::function<void()> completionCallback)
- : mCompletionCallback(completionCallback) {}
-
- binder::Status onComplete() override {
- mCompletionCallback();
- return binder::Status::ok();
- }
-
-private:
- const std::function<void()> mCompletionCallback;
-};
-
-// -------------------------------------------------------------------------------------------------
-
HalResult<void> AidlHalWrapper::ping() {
return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
}
void AidlHalWrapper::tryReconnect() {
- sp<Aidl::IVibrator> newHandle = checkVintfService<Aidl::IVibrator>();
+ auto result = mReconnectFn();
+ if (!result.isOk()) {
+ return;
+ }
+ sp<Aidl::IVibrator> newHandle = result.value();
if (newHandle) {
std::lock_guard<std::mutex> lock(mHandleMutex);
mHandle = std::move(newHandle);
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
new file mode 100644
index 0000000..b24e5c4
--- /dev/null
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorManagerHalController"
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalController.h>
+
+namespace Aidl = android::hardware::vibrator;
+
+namespace android {
+
+namespace vibrator {
+
+std::shared_ptr<ManagerHalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler) {
+ static bool gHalExists = true;
+ if (gHalExists) {
+ sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
+ if (hal) {
+ ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
+ return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), aidlHal);
+ }
+ }
+
+ gHalExists = false;
+ return std::make_shared<LegacyManagerHalWrapper>();
+}
+
+static constexpr int MAX_RETRIES = 1;
+
+template <typename T>
+HalResult<T> ManagerHalController::processHalResult(HalResult<T> result, const char* functionName) {
+ if (result.isFailed()) {
+ ALOGE("%s failed: %s", functionName, result.errorMessage());
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ mConnectedHal->tryReconnect();
+ }
+ return result;
+}
+
+template <typename T>
+HalResult<T> ManagerHalController::apply(ManagerHalController::hal_fn<T>& halFn,
+ const char* functionName) {
+ std::shared_ptr<ManagerHalWrapper> hal = nullptr;
+ {
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ if (mConnectedHal == nullptr) {
+ // Init was never called, so connect to HAL for the first time during this call.
+ mConnectedHal = mConnector(mCallbackScheduler);
+
+ if (mConnectedHal == nullptr) {
+ ALOGV("Skipped %s because VibratorManager HAL is not available", functionName);
+ return HalResult<T>::unsupported();
+ }
+ }
+ hal = mConnectedHal;
+ }
+
+ HalResult<T> ret = processHalResult(halFn(hal), functionName);
+ for (int i = 0; i < MAX_RETRIES && ret.isFailed(); i++) {
+ ret = processHalResult(halFn(hal), functionName);
+ }
+
+ return ret;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool ManagerHalController::init() {
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ if (mConnectedHal == nullptr) {
+ mConnectedHal = mConnector(mCallbackScheduler);
+ }
+ return mConnectedHal != nullptr;
+}
+
+HalResult<void> ManagerHalController::ping() {
+ hal_fn<void> pingFn = [](std::shared_ptr<ManagerHalWrapper> hal) { return hal->ping(); };
+ return apply(pingFn, "ping");
+}
+
+void ManagerHalController::tryReconnect() {
+ std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+ if (mConnectedHal == nullptr) {
+ mConnectedHal = mConnector(mCallbackScheduler);
+ } else {
+ mConnectedHal->tryReconnect();
+ }
+}
+
+HalResult<ManagerCapabilities> ManagerHalController::getCapabilities() {
+ hal_fn<ManagerCapabilities> getCapabilitiesFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+ return hal->getCapabilities();
+ };
+ return apply(getCapabilitiesFn, "getCapabilities");
+}
+
+HalResult<std::vector<int32_t>> ManagerHalController::getVibratorIds() {
+ hal_fn<std::vector<int32_t>> getVibratorIdsFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+ return hal->getVibratorIds();
+ };
+ return apply(getVibratorIdsFn, "getVibratorIds");
+}
+
+HalResult<std::shared_ptr<HalController>> ManagerHalController::getVibrator(int32_t id) {
+ hal_fn<std::shared_ptr<HalController>> getVibratorFn =
+ [&](std::shared_ptr<ManagerHalWrapper> hal) { return hal->getVibrator(id); };
+ return apply(getVibratorFn, "getVibrator");
+}
+
+HalResult<void> ManagerHalController::prepareSynced(const std::vector<int32_t>& ids) {
+ hal_fn<void> prepareSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
+ return hal->prepareSynced(ids);
+ };
+ return apply(prepareSyncedFn, "prepareSynced");
+}
+
+HalResult<void> ManagerHalController::triggerSynced(
+ const std::function<void()>& completionCallback) {
+ hal_fn<void> triggerSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
+ return hal->triggerSynced(completionCallback);
+ };
+ return apply(triggerSyncedFn, "triggerSynced");
+}
+
+HalResult<void> ManagerHalController::cancelSynced() {
+ hal_fn<void> cancelSyncedFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
+ return hal->cancelSynced();
+ };
+ return apply(cancelSyncedFn, "cancelSynced");
+}
+
+}; // namespace vibrator
+
+}; // namespace android
diff --git a/services/vibratorservice/VibratorManagerHalWrapper.cpp b/services/vibratorservice/VibratorManagerHalWrapper.cpp
index 71955af..8a08e5b 100644
--- a/services/vibratorservice/VibratorManagerHalWrapper.cpp
+++ b/services/vibratorservice/VibratorManagerHalWrapper.cpp
@@ -20,11 +20,14 @@
#include <vibratorservice/VibratorManagerHalWrapper.h>
+namespace Aidl = android::hardware::vibrator;
+
namespace android {
namespace vibrator {
constexpr int32_t SINGLE_VIBRATOR_ID = 0;
+const constexpr char* MISSING_VIBRATOR_MESSAGE_PREFIX = "No vibrator with id=";
HalResult<void> LegacyManagerHalWrapper::ping() {
return mController->ping();
@@ -34,6 +37,10 @@
mController->tryReconnect();
}
+HalResult<ManagerCapabilities> LegacyManagerHalWrapper::getCapabilities() {
+ return HalResult<ManagerCapabilities>::ok(ManagerCapabilities::NONE);
+}
+
HalResult<std::vector<int32_t>> LegacyManagerHalWrapper::getVibratorIds() {
if (mController->init()) {
return HalResult<std::vector<int32_t>>::ok(std::vector<int32_t>(1, SINGLE_VIBRATOR_ID));
@@ -47,7 +54,7 @@
return HalResult<std::shared_ptr<HalController>>::ok(mController);
}
// Controller.init did not connect to any vibrator HAL service, so the device has no vibrator.
- return HalResult<std::shared_ptr<HalController>>::failed("No vibrator with id = " +
+ return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
std::to_string(id));
}
@@ -63,6 +70,136 @@
return HalResult<void>::unsupported();
}
+// -------------------------------------------------------------------------------------------------
+
+std::shared_ptr<HalWrapper> AidlManagerHalWrapper::connectToVibrator(
+ int32_t vibratorId, std::shared_ptr<CallbackScheduler> callbackScheduler) {
+ std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=]() {
+ sp<Aidl::IVibrator> vibrator;
+ auto result = this->getHal()->getVibrator(vibratorId, &vibrator);
+ return HalResult<sp<Aidl::IVibrator>>::fromStatus(result, vibrator);
+ };
+ auto result = reconnectFn();
+ if (!result.isOk()) {
+ return nullptr;
+ }
+ auto vibrator = result.value();
+ if (!vibrator) {
+ return nullptr;
+ }
+ return std::move(std::make_unique<AidlHalWrapper>(std::move(callbackScheduler),
+ std::move(vibrator), reconnectFn));
+}
+
+HalResult<void> AidlManagerHalWrapper::ping() {
+ return HalResult<void>::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+}
+
+void AidlManagerHalWrapper::tryReconnect() {
+ sp<Aidl::IVibratorManager> newHandle = checkVintfService<Aidl::IVibratorManager>();
+ if (newHandle) {
+ std::lock_guard<std::mutex> lock(mHandleMutex);
+ mHandle = std::move(newHandle);
+ }
+}
+
+HalResult<ManagerCapabilities> AidlManagerHalWrapper::getCapabilities() {
+ std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
+ if (mCapabilities.has_value()) {
+ // Return copy of cached value.
+ return HalResult<ManagerCapabilities>::ok(*mCapabilities);
+ }
+ int32_t cap = 0;
+ auto result = getHal()->getCapabilities(&cap);
+ auto ret = HalResult<ManagerCapabilities>::fromStatus(result,
+ static_cast<ManagerCapabilities>(cap));
+ if (ret.isOk()) {
+ // Cache copy of returned value.
+ mCapabilities.emplace(ret.value());
+ }
+ return ret;
+}
+
+HalResult<std::vector<int32_t>> AidlManagerHalWrapper::getVibratorIds() {
+ std::lock_guard<std::mutex> lock(mVibratorsMutex);
+ if (mVibratorIds.has_value()) {
+ // Return copy of cached values.
+ return HalResult<std::vector<int32_t>>::ok(*mVibratorIds);
+ }
+ std::vector<int32_t> ids;
+ auto result = getHal()->getVibratorIds(&ids);
+ auto ret = HalResult<std::vector<int32_t>>::fromStatus(result, ids);
+ if (ret.isOk()) {
+ // Cache copy of returned value and the individual controllers.
+ mVibratorIds.emplace(ret.value());
+ for (auto& id : ids) {
+ HalController::Connector connector = [&, id](auto scheduler) {
+ return this->connectToVibrator(id, scheduler);
+ };
+ auto controller = std::make_unique<HalController>(mCallbackScheduler, connector);
+ mVibrators[id] = std::move(controller);
+ }
+ }
+ return ret;
+}
+
+HalResult<std::shared_ptr<HalController>> AidlManagerHalWrapper::getVibrator(int32_t id) {
+ // Make sure we cache vibrator ids and initialize the individual controllers.
+ getVibratorIds();
+ std::lock_guard<std::mutex> lock(mVibratorsMutex);
+ auto it = mVibrators.find(id);
+ if (it != mVibrators.end()) {
+ return HalResult<std::shared_ptr<HalController>>::ok(it->second);
+ }
+ return HalResult<std::shared_ptr<HalController>>::failed(MISSING_VIBRATOR_MESSAGE_PREFIX +
+ std::to_string(id));
+}
+
+HalResult<void> AidlManagerHalWrapper::prepareSynced(const std::vector<int32_t>& ids) {
+ auto ret = HalResult<void>::fromStatus(getHal()->prepareSynced(ids));
+ if (ret.isOk()) {
+ // Force reload of all vibrator controllers that were prepared for a sync operation here.
+ // This will trigger calls to getVibrator(id) on each controller, so they can use the
+ // latest service provided by this manager.
+ std::lock_guard<std::mutex> lock(mVibratorsMutex);
+ for (auto& id : ids) {
+ auto it = mVibrators.find(id);
+ if (it != mVibrators.end()) {
+ it->second->tryReconnect();
+ }
+ }
+ }
+ return ret;
+}
+
+HalResult<void> AidlManagerHalWrapper::triggerSynced(
+ const std::function<void()>& completionCallback) {
+ HalResult<ManagerCapabilities> capabilities = getCapabilities();
+ bool supportsCallback = capabilities.isOk() &&
+ static_cast<int32_t>(capabilities.value() & ManagerCapabilities::TRIGGER_CALLBACK);
+ auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+ return HalResult<void>::fromStatus(getHal()->triggerSynced(cb));
+}
+
+HalResult<void> AidlManagerHalWrapper::cancelSynced() {
+ auto ret = HalResult<void>::fromStatus(getHal()->cancelSynced());
+ if (ret.isOk()) {
+ // Force reload of all vibrator controllers that were prepared for a sync operation before.
+ // This will trigger calls to getVibrator(id) on each controller, so they can use the
+ // latest service provided by this manager.
+ std::lock_guard<std::mutex> lock(mVibratorsMutex);
+ for (auto& entry : mVibrators) {
+ entry.second->tryReconnect();
+ }
+ }
+ return ret;
+}
+
+sp<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
+ std::lock_guard<std::mutex> lock(mHandleMutex);
+ return mHandle;
+}
+
}; // namespace vibrator
}; // namespace android
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index c1a03a1..d3130f4 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -23,7 +23,7 @@
"liblog",
"libutils",
"libvibratorservice",
- "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator-unstable-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index d1028a4..c405545 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -27,27 +27,20 @@
namespace vibrator {
-// Handles the connection to he underlying HAL implementation available.
-class HalConnector {
-public:
- HalConnector() = default;
- virtual ~HalConnector() = default;
-
- virtual std::shared_ptr<HalWrapper> connect(std::shared_ptr<CallbackScheduler> scheduler);
-};
+std::shared_ptr<HalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler);
// Controller for Vibrator HAL handle.
-// This relies on HalConnector to connect to the underlying Vibrator HAL service and reconnects to
-// it after each failed api call. This also ensures connecting to the service is thread-safe.
+// This relies on a given Connector to connect to the underlying Vibrator HAL service and reconnects
+// after each failed api call. This also ensures connecting to the service is thread-safe.
class HalController : public HalWrapper {
public:
- HalController()
- : HalController(std::make_unique<HalConnector>(), std::make_shared<CallbackScheduler>()) {
- }
- HalController(std::unique_ptr<HalConnector> halConnector,
- std::shared_ptr<CallbackScheduler> callbackScheduler)
+ using Connector =
+ std::function<std::shared_ptr<HalWrapper>(std::shared_ptr<CallbackScheduler>)>;
+
+ HalController() : HalController(std::make_shared<CallbackScheduler>(), &connectHal) {}
+ HalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
: HalWrapper(std::move(callbackScheduler)),
- mHalConnector(std::move(halConnector)),
+ mConnector(connector),
mConnectedHal(nullptr) {}
virtual ~HalController() = default;
@@ -89,7 +82,7 @@
const std::function<void()>& completionCallback) final override;
private:
- std::unique_ptr<HalConnector> mHalConnector;
+ Connector mConnector;
std::mutex mConnectedHalMutex;
// Shared pointer to allow local copies to be used by different threads.
std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index bcb735d..638b483 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -19,6 +19,7 @@
#include <android-base/thread_annotations.h>
#include <android/hardware/vibrator/1.3/IVibrator.h>
+#include <android/hardware/vibrator/BnVibratorCallback.h>
#include <android/hardware/vibrator/IVibrator.h>
#include <binder/IServiceManager.h>
@@ -40,7 +41,15 @@
}
static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
- static HalResult<T> fromStatus(binder::Status status, T data);
+ static HalResult<T> fromStatus(binder::Status status, T data) {
+ if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<T>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<T>::ok(data);
+ }
+ return HalResult<T>::failed(std::string(status.toString8().c_str()));
+ }
static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data);
template <typename R>
@@ -99,6 +108,20 @@
: mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
};
+class HalCallbackWrapper : public hardware::vibrator::BnVibratorCallback {
+public:
+ HalCallbackWrapper(std::function<void()> completionCallback)
+ : mCompletionCallback(completionCallback) {}
+
+ binder::Status onComplete() override {
+ mCompletionCallback();
+ return binder::Status::ok();
+ }
+
+private:
+ const std::function<void()> mCompletionCallback;
+};
+
// -------------------------------------------------------------------------------------------------
// Vibrator HAL capabilities.
@@ -178,9 +201,16 @@
// Wrapper for the AIDL Vibrator HAL.
class AidlHalWrapper : public HalWrapper {
public:
- AidlHalWrapper(std::shared_ptr<CallbackScheduler> scheduler,
- sp<hardware::vibrator::IVibrator> handle)
- : HalWrapper(std::move(scheduler)), mHandle(std::move(handle)) {}
+ AidlHalWrapper(
+ std::shared_ptr<CallbackScheduler> scheduler, sp<hardware::vibrator::IVibrator> handle,
+ std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> reconnectFn =
+ []() {
+ return HalResult<sp<hardware::vibrator::IVibrator>>::ok(
+ checkVintfService<hardware::vibrator::IVibrator>());
+ })
+ : HalWrapper(std::move(scheduler)),
+ mReconnectFn(reconnectFn),
+ mHandle(std::move(handle)) {}
virtual ~AidlHalWrapper() = default;
HalResult<void> ping() override final;
@@ -211,6 +241,7 @@
const std::function<void()>& completionCallback) override final;
private:
+ const std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> mReconnectFn;
std::mutex mHandleMutex;
std::mutex mCapabilitiesMutex;
std::mutex mSupportedEffectsMutex;
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
new file mode 100644
index 0000000..cf82562
--- /dev/null
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
+#define ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
+
+#include <android/hardware/vibrator/IVibratorManager.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <unordered_map>
+
+namespace android {
+
+namespace vibrator {
+
+std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler);
+
+// Controller for VibratorManager HAL handle.
+class ManagerHalController : public ManagerHalWrapper {
+public:
+ using Connector =
+ std::function<std::shared_ptr<ManagerHalWrapper>(std::shared_ptr<CallbackScheduler>)>;
+
+ ManagerHalController()
+ : ManagerHalController(std::make_shared<CallbackScheduler>(), &connectManagerHal) {}
+ ManagerHalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
+ : mConnector(connector), mConnectedHal(nullptr) {}
+ virtual ~ManagerHalController() = default;
+
+ /* Connects to the HAL service, possibly waiting for the registered service to
+ * become available. This will automatically be called at the first API usage if it was not
+ * manually called beforehand. Calling this manually during the setup phase can avoid slowing
+ * the first API call later on. This will fallback to a legacy manager implementation if the
+ * service is not available.
+ */
+ virtual void init();
+
+ /* reloads HAL service instance without waiting. This relies on the HAL found by init()
+ * to rapidly reconnect to the specific HAL service, or defers to init() if it was never called.
+ */
+ void tryReconnect() override final;
+
+ HalResult<void> ping() override final;
+
+ HalResult<ManagerCapabilities> getCapabilities() override final;
+ HalResult<std::vector<int32_t>> getVibratorIds() override final;
+ HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
+
+ HalResult<void> prepareSynced(const std::vector<int32_t>& ids) override final;
+ HalResult<void> triggerSynced(const std::function<void()>& completionCallback) override final;
+ HalResult<void> cancelSynced() override final;
+
+private:
+ Connector mConnector;
+ std::mutex mConnectedHalMutex;
+ // Shared pointer to allow local copies to be used by different threads.
+ std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
+
+ template <typename T>
+ HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
+
+ template <typename T>
+ using hal_fn = std::function<HalResult<T>(std::shared_ptr<ManagerHalWrapper>)>;
+
+ template <typename T>
+ HalResult<T> apply(hal_fn<T>& halFn, const char* functionName);
+};
+
+}; // namespace vibrator
+
+}; // namespace android
+
+#endif // ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
index 99947a5..563f55e 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
@@ -17,12 +17,47 @@
#ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
#define ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
+#include <android/hardware/vibrator/IVibratorManager.h>
#include <vibratorservice/VibratorHalController.h>
+#include <unordered_map>
namespace android {
namespace vibrator {
+// VibratorManager HAL capabilities.
+enum class ManagerCapabilities : int32_t {
+ NONE = 0,
+ SYNC = hardware::vibrator::IVibratorManager::CAP_SYNC,
+ PREPARE_ON = hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
+ PREPARE_PERFORM = hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
+ PREPARE_COMPOSE = hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
+ MIXED_TRIGGER_ON = hardware::vibrator::IVibratorManager::IVibratorManager::CAP_MIXED_TRIGGER_ON,
+ MIXED_TRIGGER_PERFORM = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
+ MIXED_TRIGGER_COMPOSE = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
+ TRIGGER_CALLBACK = hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
+};
+
+inline ManagerCapabilities operator|(ManagerCapabilities lhs, ManagerCapabilities rhs) {
+ using underlying = typename std::underlying_type<ManagerCapabilities>::type;
+ return static_cast<ManagerCapabilities>(static_cast<underlying>(lhs) |
+ static_cast<underlying>(rhs));
+}
+
+inline ManagerCapabilities& operator|=(ManagerCapabilities& lhs, ManagerCapabilities rhs) {
+ return lhs = lhs | rhs;
+}
+
+inline ManagerCapabilities operator&(ManagerCapabilities lhs, ManagerCapabilities rhs) {
+ using underlying = typename std::underlying_type<ManagerCapabilities>::type;
+ return static_cast<ManagerCapabilities>(static_cast<underlying>(lhs) &
+ static_cast<underlying>(rhs));
+}
+
+inline ManagerCapabilities& operator&=(ManagerCapabilities& lhs, ManagerCapabilities rhs) {
+ return lhs = lhs & rhs;
+}
+
// Wrapper for VibratorManager HAL handlers.
class ManagerHalWrapper {
public:
@@ -36,6 +71,7 @@
*/
virtual void tryReconnect() = 0;
+ virtual HalResult<ManagerCapabilities> getCapabilities() = 0;
virtual HalResult<std::vector<int32_t>> getVibratorIds() = 0;
virtual HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) = 0;
@@ -55,6 +91,7 @@
HalResult<void> ping() override final;
void tryReconnect() override final;
+ HalResult<ManagerCapabilities> getCapabilities() override final;
HalResult<std::vector<int32_t>> getVibratorIds() override final;
HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
@@ -66,6 +103,41 @@
const std::shared_ptr<HalController> mController;
};
+// Wrapper for the AIDL VibratorManager HAL.
+class AidlManagerHalWrapper : public ManagerHalWrapper {
+public:
+ explicit AidlManagerHalWrapper(std::shared_ptr<CallbackScheduler> callbackScheduler,
+ sp<hardware::vibrator::IVibratorManager> handle)
+ : mHandle(std::move(handle)), mCallbackScheduler(callbackScheduler) {}
+ virtual ~AidlManagerHalWrapper() = default;
+
+ HalResult<void> ping() override final;
+ void tryReconnect() override final;
+
+ HalResult<ManagerCapabilities> getCapabilities() override final;
+ HalResult<std::vector<int32_t>> getVibratorIds() override final;
+ HalResult<std::shared_ptr<HalController>> getVibrator(int32_t id) override final;
+
+ HalResult<void> prepareSynced(const std::vector<int32_t>& ids) override final;
+ HalResult<void> triggerSynced(const std::function<void()>& completionCallback) override final;
+ HalResult<void> cancelSynced() override final;
+
+private:
+ std::mutex mHandleMutex;
+ std::mutex mCapabilitiesMutex;
+ std::mutex mVibratorsMutex;
+ sp<hardware::vibrator::IVibratorManager> mHandle GUARDED_BY(mHandleMutex);
+ std::optional<ManagerCapabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
+ std::optional<std::vector<int32_t>> mVibratorIds GUARDED_BY(mVibratorsMutex);
+ std::unordered_map<int32_t, std::shared_ptr<HalController>> mVibrators
+ GUARDED_BY(mVibratorsMutex);
+ std::shared_ptr<CallbackScheduler> mCallbackScheduler;
+
+ sp<hardware::vibrator::IVibratorManager> getHal();
+ std::shared_ptr<HalWrapper> connectToVibrator(int32_t vibratorId,
+ std::shared_ptr<CallbackScheduler> scheduler);
+};
+
}; // namespace vibrator
}; // namespace android
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 5fc6d45..6801f76 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -23,6 +23,7 @@
"VibratorHalWrapperHidlV1_1Test.cpp",
"VibratorHalWrapperHidlV1_2Test.cpp",
"VibratorHalWrapperHidlV1_3Test.cpp",
+ "VibratorManagerHalWrapperAidlTest.cpp",
"VibratorManagerHalWrapperLegacyTest.cpp",
],
cflags: [
@@ -37,7 +38,7 @@
"liblog",
"libvibratorservice",
"libutils",
- "android.hardware.vibrator-cpp",
+ "android.hardware.vibrator-unstable-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index cda5e9a..2d9d0d6 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -79,38 +79,6 @@
vibrator::CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
};
-class TestHalConnector : public vibrator::HalConnector {
-public:
- TestHalConnector(int32_t* connectCounter, std::shared_ptr<MockHalWrapper> mockHal)
- : mConnectCounter(connectCounter), mMockHal(std::move(mockHal)) {}
- ~TestHalConnector() = default;
-
- std::shared_ptr<vibrator::HalWrapper> connect(
- std::shared_ptr<vibrator::CallbackScheduler>) override final {
- android_atomic_inc(mConnectCounter);
- return mMockHal;
- }
-
-private:
- int32_t* mConnectCounter;
- std::shared_ptr<MockHalWrapper> mMockHal;
-};
-
-class FailingHalConnector : public vibrator::HalConnector {
-public:
- FailingHalConnector(int32_t* connectCounter) : mConnectCounter(connectCounter) {}
- ~FailingHalConnector() = default;
-
- std::shared_ptr<vibrator::HalWrapper> connect(
- std::shared_ptr<vibrator::CallbackScheduler>) override final {
- android_atomic_inc(mConnectCounter);
- return nullptr;
- }
-
-private:
- int32_t* mConnectCounter;
-};
-
// -------------------------------------------------------------------------------------------------
class VibratorHalControllerTest : public Test {
@@ -119,9 +87,12 @@
mConnectCounter = 0;
auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
- auto halConnector = std::make_unique<TestHalConnector>(&mConnectCounter, mMockHal);
- mController = std::make_unique<vibrator::HalController>(std::move(halConnector),
- std::move(callbackScheduler));
+ mController = std::make_unique<
+ vibrator::HalController>(std::move(callbackScheduler),
+ [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+ android_atomic_inc(&(this->mConnectCounter));
+ return this->mMockHal;
+ });
ASSERT_NE(mController, nullptr);
}
@@ -334,9 +305,11 @@
}
TEST_F(VibratorHalControllerTest, TestNoVibratorReturnsUnsupportedAndAttemptsToReconnect) {
- auto failingHalConnector = std::make_unique<FailingHalConnector>(&mConnectCounter);
- mController =
- std::make_unique<vibrator::HalController>(std::move(failingHalConnector), nullptr);
+ mController = std::make_unique<
+ vibrator::HalController>(nullptr, [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+ android_atomic_inc(&(this->mConnectCounter));
+ return nullptr;
+ });
ASSERT_EQ(0, mConnectCounter);
ASSERT_FALSE(mController->init());
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
new file mode 100644
index 0000000..3b036ee
--- /dev/null
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorManagerHalControllerTest"
+
+#include <cutils/atomic.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+#include "test_utils.h"
+
+using namespace android;
+using namespace testing;
+
+static constexpr int MAX_ATTEMPTS = 2;
+
+class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
+public:
+ MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+ MOCK_METHOD(vibrator::HalResult<int32_t>, getCapabilities, (), (override));
+ MOCK_METHOD(vibrator::HalResult<std::vector<int32_t>>, getVibratorIds, (), (override));
+ MOCK_METHOD(vibrator::HalResult<std::shared_ptr<vibrator::HalController>>, getVibrator,
+ (int32_t id), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, prepareSynced, (const std::vector<int32_t>& ids),
+ (override));
+ MOCK_METHOD(vibrator::HalResult<void>, triggerSynced,
+ (const std::function<void()>& completionCallback), (override));
+ MOCK_METHOD(vibrator::HalResult<void>, cancelSynced, (), (override));
+};
+
+class VibratorManagerHalControllerTest : public Test {
+public:
+ void SetUp() override {
+ mConnectCounter = 0;
+ auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
+ mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
+ mController = std::make_unique<
+ vibrator::HalController>(std::move(callbackScheduler),
+ [&](std::shared_ptr<vibrator::CallbackScheduler>) {
+ android_atomic_inc(&(this->mConnectCounter));
+ return this->mMockHal;
+ });
+ ASSERT_NE(mController, nullptr);
+ }
+
+protected:
+ int32_t mConnectCounter;
+ std::shared_ptr<MockManagerHalWrapper> mMockHal;
+ std::unique_ptr<vibrator::ManagerHalController> mController;
+
+ void setHalExpectations(int32_t cardinality, std::vector<int32_t> ids,
+ vibrator::HalResult<void> voidResult,
+ vibrator::HalResult<vibrator::ManagerCapabilities> capabilitiesResult,
+ vibrator::HalResult<std::vector<int32_t>> idsResult,
+ vibrator::HalResult<std::shared_ptr<HalController>> vibratorResult) {
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), getCapabilities())
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(capabilitiesResult));
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds())
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(idsResult));
+ EXPECT_CALL(*mMockHal.get(), getVibrator(_))
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(vibratorResult));
+ EXPECT_CALL(*mMockHal.get(), prepareSynced(_))
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(voidResult));
+ EXPECT_CALL(*mMockHal.get(), cancelSynced())
+ .Times(Exactly(cardinality))
+ .WillRepeatedly(Return(voidResult));
+
+ if (cardinality > 1) {
+ // One reconnection call after each failure.
+ EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(7 * cardinality));
+ }
+ }
+};
+
+TEST_F(VibratorManagerHalControllerTest, TestInit) {
+ ASSERT_TRUE(mController->init());
+ ASSERT_EQ(1, mConnectCounter);
+
+ // Noop when wrapper was already initialized.
+ ASSERT_TRUE(mController->init());
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestApiCallsAreForwardedToHal) {
+ std::vector<int32_t> ids;
+ ids.push_back(1);
+ ids.push_back(2);
+
+ setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::ok(),
+ vibrator::HalResult<vibrator::ManagerCapabilities>::ok(
+ vibrator::ManagerCapabilities::SYNC),
+ vibrator::HalResult<std::vector<int32_t>>::ok(ids),
+ vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::ok(nullptr));
+
+ ASSERT_TRUE(mController->ping().isOk());
+
+ auto getCapabilitiesResult = mController->getCapabilities();
+ ASSERT_TRUE(getCapabilitiesResult.isOk());
+ ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, getCapabilitiesResult.value());
+
+ auto getVibratorIdsResult = mController->getVibratorIds();
+ ASSERT_TRUE(getVibratorIdsResult.isOk());
+ ASSERT_EQ(ids, getVibratorIdsResult.value());
+
+ auto getVibratorResult = mController->getVibrator(1);
+ ASSERT_TRUE(getVibratorResult.isOk());
+ ASSERT_EQ(nullptr, getVibratorResult.value());
+
+ ASSERT_TRUE(mController->prepareSynced(ids).isOk());
+ ASSERT_TRUE(mController->triggerSynced([]() {}).isOk());
+ ASSERT_TRUE(mController->cancelSynced().isOk());
+
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestUnsupportedApiResultDoNotResetHalConnection) {
+ std::vector<int32_t> ids;
+ setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::unsupported(),
+ vibrator::HalResult<vibrator::ManagerCapabilities>::unsupported(),
+ vibrator::HalResult<std::vector<int32_t>>::unsupported(),
+ vibrator::HalResult<
+ std::shared_ptr<vibrator::HalController>>::unsupported());
+
+ ASSERT_EQ(0, mConnectCounter);
+
+ ASSERT_TRUE(mController->ping().isUnsupported());
+ ASSERT_TRUE(mController->getCapabilities().isUnsupported());
+ ASSERT_TRUE(mController->getVibratorIds().isUnsupported());
+ ASSERT_TRUE(mController->getVibrator(1).isUnsupported());
+ ASSERT_TRUE(mController->prepareSynced(ids).isUnsupported());
+ ASSERT_TRUE(mController->triggerSynced([]() {}).isUnsupported());
+ ASSERT_TRUE(mController->cancelSynced().isUnsupported());
+
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultResetsHalConnection) {
+ std::vector<int32_t> ids;
+ setHalExpectations(/* cardinality= */ 1, ids, vibrator::HalResult<void>::failed("message"),
+ vibrator::HalResult<vibrator::ManagerCapabilities>::failed("message"),
+ vibrator::HalResult<std::vector<int32_t>>::failed("message"),
+ vibrator::HalResult<std::shared_ptr<vibrator::HalController>>::failed(
+ "message"));
+
+ ASSERT_EQ(0, mConnectCounter);
+
+ ASSERT_TRUE(mController->ping().isFailed());
+ ASSERT_TRUE(mController->getCapabilities().isFailed());
+ ASSERT_TRUE(mController->getVibratorIds().isFailed());
+ ASSERT_TRUE(mController->getVibrator(1).isFailed());
+ ASSERT_TRUE(mController->prepareSynced(ids).isFailed());
+ ASSERT_TRUE(mController->triggerSynced([]() {}).isFailed());
+ ASSERT_TRUE(mController->cancelSynced().isFailed());
+
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestFailedApiResultReturnsSuccessAfterRetries) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(vibrator::HalResult<void>::failed("message")));
+ EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
+ }
+
+ ASSERT_EQ(0, mConnectCounter);
+ ASSERT_TRUE(mController->ping().isOk());
+ ASSERT_EQ(1, mConnectCounter);
+}
+
+TEST_F(VibratorManagerHalControllerTest, TestMultiThreadConnectsOnlyOnce) {
+ ASSERT_EQ(0, mConnectCounter);
+
+ EXPECT_CALL(*mMockHal.get(), ping())
+ .Times(Exactly(10))
+ .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() { ASSERT_TRUE(mController->ping().isOk()); }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+ // Connector was called only by the first thread to use the api.
+ ASSERT_EQ(1, mConnectCounter);
+}
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
new file mode 100644
index 0000000..dd71a6a
--- /dev/null
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VibratorManagerHalWrapperAidlTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Log.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+#include "test_utils.h"
+
+using android::binder::Status;
+
+using android::hardware::vibrator::CompositeEffect;
+using android::hardware::vibrator::CompositePrimitive;
+using android::hardware::vibrator::Effect;
+using android::hardware::vibrator::EffectStrength;
+using android::hardware::vibrator::IVibrator;
+using android::hardware::vibrator::IVibratorCallback;
+using android::hardware::vibrator::IVibratorManager;
+
+using namespace android;
+using namespace testing;
+
+class MockBinder : public BBinder {
+public:
+ MOCK_METHOD(status_t, linkToDeath,
+ (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
+ MOCK_METHOD(status_t, unlinkToDeath,
+ (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
+ wp<DeathRecipient>* outRecipient),
+ (override));
+ MOCK_METHOD(status_t, pingBinder, (), (override));
+};
+
+class MockIVibrator : public IVibrator {
+public:
+ MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
+ MOCK_METHOD(Status, off, (), (override));
+ MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
+ MOCK_METHOD(Status, perform,
+ (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
+ (override));
+ MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
+ MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
+ MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
+ MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
+ MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
+ MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
+ (override));
+ MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
+ MOCK_METHOD(Status, compose,
+ (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
+ (override));
+ MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
+ MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
+ MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
+ MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+ MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+ MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class MockIVibratorManager : public IVibratorManager {
+public:
+ MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
+ MOCK_METHOD(Status, getVibratorIds, (std::vector<int32_t> * ret), (override));
+ MOCK_METHOD(Status, getVibrator, (int32_t id, sp<IVibrator>* ret), (override));
+ MOCK_METHOD(Status, prepareSynced, (const std::vector<int32_t>& ids), (override));
+ MOCK_METHOD(Status, triggerSynced, (const sp<IVibratorCallback>& cb), (override));
+ MOCK_METHOD(Status, cancelSynced, (), (override));
+ MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
+ MOCK_METHOD(std::string, getInterfaceHash, (), (override));
+ MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class VibratorManagerHalWrapperAidlTest : public Test {
+public:
+ void SetUp() override {
+ mMockBinder = new StrictMock<MockBinder>();
+ mMockVibrator = new StrictMock<MockIVibrator>();
+ mMockHal = new StrictMock<MockIVibratorManager>();
+ mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
+ mWrapper = std::make_unique<vibrator::AidlManagerHalWrapper>(mMockScheduler, mMockHal);
+ ASSERT_NE(mWrapper, nullptr);
+ }
+
+protected:
+ std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
+ std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
+ sp<StrictMock<MockIVibrator>> mMockVibrator = nullptr;
+ sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+static const std::vector<int32_t> kVibratorIds = {1, 2};
+static constexpr int kVibratorId = 1;
+
+ACTION(TriggerCallback) {
+ if (arg0 != nullptr) {
+ arg0->onComplete();
+ }
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestPing) {
+ EXPECT_CALL(*mMockHal.get(), onAsBinder())
+ .Times(Exactly(2))
+ .WillRepeatedly(Return(mMockBinder.get()));
+ EXPECT_CALL(*mMockBinder.get(), pingBinder())
+ .Times(Exactly(2))
+ .WillOnce(Return(android::OK))
+ .WillRepeatedly(Return(android::DEAD_OBJECT));
+
+ ASSERT_TRUE(mWrapper->ping().isOk());
+ ASSERT_TRUE(mWrapper->ping().isFailed());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesDoesNotCacheFailedResult) {
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+
+ ASSERT_TRUE(mWrapper->getCapabilities().isUnsupported());
+ ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesCachesResult) {
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::ManagerCapabilities::SYNC, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsDoesNotCacheFailedResult) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ ASSERT_TRUE(mWrapper->getVibratorIds().isUnsupported());
+ ASSERT_TRUE(mWrapper->getVibratorIds().isFailed());
+
+ auto result = mWrapper->getVibratorIds();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(kVibratorIds, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsCachesResult) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.push_back(std::thread([&]() {
+ auto result = mWrapper->getVibratorIds();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(kVibratorIds, result.value());
+ }));
+ }
+ std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
+
+ auto result = mWrapper->getVibratorIds();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(kVibratorIds, result.value());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithValidIdReturnsController) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+ }
+
+ auto result = mWrapper->getVibrator(kVibratorId);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_NE(nullptr, result.value().get());
+ ASSERT_TRUE(result.value().get()->init());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithInvalidIdFails) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ ASSERT_TRUE(mWrapper->getVibrator(0).isFailed());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorRecoversVibratorPointer) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
+ .Times(Exactly(3))
+ .WillOnce(DoAll(SetArgPointee<1>(nullptr),
+ Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY))))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+ EXPECT_CALL(*mMockVibrator.get(), getCapabilities(_))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+
+ // Get vibrator controller is successful even if first getVibrator.
+ auto result = mWrapper->getVibrator(kVibratorId);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_NE(nullptr, result.value().get());
+
+ auto vibrator = result.value();
+ // First getVibrator call fails.
+ ASSERT_FALSE(vibrator->init());
+ // First and second getCapabilities calls fail, reload IVibrator with getVibrator.
+ ASSERT_FALSE(vibrator->getCapabilities().isOk());
+ // Third call to getCapabilities worked after IVibrator reloaded.
+ ASSERT_TRUE(vibrator->getCapabilities().isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestPrepareSynced) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
+ .Times(Exactly(2))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), prepareSynced(Eq(kVibratorIds)))
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(Return(Status()));
+
+ ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
+ ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isUnsupported());
+ ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isFailed());
+ ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestTriggerSyncedWithCallbackSupport) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
+ Return(Status())));
+ EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
+ .Times(Exactly(3))
+ .WillOnce(Return(
+ Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(DoAll(TriggerCallback(), Return(Status())));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->triggerSynced(callback).isUnsupported());
+ ASSERT_TRUE(mWrapper->triggerSynced(callback).isFailed());
+ ASSERT_TRUE(mWrapper->triggerSynced(callback).isOk());
+ ASSERT_EQ(1, *callbackCounter.get());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestTriggerSyncedWithoutCallbackSupport) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(
+ DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+ EXPECT_CALL(*mMockHal.get(), triggerSynced(Eq(nullptr)))
+ .Times(Exactly(1))
+ .WillRepeatedly(Return(Status()));
+ }
+
+ std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+ auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+ ASSERT_TRUE(mWrapper->triggerSynced(callback).isOk());
+ ASSERT_EQ(0, *callbackCounter.get());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSynced) {
+ EXPECT_CALL(*mMockHal.get(), cancelSynced())
+ .Times(Exactly(3))
+ .WillOnce(
+ Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
+ .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
+ .WillRepeatedly(Return(Status()));
+
+ ASSERT_TRUE(mWrapper->cancelSynced().isUnsupported());
+ ASSERT_TRUE(mWrapper->cancelSynced().isFailed());
+ ASSERT_TRUE(mWrapper->cancelSynced().isOk());
+}
+
+TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSyncedReloadsAllControllers) {
+ EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
+ .Times(Exactly(2))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+
+ EXPECT_CALL(*mMockHal.get(), cancelSynced()).Times(Exactly(1)).WillRepeatedly(Return(Status()));
+
+ ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
+ ASSERT_TRUE(mWrapper->cancelSynced().isOk());
+}
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
index d5520a1..6c2aabb 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
@@ -78,6 +78,12 @@
mWrapper->tryReconnect();
}
+TEST_F(VibratorManagerHalWrapperLegacyTest, TestGetCapabilities) {
+ auto result = mWrapper->getCapabilities();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(vibrator::ManagerCapabilities::NONE, result.value());
+}
+
TEST_F(VibratorManagerHalWrapperLegacyTest, TestGetVibratorIds) {
std::vector<int32_t> expectedIds;
expectedIds.push_back(0);