Merge "Reenable some tests for VK RenderEngine" into main
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index bb3faaf..6686717 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
package android.view;
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h" rust_type "nativewindow::Surface";
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index ae56cb0..07908ba 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -40,15 +40,12 @@
public:
static sp<BinderCallback> setupTo(const sp<Looper>& looper) {
sp<BinderCallback> cb = sp<BinderCallback>::make();
+ cb->mLooper = looper;
- int binder_fd = -1;
- IPCThreadState::self()->setupPolling(&binder_fd);
- LOG_ALWAYS_FATAL_IF(binder_fd < 0, "Failed to setupPolling: %d", binder_fd);
+ IPCThreadState::self()->setupPolling(&cb->mBinderFd);
+ LOG_ALWAYS_FATAL_IF(cb->mBinderFd < 0, "Failed to setupPolling: %d", cb->mBinderFd);
- int ret = looper->addFd(binder_fd,
- Looper::POLL_CALLBACK,
- Looper::EVENT_INPUT,
- cb,
+ int ret = looper->addFd(cb->mBinderFd, Looper::POLL_CALLBACK, Looper::EVENT_INPUT, cb,
nullptr /*data*/);
LOG_ALWAYS_FATAL_IF(ret != 1, "Failed to add binder FD to Looper");
@@ -59,13 +56,26 @@
IPCThreadState::self()->handlePolledCommands();
return 1; // Continue receiving callbacks.
}
+
+ void repoll() {
+ if (!mLooper->repoll(mBinderFd)) {
+ ALOGE("Failed to repoll binder FD.");
+ }
+ }
+
+private:
+ sp<Looper> mLooper;
+ int mBinderFd = -1;
};
// LooperCallback for IClientCallback
class ClientCallbackCallback : public LooperCallback {
public:
- static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper, const sp<ServiceManager>& manager) {
+ static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper,
+ const sp<ServiceManager>& manager,
+ sp<BinderCallback> binderCallback) {
sp<ClientCallbackCallback> cb = sp<ClientCallbackCallback>::make(manager);
+ cb->mBinderCallback = binderCallback;
int fdTimer = timerfd_create(CLOCK_MONOTONIC, 0 /*flags*/);
LOG_ALWAYS_FATAL_IF(fdTimer < 0, "Failed to timerfd_create: fd: %d err: %d", fdTimer, errno);
@@ -102,12 +112,15 @@
}
mManager->handleClientCallbacks();
+ mBinderCallback->repoll(); // b/316829336
+
return 1; // Continue receiving callbacks.
}
private:
friend sp<ClientCallbackCallback>;
ClientCallbackCallback(const sp<ServiceManager>& manager) : mManager(manager) {}
sp<ServiceManager> mManager;
+ sp<BinderCallback> mBinderCallback;
};
int main(int argc, char** argv) {
@@ -139,8 +152,8 @@
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
- BinderCallback::setupTo(looper);
- ClientCallbackCallback::setupTo(looper, manager);
+ sp<BinderCallback> binderCallback = BinderCallback::setupTo(looper);
+ ClientCallbackCallback::setupTo(looper, manager, binderCallback);
#ifndef VENDORSERVICEMANAGER
if (!SetProperty("servicemanager.ready", "true")) {
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e1dc791..6b8e824 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -416,26 +416,36 @@
};
struct DisplayState {
- enum {
+ enum : uint32_t {
eSurfaceChanged = 0x01,
eLayerStackChanged = 0x02,
eDisplayProjectionChanged = 0x04,
eDisplaySizeChanged = 0x08,
- eFlagsChanged = 0x10
+ eFlagsChanged = 0x10,
+
+ eAllChanged = ~0u
};
+ // Not for direct use. Prefer constructor below for new displays.
DisplayState();
+
+ DisplayState(sp<IBinder> token, ui::LayerStack layerStack)
+ : what(eAllChanged),
+ token(std::move(token)),
+ layerStack(layerStack),
+ layerStackSpaceRect(Rect::INVALID_RECT),
+ orientedDisplaySpaceRect(Rect::INVALID_RECT) {}
+
void merge(const DisplayState& other);
void sanitize(int32_t permissions);
uint32_t what = 0;
uint32_t flags = 0;
sp<IBinder> token;
- sp<IGraphicBufferProducer> surface;
ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
- // These states define how layers are projected onto the physical display.
+ // These states define how layers are projected onto the physical or virtual display.
//
// Layers are first clipped to `layerStackSpaceRect'. They are then translated and
// scaled from `layerStackSpaceRect' to `orientedDisplaySpaceRect'. Finally, they are rotated
@@ -446,10 +456,17 @@
// will be scaled by a factor of 2 and translated by (20, 10). When orientation is 1, layers
// will be additionally rotated by 90 degrees around the origin clockwise and translated by (W,
// 0).
+ //
+ // Rect::INVALID_RECT sizes the space to the active resolution of the physical display, or the
+ // default dimensions of the virtual display surface.
+ //
ui::Rotation orientation = ui::ROTATION_0;
Rect layerStackSpaceRect = Rect::EMPTY_RECT;
Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
+ // Exclusive to virtual displays: The sink surface into which the virtual display is rendered,
+ // and an optional resolution that overrides its default dimensions.
+ sp<IGraphicBufferProducer> surface;
uint32_t width = 0;
uint32_t height = 0;
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index aab7df0..22ad834 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,6 +16,8 @@
extern crate nativewindow_bindgen as ffi;
+pub mod surface;
+
pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
use binder::{
@@ -210,7 +212,7 @@
}
impl Debug for HardwareBuffer {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("HardwareBuffer").field("id", &self.id()).finish()
}
}
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
new file mode 100644
index 0000000..c812612
--- /dev/null
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -0,0 +1,140 @@
+// Copyright (C) 2024 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 wrapper for `ANativeWindow` and related types.
+
+use binder::{
+ binder_impl::{BorrowedParcel, UnstructuredParcelable},
+ impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+ unstable_api::{status_result, AsNative},
+ StatusCode,
+};
+use nativewindow_bindgen::{
+ AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire, ANativeWindow_getFormat,
+ ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_readFromParcel,
+ ANativeWindow_release, ANativeWindow_writeToParcel,
+};
+use std::error::Error;
+use std::fmt::{self, Debug, Display, Formatter};
+use std::ptr::{null_mut, NonNull};
+
+/// Wrapper around an opaque C `ANativeWindow`.
+#[derive(PartialEq, Eq)]
+pub struct Surface(NonNull<ANativeWindow>);
+
+impl Surface {
+ /// Returns the current width in pixels of the window surface.
+ pub fn width(&self) -> Result<u32, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let width = unsafe { ANativeWindow_getWidth(self.0.as_ptr()) };
+ width.try_into().map_err(|_| ErrorCode(width))
+ }
+
+ /// Returns the current height in pixels of the window surface.
+ pub fn height(&self) -> Result<u32, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let height = unsafe { ANativeWindow_getHeight(self.0.as_ptr()) };
+ height.try_into().map_err(|_| ErrorCode(height))
+ }
+
+ /// Returns the current pixel format of the window surface.
+ pub fn format(&self) -> Result<AHardwareBuffer_Format::Type, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let format = unsafe { ANativeWindow_getFormat(self.0.as_ptr()) };
+ format.try_into().map_err(|_| ErrorCode(format))
+ }
+}
+
+impl Drop for Surface {
+ fn drop(&mut self) {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_release(self.0.as_ptr()) }
+ }
+}
+
+impl Debug for Surface {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("Surface")
+ .field("width", &self.width())
+ .field("height", &self.height())
+ .field("format", &self.format())
+ .finish()
+ }
+}
+
+impl Clone for Surface {
+ fn clone(&self) -> Self {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_acquire(self.0.as_ptr()) };
+ Self(self.0)
+ }
+}
+
+impl UnstructuredParcelable for Surface {
+ fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+ let status =
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_writeToParcel(self.0.as_ptr(), parcel.as_native_mut()) };
+ status_result(status)
+ }
+
+ fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+ let mut buffer = null_mut();
+
+ let status =
+ // SAFETY: Both pointers must be valid because they are obtained from references.
+ // `ANativeWindow_readFromParcel` doesn't store them or do anything else special
+ // with them. If it returns success then it will have allocated a new
+ // `ANativeWindow` and incremented the reference count, so we can use it until we
+ // release it.
+ unsafe { ANativeWindow_readFromParcel(parcel.as_native(), &mut buffer) };
+
+ status_result(status)?;
+
+ Ok(Self(
+ NonNull::new(buffer)
+ .expect("ANativeWindow_readFromParcel returned success but didn't allocate buffer"),
+ ))
+ }
+}
+
+impl_deserialize_for_unstructured_parcelable!(Surface);
+impl_serialize_for_unstructured_parcelable!(Surface);
+
+// SAFETY: The underlying *ANativeWindow can be moved between threads.
+unsafe impl Send for Surface {}
+
+/// An error code returned by methods on [`Surface`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct ErrorCode(i32);
+
+impl Error for ErrorCode {}
+
+impl Display for ErrorCode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Error {}", self.0)
+ }
+}
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index 4525a42..5689f7d 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -19,3 +19,4 @@
#include <android/hardware_buffer_aidl.h>
#include <android/hdr_metadata.h>
#include <android/native_window.h>
+#include <android/native_window_aidl.h>
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 72c6f1a..1ada5e5 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -118,6 +118,15 @@
}
}
+void InputFilter::setAccessibilitySlowKeysThreshold(nsecs_t threshold) {
+ std::scoped_lock _l(mLock);
+
+ if (mConfig.slowKeysThresholdNs != threshold) {
+ mConfig.slowKeysThresholdNs = threshold;
+ notifyConfigurationChangedLocked();
+ }
+}
+
void InputFilter::setAccessibilityStickyKeysEnabled(bool enabled) {
std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 153d29d..4ddc9f4 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -35,6 +35,7 @@
*/
virtual void dump(std::string& dump) = 0;
virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+ virtual void setAccessibilitySlowKeysThreshold(nsecs_t threshold) = 0;
virtual void setAccessibilityStickyKeysEnabled(bool enabled) = 0;
};
@@ -61,6 +62,7 @@
void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+ void setAccessibilitySlowKeysThreshold(nsecs_t threshold) override;
void setAccessibilityStickyKeysEnabled(bool enabled) override;
void dump(std::string& dump) override;
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index a8759b7..6c31442 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -17,6 +17,11 @@
#define LOG_TAG "InputFilterCallbacks"
#include "InputFilterCallbacks.h"
+#include <aidl/com/android/server/inputflinger/BnInputThread.h>
+#include <android/binder_auto_utils.h>
+#include <utils/StrongPointer.h>
+#include <utils/Thread.h>
+#include <functional>
namespace android {
@@ -29,6 +34,47 @@
event.scanCode, event.metaState, event.downTime);
}
+namespace {
+
+using namespace aidl::com::android::server::inputflinger;
+
+class InputFilterThreadImpl : public Thread {
+public:
+ explicit InputFilterThreadImpl(std::function<void()> loop)
+ : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
+
+ ~InputFilterThreadImpl() {}
+
+private:
+ std::function<void()> mThreadLoop;
+
+ bool threadLoop() override {
+ mThreadLoop();
+ return true;
+ }
+};
+
+class InputFilterThread : public BnInputThread {
+public:
+ InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
+ mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
+ mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+ }
+
+ ndk::ScopedAStatus finish() override {
+ mThread->requestExit();
+ return ndk::ScopedAStatus::ok();
+ }
+
+private:
+ sp<Thread> mThread;
+ std::shared_ptr<IInputThreadCallback> mCallback;
+
+ void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
+};
+
+} // namespace
+
InputFilterCallbacks::InputFilterCallbacks(InputListenerInterface& listener,
InputFilterPolicyInterface& policy)
: mNextListener(listener), mPolicy(policy) {}
@@ -49,6 +95,13 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus InputFilterCallbacks::createInputFilterThread(
+ const std::shared_ptr<IInputThreadCallback>& callback,
+ std::shared_ptr<IInputThread>* aidl_return) {
+ *aidl_return = ndk::SharedRefBase::make<InputFilterThread>(callback);
+ return ndk::ScopedAStatus::ok();
+}
+
uint32_t InputFilterCallbacks::getModifierState() {
std::scoped_lock _l(mLock);
return mStickyModifierState.modifierState;
diff --git a/services/inputflinger/InputFilterCallbacks.h b/services/inputflinger/InputFilterCallbacks.h
index 31c160a..a74955b 100644
--- a/services/inputflinger/InputFilterCallbacks.h
+++ b/services/inputflinger/InputFilterCallbacks.h
@@ -19,6 +19,7 @@
#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
#include <android/binder_auto_utils.h>
#include <utils/Mutex.h>
+#include <memory>
#include <mutex>
#include "InputFilterPolicyInterface.h"
#include "InputListener.h"
@@ -31,6 +32,9 @@
using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::IInputThread;
+using IInputThreadCallback =
+ aidl::com::android::server::inputflinger::IInputThread::IInputThreadCallback;
class InputFilterCallbacks : public IInputFilter::BnInputFilterCallbacks {
public:
@@ -53,6 +57,9 @@
ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override;
ndk::ScopedAStatus onModifierStateChanged(int32_t modifierState,
int32_t lockedModifierState) override;
+ ndk::ScopedAStatus createInputFilterThread(
+ const std::shared_ptr<IInputThreadCallback>& callback,
+ std::shared_ptr<IInputThread>* aidl_return) override;
};
} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 2921d30..994d1c4 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -17,6 +17,8 @@
package com.android.server.inputflinger;
import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.IInputThread;
+import com.android.server.inputflinger.IInputThread.IInputThreadCallback;
import com.android.server.inputflinger.InputFilterConfiguration;
import com.android.server.inputflinger.KeyEvent;
@@ -36,6 +38,9 @@
/** Sends back modifier state */
void onModifierStateChanged(int modifierState, int lockedModifierState);
+
+ /** Creates an Input filter thread */
+ IInputThread createInputFilterThread(in IInputThreadCallback callback);
}
/** Returns if InputFilter is enabled */
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
new file mode 100644
index 0000000..2f6b8fc
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputflinger;
+
+/** Interface to handle and run things on an InputThread
+ * Exposes main functionality of InputThread.h to rust which internally used system/core/libutils
+ * infrastructure.
+ *
+ * <p>
+ * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+ * have JNI support and can't call into Java policy that we use currently. libutils provided
+ * Thread.h also recommends against using std::thread and using the provided infrastructure that
+ * already provides way of attaching JniEnv to the created thread. So, we are using this interface
+ * to expose the InputThread infrastructure to rust.
+ * </p>
+ * TODO(b/321769871): Implement the threading infrastructure with JniEnv support in rust
+ */
+interface IInputThread {
+ /** Finish input thread (if not running, this call does nothing) */
+ void finish();
+
+ /** Callbacks from C++ to call into inputflinger rust components */
+ interface IInputThreadCallback {
+ /**
+ * The created thread will keep looping and calling this function.
+ * It's the responsibility of RUST component to appropriately put the thread to sleep and
+ * wake according to the use case.
+ */
+ void loopOnce();
+ }
+}
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
index 38b1612..9984a6a 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -22,6 +22,8 @@
parcelable InputFilterConfiguration {
// Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
long bounceKeysThresholdNs;
- // If sticky keys filter is enabled
+ // If sticky keys filter is enabled (check sticky_keys_filter.rs)
boolean stickyKeysEnabled;
+ // Threshold value for Slow keys filter (check slow_keys_filter.rs)
+ long slowKeysThresholdNs;
}
\ No newline at end of file
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 2803805..9e6dbe4 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -42,6 +42,7 @@
"libbinder_rs",
"liblog_rust",
"liblogger",
+ "libnix",
],
host_supported: true,
}
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
index 894b881..2d5039a 100644
--- a/services/inputflinger/rust/bounce_keys_filter.rs
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -118,6 +118,10 @@
}
self.next.notify_devices_changed(device_infos);
}
+
+ fn destroy(&mut self) {
+ self.next.destroy();
+ }
}
#[cfg(test)]
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index e94a71f..a544fa3 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -22,11 +22,14 @@
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
DeviceInfo::DeviceInfo,
IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+ IInputThread::{IInputThread, IInputThreadCallback::IInputThreadCallback},
InputFilterConfiguration::InputFilterConfiguration,
KeyEvent::KeyEvent,
};
use crate::bounce_keys_filter::BounceKeysFilter;
+use crate::input_filter_thread::InputFilterThread;
+use crate::slow_keys_filter::SlowKeysFilter;
use crate::sticky_keys_filter::StickyKeysFilter;
use log::{error, info};
use std::sync::{Arc, Mutex, RwLock};
@@ -35,6 +38,7 @@
pub trait Filter {
fn notify_key(&mut self, event: &KeyEvent);
fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+ fn destroy(&mut self);
}
struct InputFilterState {
@@ -50,6 +54,7 @@
// Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
// guarded by Mutex for thread safety
state: Mutex<InputFilterState>,
+ input_filter_thread: InputFilterThread,
}
impl Interface for InputFilter {}
@@ -67,7 +72,11 @@
first_filter: Box<dyn Filter + Send + Sync>,
callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
) -> InputFilter {
- Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
+ Self {
+ callbacks: callbacks.clone(),
+ state: Mutex::new(InputFilterState { first_filter, enabled: false }),
+ input_filter_thread: InputFilterThread::new(InputFilterThreadCreator::new(callbacks)),
+ }
}
}
@@ -89,24 +98,36 @@
}
fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
- let mut state = self.state.lock().unwrap();
- let mut first_filter: Box<dyn Filter + Send + Sync> =
- Box::new(BaseFilter::new(self.callbacks.clone()));
- if config.stickyKeysEnabled {
- first_filter = Box::new(StickyKeysFilter::new(
- first_filter,
- ModifierStateListener::new(self.callbacks.clone()),
- ));
- state.enabled = true;
- info!("Sticky keys filter is installed");
+ {
+ let mut state = self.state.lock().unwrap();
+ state.first_filter.destroy();
+ let mut first_filter: Box<dyn Filter + Send + Sync> =
+ Box::new(BaseFilter::new(self.callbacks.clone()));
+ if config.stickyKeysEnabled {
+ first_filter = Box::new(StickyKeysFilter::new(
+ first_filter,
+ ModifierStateListener::new(self.callbacks.clone()),
+ ));
+ state.enabled = true;
+ info!("Sticky keys filter is installed");
+ }
+ if config.slowKeysThresholdNs > 0 {
+ first_filter = Box::new(SlowKeysFilter::new(
+ first_filter,
+ config.slowKeysThresholdNs,
+ self.input_filter_thread.clone(),
+ ));
+ state.enabled = true;
+ info!("Slow keys filter is installed");
+ }
+ if config.bounceKeysThresholdNs > 0 {
+ first_filter =
+ Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+ state.enabled = true;
+ info!("Bounce keys filter is installed");
+ }
+ state.first_filter = first_filter;
}
- if config.bounceKeysThresholdNs > 0 {
- first_filter =
- Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
- state.enabled = true;
- info!("Bounce keys filter is installed");
- }
- state.first_filter = first_filter;
Result::Ok(())
}
}
@@ -132,27 +153,51 @@
fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
// do nothing
}
+
+ fn destroy(&mut self) {
+ // do nothing
+ }
}
-pub struct ModifierStateListener {
- callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
-}
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code onModifierStateChanged()} method of the callback.
+#[derive(Clone)]
+pub struct ModifierStateListener(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
impl ModifierStateListener {
- /// Create a new InputFilter instance.
pub fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> ModifierStateListener {
- Self { callbacks }
+ Self(callbacks)
}
pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
let _ = self
- .callbacks
+ .0
.read()
.unwrap()
.onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
}
}
+/// This struct wraps around IInputFilterCallbacks restricting access to only
+/// {@code createInputFilterThread()} method of the callback.
+#[derive(Clone)]
+pub struct InputFilterThreadCreator(Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>);
+
+impl InputFilterThreadCreator {
+ pub fn new(
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+ ) -> InputFilterThreadCreator {
+ Self(callbacks)
+ }
+
+ pub fn create(
+ &self,
+ input_thread_callback: &Strong<dyn IInputThreadCallback>,
+ ) -> Strong<dyn IInputThread> {
+ self.0.read().unwrap().createInputFilterThread(input_thread_callback).unwrap()
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::input_filter::{
@@ -218,7 +263,7 @@
let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
bounceKeysThresholdNs: 100,
- stickyKeysEnabled: false,
+ ..Default::default()
});
assert!(result.is_ok());
let result = input_filter.isEnabled();
@@ -231,8 +276,8 @@
let test_callbacks = TestCallbacks::new();
let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
- bounceKeysThresholdNs: 0,
stickyKeysEnabled: true,
+ ..Default::default()
});
assert!(result.is_ok());
let result = input_filter.isEnabled();
@@ -240,6 +285,33 @@
assert!(result.unwrap());
}
+ #[test]
+ fn test_notify_configuration_changed_enabled_slow_keys() {
+ let test_callbacks = TestCallbacks::new();
+ let input_filter = InputFilter::new(Strong::new(Box::new(test_callbacks)));
+ let result = input_filter.notifyConfigurationChanged(&InputFilterConfiguration {
+ slowKeysThresholdNs: 100,
+ ..Default::default()
+ });
+ assert!(result.is_ok());
+ let result = input_filter.isEnabled();
+ assert!(result.is_ok());
+ assert!(result.unwrap());
+ }
+
+ #[test]
+ fn test_notify_configuration_changed_destroys_existing_filters() {
+ let test_filter = TestFilter::new();
+ let test_callbacks = TestCallbacks::new();
+ let input_filter = InputFilter::create_input_filter(
+ Box::new(test_filter.clone()),
+ Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
+ );
+ let _ = input_filter
+ .notifyConfigurationChanged(&InputFilterConfiguration { ..Default::default() });
+ assert!(test_filter.is_destroy_called());
+ }
+
fn create_key_event() -> KeyEvent {
KeyEvent {
id: 1,
@@ -271,6 +343,7 @@
struct TestFilterInner {
is_device_changed_called: bool,
last_event: Option<KeyEvent>,
+ is_destroy_called: bool,
}
#[derive(Default, Clone)]
@@ -296,6 +369,10 @@
pub fn is_device_changed_called(&self) -> bool {
self.0.read().unwrap().is_device_changed_called
}
+
+ pub fn is_destroy_called(&self) -> bool {
+ self.0.read().unwrap().is_destroy_called
+ }
}
impl Filter for TestFilter {
@@ -305,14 +382,19 @@
fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
self.inner().is_device_changed_called = true;
}
+ fn destroy(&mut self) {
+ self.inner().is_destroy_called = true;
+ }
}
}
#[cfg(test)]
pub mod test_callbacks {
- use binder::Interface;
+ use binder::{BinderFeatures, Interface, Strong};
use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
- IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent,
+ IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+ IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
+ KeyEvent::KeyEvent,
};
use std::sync::{Arc, RwLock, RwLockWriteGuard};
@@ -321,6 +403,7 @@
last_modifier_state: u32,
last_locked_modifier_state: u32,
last_event: Option<KeyEvent>,
+ test_thread: Option<TestThread>,
}
#[derive(Default, Clone)]
@@ -354,6 +437,17 @@
pub fn get_last_locked_modifier_state(&self) -> u32 {
self.0.read().unwrap().last_locked_modifier_state
}
+
+ pub fn is_thread_created(&self) -> bool {
+ self.0.read().unwrap().test_thread.is_some()
+ }
+
+ pub fn is_thread_finished(&self) -> bool {
+ if let Some(test_thread) = &self.0.read().unwrap().test_thread {
+ return test_thread.is_finish_called();
+ }
+ false
+ }
}
impl IInputFilterCallbacks for TestCallbacks {
@@ -371,5 +465,45 @@
self.inner().last_locked_modifier_state = locked_modifier_state as u32;
Result::Ok(())
}
+
+ fn createInputFilterThread(
+ &self,
+ _callback: &Strong<dyn IInputThreadCallback>,
+ ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
+ let test_thread = TestThread::new();
+ self.inner().test_thread = Some(test_thread.clone());
+ Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
+ }
+ }
+
+ #[derive(Default)]
+ struct TestThreadInner {
+ is_finish_called: bool,
+ }
+
+ #[derive(Default, Clone)]
+ struct TestThread(Arc<RwLock<TestThreadInner>>);
+
+ impl Interface for TestThread {}
+
+ impl TestThread {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
+ self.0.write().unwrap()
+ }
+
+ pub fn is_finish_called(&self) -> bool {
+ self.0.read().unwrap().is_finish_called
+ }
+ }
+
+ impl IInputThread for TestThread {
+ fn finish(&self) -> binder::Result<()> {
+ self.inner().is_finish_called = true;
+ Result::Ok(())
+ }
}
}
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
new file mode 100644
index 0000000..2d503ae
--- /dev/null
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Input filter thread implementation in rust.
+//! Using IInputFilter.aidl interface to create ever looping thread with JNI support, rest of
+//! thread handling is done from rust side.
+//!
+//! NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
+//! have JNI support and can't call into Java policy that we use currently. libutils provided
+//! Thread.h also recommends against using std::thread and using the provided infrastructure that
+//! already provides way of attaching JniEnv to the created thread. So, we are using an AIDL
+//! interface to expose the InputThread infrastructure to rust.
+
+use crate::input_filter::InputFilterThreadCreator;
+use binder::{BinderFeatures, Interface, Strong};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::IInputThread::{
+ IInputThread, IInputThreadCallback::BnInputThreadCallback,
+ IInputThreadCallback::IInputThreadCallback,
+};
+use log::{debug, error};
+use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+use std::sync::{Arc, RwLock, RwLockWriteGuard};
+use std::time::Duration;
+use std::{thread, thread::Thread};
+
+/// Interface to receive callback from Input filter thread
+pub trait ThreadCallback {
+ /// Calls back after the requested timeout expires.
+ /// {@see InputFilterThread.request_timeout_at_time(...)}
+ ///
+ /// NOTE: In case of multiple requests, the timeout request which is earliest in time, will be
+ /// fulfilled and notified to all the listeners. It's up to the listeners to re-request another
+ /// timeout in the future.
+ fn notify_timeout_expired(&self, when_nanos: i64);
+ /// Unique name for the listener, which will be used to uniquely identify the listener.
+ fn name(&self) -> &str;
+}
+
+#[derive(Clone)]
+pub struct InputFilterThread {
+ thread_creator: InputFilterThreadCreator,
+ thread_callback_handler: ThreadCallbackHandler,
+ inner: Arc<RwLock<InputFilterThreadInner>>,
+}
+
+struct InputFilterThreadInner {
+ cpp_thread: Option<Strong<dyn IInputThread>>,
+ looper: Option<Thread>,
+ next_timeout: i64,
+ is_finishing: bool,
+}
+
+impl InputFilterThread {
+ /// Create a new InputFilterThread instance.
+ /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
+ pub fn new(thread_creator: InputFilterThreadCreator) -> InputFilterThread {
+ Self {
+ thread_creator,
+ thread_callback_handler: ThreadCallbackHandler::new(),
+ inner: Arc::new(RwLock::new(InputFilterThreadInner {
+ cpp_thread: None,
+ looper: None,
+ next_timeout: i64::MAX,
+ is_finishing: false,
+ })),
+ }
+ }
+
+ /// Listener requesting a timeout in future will receive a callback at or before the requested
+ /// time on the input filter thread.
+ /// {@see ThreadCallback.notify_timeout_expired(...)}
+ pub fn request_timeout_at_time(&self, when_nanos: i64) {
+ let filter_thread = &mut self.filter_thread();
+ if when_nanos < filter_thread.next_timeout {
+ filter_thread.next_timeout = when_nanos;
+ if let Some(looper) = &filter_thread.looper {
+ looper.unpark();
+ }
+ }
+ }
+
+ /// Registers a callback listener.
+ ///
+ /// NOTE: If a listener with the same name already exists when registering using
+ /// {@see InputFilterThread.register_thread_callback(...)}, we will ignore the listener. You
+ /// must clear any previously registered listeners using
+ /// {@see InputFilterThread.unregister_thread_callback(...) before registering the new listener.
+ ///
+ /// NOTE: Also, registering a callback will start the looper if not already started.
+ pub fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+ self.thread_callback_handler.register_thread_callback(callback);
+ self.start();
+ }
+
+ /// Unregisters a callback listener.
+ ///
+ /// NOTE: Unregistering a callback will stop the looper if not other callback registered.
+ pub fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+ self.thread_callback_handler.unregister_thread_callback(callback);
+ // Stop the thread if no registered callbacks exist. We will recreate the thread when new
+ // callbacks are registered.
+ let has_callbacks = self.thread_callback_handler.has_callbacks();
+ if !has_callbacks {
+ self.stop();
+ }
+ }
+
+ fn start(&self) {
+ debug!("InputFilterThread: start thread");
+ let filter_thread = &mut self.filter_thread();
+ if filter_thread.cpp_thread.is_none() {
+ filter_thread.cpp_thread = Some(self.thread_creator.create(
+ &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+ ));
+ filter_thread.looper = None;
+ filter_thread.is_finishing = false;
+ }
+ }
+
+ fn stop(&self) {
+ debug!("InputFilterThread: stop thread");
+ let filter_thread = &mut self.filter_thread();
+ filter_thread.is_finishing = true;
+ if let Some(looper) = &filter_thread.looper {
+ looper.unpark();
+ }
+ if let Some(cpp_thread) = &filter_thread.cpp_thread {
+ let _ = cpp_thread.finish();
+ }
+ // Clear all references
+ filter_thread.cpp_thread = None;
+ filter_thread.looper = None;
+ }
+
+ fn loop_once(&self, now: i64) {
+ let mut wake_up_time = i64::MAX;
+ let mut timeout_expired = false;
+ {
+ // acquire thread lock
+ let filter_thread = &mut self.filter_thread();
+ if filter_thread.is_finishing {
+ // Thread is finishing so don't block processing on it and let it loop.
+ return;
+ }
+ if filter_thread.next_timeout != i64::MAX {
+ if filter_thread.next_timeout <= now {
+ timeout_expired = true;
+ filter_thread.next_timeout = i64::MAX;
+ } else {
+ wake_up_time = filter_thread.next_timeout;
+ }
+ }
+ if filter_thread.looper.is_none() {
+ filter_thread.looper = Some(std::thread::current());
+ }
+ } // release thread lock
+ if timeout_expired {
+ self.thread_callback_handler.notify_timeout_expired(now);
+ }
+ if wake_up_time == i64::MAX {
+ thread::park();
+ } else {
+ let duration_now = Duration::from_nanos(now as u64);
+ let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+ thread::park_timeout(duration_wake_up - duration_now);
+ }
+ }
+
+ fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
+ self.inner.write().unwrap()
+ }
+}
+
+impl Interface for InputFilterThread {}
+
+impl IInputThreadCallback for InputFilterThread {
+ fn loopOnce(&self) -> binder::Result<()> {
+ self.loop_once(clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds());
+ Result::Ok(())
+ }
+}
+
+#[derive(Default, Clone)]
+struct ThreadCallbackHandler(Arc<RwLock<ThreadCallbackHandlerInner>>);
+
+#[derive(Default)]
+struct ThreadCallbackHandlerInner {
+ callbacks: Vec<Box<dyn ThreadCallback + Send + Sync>>,
+}
+
+impl ThreadCallbackHandler {
+ fn new() -> Self {
+ Default::default()
+ }
+
+ fn has_callbacks(&self) -> bool {
+ !&self.0.read().unwrap().callbacks.is_empty()
+ }
+
+ fn register_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+ let callbacks = &mut self.0.write().unwrap().callbacks;
+ if callbacks.iter().any(|x| x.name() == callback.name()) {
+ error!(
+ "InputFilterThread: register_thread_callback, callback {:?} already exists!",
+ callback.name()
+ );
+ return;
+ }
+ debug!(
+ "InputFilterThread: register_thread_callback, callback {:?} added!",
+ callback.name()
+ );
+ callbacks.push(callback);
+ }
+
+ fn unregister_thread_callback(&self, callback: Box<dyn ThreadCallback + Send + Sync>) {
+ let callbacks = &mut self.0.write().unwrap().callbacks;
+ if let Some(index) = callbacks.iter().position(|x| x.name() == callback.name()) {
+ callbacks.remove(index);
+ debug!(
+ "InputFilterThread: unregister_thread_callback, callback {:?} removed!",
+ callback.name()
+ );
+ return;
+ }
+ error!(
+ "InputFilterThread: unregister_thread_callback, callback {:?} doesn't exist",
+ callback.name()
+ );
+ }
+
+ fn notify_timeout_expired(&self, when_nanos: i64) {
+ let callbacks = &self.0.read().unwrap().callbacks;
+ for callback in callbacks.iter() {
+ callback.notify_timeout_expired(when_nanos);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_filter::test_callbacks::TestCallbacks;
+ use crate::input_filter_thread::{
+ test_thread::TestThread, test_thread_callback::TestThreadCallback,
+ };
+
+ #[test]
+ fn test_register_callback_creates_cpp_thread() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread_callback = TestThreadCallback::new();
+ test_thread.register_thread_callback(test_thread_callback);
+ assert!(test_callbacks.is_thread_created());
+ }
+
+ #[test]
+ fn test_unregister_callback_finishes_cpp_thread() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread_callback = TestThreadCallback::new();
+ test_thread.register_thread_callback(test_thread_callback.clone());
+ test_thread.unregister_thread_callback(test_thread_callback);
+ assert!(test_callbacks.is_thread_finished());
+ }
+
+ #[test]
+ fn test_notify_timeout_called_after_timeout_expired() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread_callback = TestThreadCallback::new();
+ test_thread.register_thread_callback(test_thread_callback.clone());
+ test_thread.start_looper();
+
+ test_thread.request_timeout_at_time(500);
+ test_thread.dispatch_next();
+
+ test_thread.move_time_forward(500);
+
+ test_thread.stop_looper();
+ assert!(test_thread_callback.is_notify_timeout_called());
+ }
+
+ #[test]
+ fn test_notify_timeout_not_called_before_timeout_expired() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let test_thread_callback = TestThreadCallback::new();
+ test_thread.register_thread_callback(test_thread_callback.clone());
+ test_thread.start_looper();
+
+ test_thread.request_timeout_at_time(500);
+ test_thread.dispatch_next();
+
+ test_thread.move_time_forward(100);
+
+ test_thread.stop_looper();
+ assert!(!test_thread_callback.is_notify_timeout_called());
+ }
+}
+
+#[cfg(test)]
+pub mod test_thread {
+
+ use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+ use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+ use binder::Strong;
+ use std::sync::{
+ atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
+ };
+ use std::time::Duration;
+
+ #[derive(Clone)]
+ pub struct TestThread {
+ input_thread: InputFilterThread,
+ inner: Arc<RwLock<TestThreadInner>>,
+ exit_flag: Arc<AtomicBool>,
+ now: Arc<AtomicI64>,
+ }
+
+ struct TestThreadInner {
+ join_handle: Option<std::thread::JoinHandle<()>>,
+ }
+
+ impl TestThread {
+ pub fn new(callbacks: TestCallbacks) -> TestThread {
+ Self {
+ input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
+ RwLock::new(Strong::new(Box::new(callbacks))),
+ ))),
+ inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
+ exit_flag: Arc::new(AtomicBool::new(false)),
+ now: Arc::new(AtomicI64::new(0)),
+ }
+ }
+
+ fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
+ self.inner.write().unwrap()
+ }
+
+ pub fn get_input_thread(&self) -> InputFilterThread {
+ self.input_thread.clone()
+ }
+
+ pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
+ self.input_thread.register_thread_callback(Box::new(thread_callback));
+ }
+
+ pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
+ self.input_thread.unregister_thread_callback(Box::new(thread_callback));
+ }
+
+ pub fn start_looper(&self) {
+ self.exit_flag.store(false, Ordering::Relaxed);
+ let clone = self.clone();
+ let join_handle = std::thread::Builder::new()
+ .name("test_thread".to_string())
+ .spawn(move || {
+ while !clone.exit_flag.load(Ordering::Relaxed) {
+ clone.loop_once();
+ }
+ })
+ .unwrap();
+ self.inner().join_handle = Some(join_handle);
+ // Sleep until the looper thread starts
+ std::thread::sleep(Duration::from_millis(10));
+ }
+
+ pub fn stop_looper(&self) {
+ self.exit_flag.store(true, Ordering::Relaxed);
+ {
+ let mut inner = self.inner();
+ if let Some(join_handle) = &inner.join_handle {
+ join_handle.thread().unpark();
+ }
+ inner.join_handle.take().map(std::thread::JoinHandle::join);
+ inner.join_handle = None;
+ }
+ self.exit_flag.store(false, Ordering::Relaxed);
+ }
+
+ pub fn move_time_forward(&self, value: i64) {
+ let _ = self.now.fetch_add(value, Ordering::Relaxed);
+ self.dispatch_next();
+ }
+
+ pub fn dispatch_next(&self) {
+ if let Some(join_handle) = &self.inner().join_handle {
+ join_handle.thread().unpark();
+ }
+ // Sleep until the looper thread runs a loop
+ std::thread::sleep(Duration::from_millis(10));
+ }
+
+ fn loop_once(&self) {
+ self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
+ }
+
+ pub fn request_timeout_at_time(&self, when_nanos: i64) {
+ self.input_thread.request_timeout_at_time(when_nanos);
+ }
+ }
+}
+
+#[cfg(test)]
+pub mod test_thread_callback {
+ use crate::input_filter_thread::ThreadCallback;
+ use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+ #[derive(Default)]
+ struct TestThreadCallbackInner {
+ is_notify_timeout_called: bool,
+ }
+
+ #[derive(Default, Clone)]
+ pub struct TestThreadCallback(Arc<RwLock<TestThreadCallbackInner>>);
+
+ impl TestThreadCallback {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ fn inner(&self) -> RwLockWriteGuard<'_, TestThreadCallbackInner> {
+ self.0.write().unwrap()
+ }
+
+ pub fn is_notify_timeout_called(&self) -> bool {
+ self.0.read().unwrap().is_notify_timeout_called
+ }
+ }
+
+ impl ThreadCallback for TestThreadCallback {
+ fn notify_timeout_expired(&self, _when_nanos: i64) {
+ self.inner().is_notify_timeout_called = true;
+ }
+ fn name(&self) -> &str {
+ "TestThreadCallback"
+ }
+ }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
index fa16898..da72134 100644
--- a/services/inputflinger/rust/lib.rs
+++ b/services/inputflinger/rust/lib.rs
@@ -21,6 +21,8 @@
mod bounce_keys_filter;
mod input_filter;
+mod input_filter_thread;
+mod slow_keys_filter;
mod sticky_keys_filter;
use crate::input_filter::InputFilter;
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
new file mode 100644
index 0000000..01165b5
--- /dev/null
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -0,0 +1,382 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Slow keys input filter implementation.
+//! Slow keys is an accessibility feature to aid users who have physical disabilities, that allows
+//! the user to specify the duration for which one must press-and-hold a key before the system
+//! accepts the keypress.
+use crate::input_filter::Filter;
+use crate::input_filter_thread::{InputFilterThread, ThreadCallback};
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::HashSet;
+use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
+
+#[derive(Debug)]
+struct OngoingKeyDown {
+ scancode: i32,
+ device_id: i32,
+ down_time: i64,
+}
+
+struct SlowKeysFilterInner {
+ next: Box<dyn Filter + Send + Sync>,
+ slow_key_threshold_ns: i64,
+ external_devices: HashSet<i32>,
+ // This tracks KeyEvents that are blocked by Slow keys filter and will be passed through if the
+ // press duration exceeds the slow keys threshold.
+ pending_down_events: Vec<KeyEvent>,
+ // This tracks KeyEvent streams that have press duration greater than the slow keys threshold,
+ // hence any future ACTION_DOWN (if repeats are handled on HW side) or ACTION_UP are allowed to
+ // pass through without waiting.
+ ongoing_down_events: Vec<OngoingKeyDown>,
+ input_filter_thread: InputFilterThread,
+}
+
+#[derive(Clone)]
+pub struct SlowKeysFilter(Arc<RwLock<SlowKeysFilterInner>>);
+
+impl SlowKeysFilter {
+ /// Create a new SlowKeysFilter instance.
+ pub fn new(
+ next: Box<dyn Filter + Send + Sync>,
+ slow_key_threshold_ns: i64,
+ input_filter_thread: InputFilterThread,
+ ) -> SlowKeysFilter {
+ let filter = Self(Arc::new(RwLock::new(SlowKeysFilterInner {
+ next,
+ slow_key_threshold_ns,
+ external_devices: HashSet::new(),
+ pending_down_events: Vec::new(),
+ ongoing_down_events: Vec::new(),
+ input_filter_thread: input_filter_thread.clone(),
+ })));
+ input_filter_thread.register_thread_callback(Box::new(filter.clone()));
+ filter
+ }
+
+ fn read_inner(&self) -> RwLockReadGuard<'_, SlowKeysFilterInner> {
+ self.0.read().unwrap()
+ }
+
+ fn write_inner(&self) -> RwLockWriteGuard<'_, SlowKeysFilterInner> {
+ self.0.write().unwrap()
+ }
+
+ fn request_next_callback(&self) {
+ let slow_filter = &self.read_inner();
+ if slow_filter.pending_down_events.is_empty() {
+ return;
+ }
+ if let Some(event) = slow_filter.pending_down_events.iter().min_by_key(|x| x.downTime) {
+ slow_filter.input_filter_thread.request_timeout_at_time(event.downTime);
+ }
+ }
+}
+
+impl Filter for SlowKeysFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ {
+ // acquire write lock
+ let mut slow_filter = self.write_inner();
+ if !(slow_filter.external_devices.contains(&event.deviceId)
+ && event.source == Source::KEYBOARD)
+ {
+ slow_filter.next.notify_key(event);
+ return;
+ }
+ // Pass all events through if key down has already been processed
+ // Do update the downtime before sending the events through
+ if let Some(index) = slow_filter
+ .ongoing_down_events
+ .iter()
+ .position(|x| x.device_id == event.deviceId && x.scancode == event.scanCode)
+ {
+ let mut new_event = *event;
+ new_event.downTime = slow_filter.ongoing_down_events[index].down_time;
+ slow_filter.next.notify_key(&new_event);
+ if event.action == KeyEventAction::UP {
+ slow_filter.ongoing_down_events.remove(index);
+ }
+ return;
+ }
+ match event.action {
+ KeyEventAction::DOWN => {
+ if slow_filter
+ .pending_down_events
+ .iter()
+ .any(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+ {
+ debug!("Dropping key down event since another pending down event exists");
+ return;
+ }
+ let mut pending_event = *event;
+ pending_event.downTime += slow_filter.slow_key_threshold_ns;
+ pending_event.eventTime = pending_event.downTime;
+ slow_filter.pending_down_events.push(pending_event);
+ }
+ KeyEventAction::UP => {
+ debug!("Dropping key up event due to insufficient press duration");
+ if let Some(index) = slow_filter
+ .pending_down_events
+ .iter()
+ .position(|x| x.deviceId == event.deviceId && x.scanCode == event.scanCode)
+ {
+ slow_filter.pending_down_events.remove(index);
+ }
+ }
+ _ => (),
+ }
+ } // release write lock
+ self.request_next_callback();
+ }
+
+ fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+ let mut slow_filter = self.write_inner();
+ slow_filter
+ .pending_down_events
+ .retain(|event| device_infos.iter().any(|x| event.deviceId == x.deviceId));
+ slow_filter
+ .ongoing_down_events
+ .retain(|event| device_infos.iter().any(|x| event.device_id == x.deviceId));
+ slow_filter.external_devices.clear();
+ for device_info in device_infos {
+ if device_info.external {
+ slow_filter.external_devices.insert(device_info.deviceId);
+ }
+ }
+ slow_filter.next.notify_devices_changed(device_infos);
+ }
+
+ fn destroy(&mut self) {
+ let mut slow_filter = self.write_inner();
+ slow_filter.input_filter_thread.unregister_thread_callback(Box::new(self.clone()));
+ slow_filter.next.destroy();
+ }
+}
+
+impl ThreadCallback for SlowKeysFilter {
+ fn notify_timeout_expired(&self, when_nanos: i64) {
+ {
+ // acquire write lock
+ let slow_filter = &mut self.write_inner();
+ for event in slow_filter.pending_down_events.clone() {
+ if event.downTime <= when_nanos {
+ slow_filter.next.notify_key(&event);
+ slow_filter.ongoing_down_events.push(OngoingKeyDown {
+ scancode: event.scanCode,
+ device_id: event.deviceId,
+ down_time: event.downTime,
+ });
+ }
+ }
+ slow_filter.pending_down_events.retain(|event| event.downTime > when_nanos);
+ } // release write lock
+ self.request_next_callback();
+ }
+
+ fn name(&self) -> &str {
+ "slow_keys_filter"
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
+ use crate::input_filter_thread::test_thread::TestThread;
+ use crate::slow_keys_filter::SlowKeysFilter;
+ use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+ };
+
+ static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+ id: 1,
+ deviceId: 1,
+ downTime: 0,
+ readTime: 0,
+ eventTime: 0,
+ source: Source::KEYBOARD,
+ displayId: 0,
+ policyFlags: 0,
+ action: KeyEventAction::DOWN,
+ flags: 0,
+ keyCode: 1,
+ scanCode: 0,
+ metaState: 0,
+ };
+
+ #[test]
+ fn test_is_notify_key_for_internal_keyboard_not_blocked() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_internal_device(
+ Box::new(next.clone()),
+ test_thread.clone(),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+ test_thread.start_looper();
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_for_external_stylus_not_blocked() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ test_thread.clone(),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+ test_thread.start_looper();
+
+ let event =
+ KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ test_thread.clone(),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+ test_thread.start_looper();
+
+ filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+ assert!(next.last_event().is_none());
+ test_thread.dispatch_next();
+
+ test_thread.move_time_forward(100);
+
+ test_thread.stop_looper();
+ assert_eq!(
+ next.last_event().unwrap(),
+ KeyEvent {
+ action: KeyEventAction::DOWN,
+ downTime: 100,
+ eventTime: 100,
+ ..BASE_KEY_EVENT
+ }
+ );
+ }
+
+ #[test]
+ fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ test_thread.clone(),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+ test_thread.start_looper();
+
+ filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+ test_thread.dispatch_next();
+
+ test_thread.move_time_forward(10);
+
+ filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
+ test_thread.dispatch_next();
+
+ test_thread.stop_looper();
+ assert!(next.last_event().is_none());
+ }
+
+ #[test]
+ fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
+ let test_callbacks = TestCallbacks::new();
+ let test_thread = TestThread::new(test_callbacks.clone());
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ test_thread.clone(),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+ test_thread.start_looper();
+
+ filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+ assert!(next.last_event().is_none());
+ test_thread.dispatch_next();
+
+ filter.notify_devices_changed(&[]);
+ test_thread.dispatch_next();
+
+ test_thread.move_time_forward(100);
+
+ test_thread.stop_looper();
+ assert!(next.last_event().is_none());
+ }
+
+ fn setup_filter_with_external_device(
+ next: Box<dyn Filter + Send + Sync>,
+ test_thread: TestThread,
+ device_id: i32,
+ threshold: i64,
+ ) -> SlowKeysFilter {
+ setup_filter_with_devices(
+ next,
+ test_thread,
+ &[DeviceInfo { deviceId: device_id, external: true }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_internal_device(
+ next: Box<dyn Filter + Send + Sync>,
+ test_thread: TestThread,
+ device_id: i32,
+ threshold: i64,
+ ) -> SlowKeysFilter {
+ setup_filter_with_devices(
+ next,
+ test_thread,
+ &[DeviceInfo { deviceId: device_id, external: false }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_devices(
+ next: Box<dyn Filter + Send + Sync>,
+ test_thread: TestThread,
+ devices: &[DeviceInfo],
+ threshold: i64,
+ ) -> SlowKeysFilter {
+ let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+ filter.notify_devices_changed(devices);
+ filter
+ }
+}
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index da581b8..6c2277c 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -142,6 +142,10 @@
}
self.next.notify_devices_changed(device_infos);
}
+
+ fn destroy(&mut self) {
+ self.next.destroy();
+ }
}
fn is_modifier_key(keycode: i32) -> bool {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index d3bbfac..f1f4a61 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -7739,7 +7739,7 @@
application->setDispatchingTimeout(100ms);
mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
ADISPLAY_ID_DEFAULT);
- mWindow->setFrame(Rect(0, 0, 30, 30));
+ mWindow->setFrame(Rect(0, 0, 100, 100));
mWindow->setDispatchingTimeout(100ms);
mWindow->setFocusable(true);
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index c049d7d..bc178bc 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -88,7 +88,7 @@
mConnectedHal = nullptr;
mHalConnector->reset();
}
- return result;
+ return std::move(result);
}
HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index c71c517..39748b8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -60,7 +60,7 @@
virtual void createClientCompositionCache(uint32_t cacheSize) = 0;
// Sends the brightness setting to HWC
- virtual void applyDisplayBrightness(const bool applyImmediately) = 0;
+ virtual void applyDisplayBrightness(bool applyImmediately) = 0;
protected:
~Display() = default;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index c53b461..2dc9a1a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -73,7 +73,7 @@
const compositionengine::DisplayColorProfileCreationArgs&) override;
void createRenderSurface(const compositionengine::RenderSurfaceCreationArgs&) override;
void createClientCompositionCache(uint32_t cacheSize) override;
- void applyDisplayBrightness(const bool applyImmediately) override;
+ void applyDisplayBrightness(bool applyImmediately) override;
void setSecure(bool secure) override;
// Internal helpers used by chooseCompositionStrategy()
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index d907bf5..6428d08 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -204,13 +204,12 @@
setReleasedLayers(std::move(releasedLayers));
}
-void Display::applyDisplayBrightness(const bool applyImmediately) {
- auto& hwc = getCompositionEngine().getHwComposer();
- const auto halDisplayId = HalDisplayId::tryCast(*getDisplayId());
- if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
- physicalDisplayId && getState().displayBrightness) {
+void Display::applyDisplayBrightness(bool applyImmediately) {
+ if (const auto displayId = ftl::Optional(getDisplayId()).and_then(PhysicalDisplayId::tryCast);
+ displayId && getState().displayBrightness) {
+ auto& hwc = getCompositionEngine().getHwComposer();
const status_t result =
- hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
+ hwc.setDisplayBrightness(*displayId, *getState().displayBrightness,
getState().displayBrightnessNits,
Hwc2::Composer::DisplayBrightnessOptions{
.applyImmediately = applyImmediately})
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 799d62c..4f81482 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -67,6 +67,7 @@
mActiveModeFpsTrace(concatId("ActiveModeFps")),
mRenderRateFpsTrace(concatId("RenderRateFps")),
mPhysicalOrientation(args.physicalOrientation),
+ mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode),
mIsPrimary(args.isPrimary),
mRequestedRefreshRate(args.requestedRefreshRate),
mRefreshRateSelector(std::move(args.refreshRateSelector)),
@@ -105,9 +106,7 @@
mCompositionDisplay->getRenderSurface()->initialize();
- if (const auto powerModeOpt = args.initialPowerMode) {
- setPowerMode(*powerModeOpt);
- }
+ setPowerMode(args.initialPowerMode);
// initialize the display orientation transform.
setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
@@ -172,6 +171,7 @@
}
void DisplayDevice::setPowerMode(hal::PowerMode mode) {
+ // TODO(b/241285876): Skip this for virtual displays.
if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
if (mStagedBrightness && mBrightness != mStagedBrightness) {
getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
@@ -181,33 +181,26 @@
getCompositionDisplay()->applyDisplayBrightness(true);
}
- if (mPowerMode) {
- *mPowerMode = mode;
- } else {
- mPowerMode.emplace("PowerMode -" + to_string(getId()), mode);
- }
+ mPowerMode = mode;
getCompositionDisplay()->setCompositionEnabled(isPoweredOn());
}
void DisplayDevice::tracePowerMode() {
- // assign the same value for tracing
- if (mPowerMode) {
- const hal::PowerMode powerMode = *mPowerMode;
- *mPowerMode = powerMode;
- }
+ // Assign the same value for tracing.
+ mPowerMode = mPowerMode.get();
}
void DisplayDevice::enableLayerCaching(bool enable) {
getCompositionDisplay()->setLayerCachingEnabled(enable);
}
-std::optional<hal::PowerMode> DisplayDevice::getPowerMode() const {
+hal::PowerMode DisplayDevice::getPowerMode() const {
return mPowerMode;
}
bool DisplayDevice::isPoweredOn() const {
- return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
+ return mPowerMode != hal::PowerMode::OFF;
}
void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 97b56a2..4ab6321 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -173,8 +173,8 @@
/* ------------------------------------------------------------------------
* Display power mode management.
*/
- std::optional<hardware::graphics::composer::hal::PowerMode> getPowerMode() const;
- void setPowerMode(hardware::graphics::composer::hal::PowerMode mode);
+ hardware::graphics::composer::hal::PowerMode getPowerMode() const;
+ void setPowerMode(hardware::graphics::composer::hal::PowerMode);
bool isPoweredOn() const;
void tracePowerMode();
@@ -271,9 +271,7 @@
ui::Rotation mOrientation = ui::ROTATION_0;
bool mIsOrientationChanged = false;
- // Allow nullopt as initial power mode.
- using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
- std::optional<TracedPowerMode> mPowerMode;
+ TracedOrdinal<hardware::graphics::composer::hal::PowerMode> mPowerMode;
std::optional<float> mStagedBrightness;
std::optional<float> mBrightness;
@@ -363,7 +361,8 @@
HdrCapabilities hdrCapabilities;
int32_t supportedPerFrameMetadata{0};
std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
- std::optional<hardware::graphics::composer::hal::PowerMode> initialPowerMode;
+ hardware::graphics::composer::hal::PowerMode initialPowerMode{
+ hardware::graphics::composer::hal::PowerMode::OFF};
bool isPrimary{false};
DisplayModeId activeModeId;
// Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp
index 155cf4d..a47348f 100644
--- a/services/surfaceflinger/FpsReporter.cpp
+++ b/services/surfaceflinger/FpsReporter.cpp
@@ -26,13 +26,12 @@
namespace android {
-FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
- std::unique_ptr<Clock> clock)
- : mFrameTimeline(frameTimeline), mFlinger(flinger), mClock(std::move(clock)) {
+FpsReporter::FpsReporter(frametimeline::FrameTimeline& frameTimeline, std::unique_ptr<Clock> clock)
+ : mFrameTimeline(frameTimeline), mClock(std::move(clock)) {
LOG_ALWAYS_FATAL_IF(mClock == nullptr, "Passed in null clock when constructing FpsReporter!");
}
-void FpsReporter::dispatchLayerFps() {
+void FpsReporter::dispatchLayerFps(const frontend::LayerHierarchy& layerHierarchy) {
const auto now = mClock->now();
if (now - mLastDispatch < kMinDispatchDuration) {
return;
@@ -52,31 +51,42 @@
}
std::unordered_set<int32_t> seenTasks;
- std::vector<std::pair<TrackedListener, sp<Layer>>> listenersAndLayersToReport;
+ std::vector<std::pair<TrackedListener, const frontend::LayerHierarchy*>>
+ listenersAndLayersToReport;
- mFlinger.mCurrentState.traverse([&](Layer* layer) {
- auto& currentState = layer->getDrawingState();
- if (currentState.metadata.has(gui::METADATA_TASK_ID)) {
- int32_t taskId = currentState.metadata.getInt32(gui::METADATA_TASK_ID, 0);
+ layerHierarchy.traverse([&](const frontend::LayerHierarchy& hierarchy,
+ const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+ if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+ return false;
+ }
+ const auto& metadata = hierarchy.getLayer()->metadata;
+ if (metadata.has(gui::METADATA_TASK_ID)) {
+ int32_t taskId = metadata.getInt32(gui::METADATA_TASK_ID, 0);
if (seenTasks.count(taskId) == 0) {
// localListeners is expected to be tiny
for (TrackedListener& listener : localListeners) {
if (listener.taskId == taskId) {
seenTasks.insert(taskId);
- listenersAndLayersToReport.push_back(
- {listener, sp<Layer>::fromExisting(layer)});
+ listenersAndLayersToReport.push_back({listener, &hierarchy});
break;
}
}
}
}
+ return true;
});
- for (const auto& [listener, layer] : listenersAndLayersToReport) {
+ for (const auto& [listener, hierarchy] : listenersAndLayersToReport) {
std::unordered_set<int32_t> layerIds;
- layer->traverse(LayerVector::StateSet::Current,
- [&](Layer* layer) { layerIds.insert(layer->getSequence()); });
+ hierarchy->traverse([&](const frontend::LayerHierarchy& hierarchy,
+ const frontend::LayerHierarchy::TraversalPath& traversalPath) {
+ if (traversalPath.variant == frontend::LayerHierarchy::Variant::Detached) {
+ return false;
+ }
+ layerIds.insert(static_cast<int32_t>(hierarchy.getLayer()->id));
+ return true;
+ });
listener.listener->onFpsReported(mFrameTimeline.computeFps(layerIds));
}
diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h
index 438b1aa..01f1e07 100644
--- a/services/surfaceflinger/FpsReporter.h
+++ b/services/surfaceflinger/FpsReporter.h
@@ -24,6 +24,7 @@
#include "Clock.h"
#include "FrameTimeline/FrameTimeline.h"
+#include "FrontEnd/LayerHierarchy.h"
#include "WpHash.h"
namespace android {
@@ -33,13 +34,13 @@
class FpsReporter : public IBinder::DeathRecipient {
public:
- FpsReporter(frametimeline::FrameTimeline& frameTimeline, SurfaceFlinger& flinger,
+ FpsReporter(frametimeline::FrameTimeline& frameTimeline,
std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
// Dispatches updated layer fps values for the registered listeners
// This method promotes Layer weak pointers and performs layer stack traversals, so mStateLock
// must be held when calling this method.
- void dispatchLayerFps() EXCLUDES(mMutex);
+ void dispatchLayerFps(const frontend::LayerHierarchy&) EXCLUDES(mMutex);
// Override for IBinder::DeathRecipient
void binderDied(const wp<IBinder>&) override;
@@ -58,7 +59,6 @@
};
frametimeline::FrameTimeline& mFrameTimeline;
- SurfaceFlinger& mFlinger;
static const constexpr std::chrono::steady_clock::duration kMinDispatchDuration =
std::chrono::milliseconds(500);
std::unique_ptr<Clock> mClock;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index fff97f7..c76d4bd 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -646,13 +646,13 @@
return Fps{};
}
const Display& display = *displayOpt;
- const nsecs_t threshold =
- display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
- const nsecs_t nextVsyncTime =
- display.schedulePtr->getTracker()
- .nextAnticipatedVSyncTimeFrom(currentExpectedPresentTime.ns() + threshold,
- currentExpectedPresentTime.ns());
- return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
+ const Duration threshold =
+ display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriod() / 2;
+ const TimePoint nextVsyncTime =
+ display.schedulePtr->vsyncDeadlineAfter(currentExpectedPresentTime + threshold,
+ currentExpectedPresentTime);
+ const Duration frameInterval = nextVsyncTime - currentExpectedPresentTime;
+ return Fps::fromPeriodNsecs(frameInterval.ns());
}
void Scheduler::resync() {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index db6a187..3491600 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -89,8 +89,12 @@
return period();
}
-TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint) const {
- return TimePoint::fromNs(mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns()));
+TimePoint VsyncSchedule::vsyncDeadlineAfter(TimePoint timePoint,
+ ftl::Optional<TimePoint> lastVsyncOpt) const {
+ return TimePoint::fromNs(
+ mTracker->nextAnticipatedVSyncTimeFrom(timePoint.ns(),
+ lastVsyncOpt.transform(
+ [](TimePoint t) { return t.ns(); })));
}
void VsyncSchedule::dump(std::string& out) const {
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 722ea0b..6f656d2 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -63,7 +63,8 @@
// IVsyncSource overrides:
Period period() const override;
- TimePoint vsyncDeadlineAfter(TimePoint) const override;
+ TimePoint vsyncDeadlineAfter(TimePoint,
+ ftl::Optional<TimePoint> lastVsyncOpt = {}) const override;
Period minFramePeriod() const override;
// Inform the schedule that the display mode changed the schedule needs to recalibrate
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
index 0154060..f0f7a87 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -16,13 +16,14 @@
#pragma once
+#include <ftl/optional.h>
#include <scheduler/Time.h>
namespace android::scheduler {
struct IVsyncSource {
virtual Period period() const = 0;
- virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+ virtual TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const = 0;
virtual Period minFramePeriod() const = 0;
protected:
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index a9abcaf..29711af 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -38,7 +38,9 @@
const TimePoint vsyncDeadline;
Period period() const override { return vsyncPeriod; }
- TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+ TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const override {
+ return vsyncDeadline;
+ }
Period minFramePeriod() const override { return framePeriod; }
};
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5455e10..51a11e2 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1952,6 +1952,7 @@
const char* const whence = __func__;
return ftl::Future(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
+ // TODO(b/241285876): Validate that the display is physical instead of failing later.
if (const auto display = getDisplayDeviceLocked(displayToken)) {
const bool supportsDisplayBrightnessCommand =
getHwComposer().getComposer()->isSupported(
@@ -2001,7 +2002,6 @@
Hwc2::Composer::DisplayBrightnessOptions{
.applyImmediately = true});
}
-
} else {
ALOGE("%s: Invalid display token %p", whence, displayToken.get());
return ftl::yield<status_t>(NAME_NOT_FOUND);
@@ -3097,7 +3097,7 @@
{
Mutex::Autolock lock(mStateLock);
if (mFpsReporter) {
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(mLayerHierarchyBuilder.getHierarchy());
}
if (mTunnelModeEnabledReporter) {
@@ -3546,9 +3546,7 @@
getPhysicalDisplayOrientation(compositionDisplay->getId(), creationArgs.isPrimary);
ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation));
- // virtual displays are always considered enabled
- creationArgs.initialPowerMode =
- state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
+ creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;
creationArgs.requestedRefreshRate = state.requestedRefreshRate;
@@ -4240,7 +4238,7 @@
mRegionSamplingThread =
sp<RegionSamplingThread>::make(*this,
RegionSamplingThread::EnvironmentTimingTunables());
- mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+ mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline);
}
void SurfaceFlinger::doCommitTransactions() {
@@ -5812,12 +5810,6 @@
}
void SurfaceFlinger::initializeDisplays() {
- const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
- if (!display) return;
-
- const sp<IBinder> token = display->getDisplayToken().promote();
- LOG_ALWAYS_FATAL_IF(token == nullptr);
-
TransactionState state;
state.inputWindowCommands = mInputWindowCommands;
const nsecs_t now = systemTime();
@@ -5828,18 +5820,10 @@
const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++;
state.id = transactionId;
- // reset screen orientation and use primary layer stack
- DisplayState d;
- d.what = DisplayState::eDisplayProjectionChanged |
- DisplayState::eLayerStackChanged;
- d.token = token;
- d.layerStack = ui::DEFAULT_LAYER_STACK;
- d.orientation = ui::ROTATION_0;
- d.orientedDisplaySpaceRect.makeInvalid();
- d.layerStackSpaceRect.makeInvalid();
- d.width = 0;
- d.height = 0;
- state.displays.add(d);
+ auto layerStack = ui::DEFAULT_LAYER_STACK.id;
+ for (const auto& [id, display] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) {
+ state.displays.push(DisplayState(display.token(), ui::LayerStack::fromValue(layerStack++)));
+ }
std::vector<TransactionState> transactions;
transactions.emplace_back(state);
@@ -5852,12 +5836,25 @@
{
ftl::FakeGuard guard(mStateLock);
- setPowerModeInternal(display, hal::PowerMode::ON);
+
+ // In case of a restart, ensure all displays are off.
+ for (const auto& [id, display] : mPhysicalDisplays) {
+ setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::OFF);
+ }
+
+ // Power on all displays. The primary display is first, so becomes the active display. Also,
+ // the DisplayCapability set of a display is populated on its first powering on. Do this now
+ // before responding to any Binder query from DisplayManager about display capabilities.
+ for (const auto& [id, display] : mPhysicalDisplays) {
+ setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+ }
}
}
void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
if (display->isVirtual()) {
+ // TODO(b/241285876): This code path should not be reachable, so enforce this at compile
+ // time.
ALOGE("%s: Invalid operation on virtual display", __func__);
return;
}
@@ -5865,8 +5862,8 @@
const auto displayId = display->getPhysicalId();
ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
- const auto currentModeOpt = display->getPowerMode();
- if (currentModeOpt == mode) {
+ const auto currentMode = display->getPowerMode();
+ if (currentMode == mode) {
return;
}
@@ -5883,7 +5880,7 @@
display->setPowerMode(mode);
const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr;
- if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
+ if (currentMode == hal::PowerMode::OFF) {
// Turn on the display
// Activate the display (which involves a modeset to the active mode) when the inner or
@@ -5928,7 +5925,7 @@
mVisibleRegionsDirty = true;
scheduleComposite(FrameHint::kActive);
} else if (mode == hal::PowerMode::OFF) {
- const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND);
+ const bool currentModeNotDozeSuspend = (currentMode != hal::PowerMode::DOZE_SUSPEND);
// Turn off the display
if (displayId == mActiveDisplayId) {
if (const auto display = getActivatableDisplay()) {
@@ -5969,7 +5966,7 @@
} else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
// Update display while dozing
getHwComposer().setPowerMode(displayId, mode);
- if (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND &&
+ if (currentMode == hal::PowerMode::DOZE_SUSPEND &&
(displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
if (displayId == mActiveDisplayId) {
ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6e12172..7cfe46c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -903,7 +903,8 @@
* Display and layer stack management
*/
- // Called during boot, and restart after system_server death.
+ // Called during boot and restart after system_server death, setting the stage for bootanimation
+ // before DisplayManager takes over.
void initializeDisplays() REQUIRES(kMainThreadContext);
sp<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index c098dda..a6b12d0 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -441,7 +441,9 @@
FuzzedDataProvider& fuzzer;
Period period() const { return getFuzzedDuration(fuzzer); }
- TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+ TimePoint vsyncDeadlineAfter(TimePoint, ftl::Optional<TimePoint> = {}) const {
+ return getFuzzedTimePoint(fuzzer);
+ }
Period minFramePeriod() const { return period(); }
} vsyncSource{mFdp};
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
index f1bb231..b17b529 100644
--- a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -17,7 +17,7 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -25,31 +25,10 @@
namespace android {
namespace {
-struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
- static constexpr bool kWithMockScheduler = false;
- ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
+struct ActiveDisplayRotationFlagsTest
+ : DualDisplayTransactionTest<hal::PowerMode::ON, hal::PowerMode::OFF> {
void SetUp() override {
- injectMockScheduler(kInnerDisplayId);
-
- // Inject inner and outer displays with uninitialized power modes.
- constexpr bool kInitPowerMode = false;
- {
- InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
- auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
- injector.setPowerMode(std::nullopt);
- injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
- mInnerDisplay = injector.inject();
- }
- {
- OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
- auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
- injector.setPowerMode(std::nullopt);
- mOuterDisplay = injector.inject();
- }
-
- mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
- mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+ DualDisplayTransactionTest::SetUp();
// The flags are a static variable, so by modifying them in the test, we
// are modifying the real ones used by SurfaceFlinger. Save the original
@@ -64,10 +43,6 @@
void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
- static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
- static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
- sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
ui::Transform::RotationFlags mOldRotationFlags;
};
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 6671414..387d2f2 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -347,7 +347,6 @@
// The HWC active configuration id
static constexpr hal::HWConfigId HWC_ACTIVE_CONFIG_ID = 2001;
- static constexpr PowerMode INIT_POWER_MODE = hal::PowerMode::ON;
static void injectPendingHotplugEvent(DisplayTransactionTest* test, Connection connection) {
test->mFlinger.mutablePendingHotplugEvents().emplace_back(
@@ -355,7 +354,7 @@
}
// Called by tests to inject a HWC display setup
- template <bool kInitPowerMode = true>
+ template <hal::PowerMode kPowerMode = hal::PowerMode::ON>
static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) {
const auto displayId = DisplayVariant::DISPLAY_ID::get();
ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId));
@@ -364,22 +363,37 @@
.setHwcDisplayId(HWC_DISPLAY_ID)
.setResolution(DisplayVariant::RESOLUTION)
.setActiveConfig(HWC_ACTIVE_CONFIG_ID)
- .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt)
+ .setPowerMode(kPowerMode)
.inject(&test->mFlinger, test->mComposer);
}
// Called by tests to inject a HWC display setup
- template <bool kInitPowerMode = true>
+ //
+ // TODO(b/241285876): The `kExpectSetPowerModeOnce` argument is set to `false` by tests that
+ // power on/off displays several times. Replace those catch-all expectations with `InSequence`
+ // and `RetiresOnSaturation`.
+ //
+ template <hal::PowerMode kPowerMode = hal::PowerMode::ON, bool kExpectSetPowerModeOnce = true>
static void injectHwcDisplay(DisplayTransactionTest* test) {
- if constexpr (kInitPowerMode) {
- EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
- Return(Error::NONE)));
+ if constexpr (kExpectSetPowerModeOnce) {
+ if constexpr (kPowerMode == hal::PowerMode::ON) {
+ EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+ Return(Error::NONE)));
+ }
- EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
+ EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, kPowerMode))
.WillOnce(Return(Error::NONE));
+ } else {
+ EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+ Return(Error::NONE)));
+
+ EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, _))
+ .WillRepeatedly(Return(Error::NONE));
}
- injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test);
+
+ injectHwcDisplayWithNoDefaultCapabilities<kPowerMode>(test);
}
static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
diff --git a/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
new file mode 100644
index 0000000..90e716f
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DualDisplayTransactionTest.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 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 "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+template <hal::PowerMode kInnerDisplayPowerMode, hal::PowerMode kOuterDisplayPowerMode,
+ bool kExpectSetPowerModeOnce = true>
+struct DualDisplayTransactionTest : DisplayTransactionTest {
+ static constexpr bool kWithMockScheduler = false;
+ DualDisplayTransactionTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+ void SetUp() override {
+ injectMockScheduler(kInnerDisplayId);
+
+ {
+ InnerDisplayVariant::injectHwcDisplay<kInnerDisplayPowerMode, kExpectSetPowerModeOnce>(
+ this);
+
+ auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+ injector.setPowerMode(kInnerDisplayPowerMode);
+ mInnerDisplay = injector.inject();
+ }
+ {
+ OuterDisplayVariant::injectHwcDisplay<kOuterDisplayPowerMode, kExpectSetPowerModeOnce>(
+ this);
+
+ auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setPowerMode(kOuterDisplayPowerMode);
+ mOuterDisplay = injector.inject();
+ }
+ }
+
+ static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+ static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+ sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
index f695b09..9e8e306 100644
--- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -24,7 +24,11 @@
#include <gtest/gtest.h>
#include <gui/LayerMetadata.h>
+#include "Client.h" // temporarily needed for LayerCreationArgs
#include "FpsReporter.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
#include "Layer.h"
#include "TestableSurfaceFlinger.h"
#include "fake/FakeClock.h"
@@ -76,7 +80,15 @@
sp<Layer> createBufferStateLayer(LayerMetadata metadata);
- TestableSurfaceFlinger mFlinger;
+ LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+ LayerMetadata metadata);
+
+ void createRootLayer(uint32_t id, LayerMetadata metadata);
+
+ void createLayer(uint32_t id, uint32_t parentId, LayerMetadata metadata);
+
+ frontend::LayerLifecycleManager mLifecycleManager;
+
mock::FrameTimeline mFrameTimeline =
mock::FrameTimeline(std::make_shared<impl::TimeStats>(), 0);
@@ -89,8 +101,8 @@
sp<TestableFpsListener> mFpsListener;
fake::FakeClock* mClock = new fake::FakeClock();
- sp<FpsReporter> mFpsReporter = sp<FpsReporter>::make(mFrameTimeline, *(mFlinger.flinger()),
- std::unique_ptr<Clock>(mClock));
+ sp<FpsReporter> mFpsReporter =
+ sp<FpsReporter>::make(mFrameTimeline, std::unique_ptr<Clock>(mClock));
};
FpsReporterTest::FpsReporterTest() {
@@ -98,9 +110,6 @@
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- mFlinger.setupMockScheduler();
- mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-
mFpsListener = sp<TestableFpsListener>::make();
}
@@ -110,76 +119,94 @@
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
-sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
+LayerCreationArgs FpsReporterTest::createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+ LayerMetadata metadata) {
sp<Client> client;
- LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
- return sp<Layer>::make(args);
+ LayerCreationArgs args(std::make_optional(id));
+ args.name = "testlayer";
+ args.addToRoot = canBeRoot;
+ args.flags = LAYER_FLAGS;
+ args.metadata = metadata;
+ args.parentId = parentId;
+ return args;
+}
+
+void FpsReporterTest::createRootLayer(uint32_t id, LayerMetadata metadata = LayerMetadata()) {
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+ /*metadata=*/metadata)));
+ mLifecycleManager.addLayers(std::move(layers));
+}
+
+void FpsReporterTest::createLayer(uint32_t id, uint32_t parentId,
+ LayerMetadata metadata = LayerMetadata()) {
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<frontend::RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+ /*mirror=*/metadata)));
+ mLifecycleManager.addLayers(std::move(layers));
}
namespace {
TEST_F(FpsReporterTest, callsListeners) {
- mParent = createBufferStateLayer();
constexpr int32_t kTaskId = 12;
LayerMetadata targetMetadata;
targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
- mTarget = createBufferStateLayer(targetMetadata);
- mChild = createBufferStateLayer();
- mGrandChild = createBufferStateLayer();
- mUnrelated = createBufferStateLayer();
- mParent->addChild(mTarget);
- mTarget->addChild(mChild);
- mChild->addChild(mGrandChild);
- mParent->commitChildList();
- mFlinger.mutableCurrentState().layersSortedByZ.add(mParent);
- mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
- mFlinger.mutableCurrentState().layersSortedByZ.add(mChild);
- mFlinger.mutableCurrentState().layersSortedByZ.add(mGrandChild);
+
+ createRootLayer(1, targetMetadata);
+ createLayer(11, 1);
+ createLayer(111, 11);
+
+ frontend::LayerHierarchyBuilder hierarchyBuilder;
+ hierarchyBuilder.update(mLifecycleManager);
float expectedFps = 44.0;
- EXPECT_CALL(mFrameTimeline,
- computeFps(UnorderedElementsAre(mTarget->getSequence(), mChild->getSequence(),
- mGrandChild->getSequence())))
+ EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(1, 11, 111)))
.WillOnce(Return(expectedFps));
mFpsReporter->addListener(mFpsListener, kTaskId);
mClock->advanceTime(600ms);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
EXPECT_EQ(expectedFps, mFpsListener->lastReportedFps);
mFpsReporter->removeListener(mFpsListener);
Mock::VerifyAndClearExpectations(&mFrameTimeline);
EXPECT_CALL(mFrameTimeline, computeFps(_)).Times(0);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
}
TEST_F(FpsReporterTest, rateLimits) {
const constexpr int32_t kTaskId = 12;
LayerMetadata targetMetadata;
targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
- mTarget = createBufferStateLayer(targetMetadata);
- mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
+ createRootLayer(1);
+ createLayer(11, 1, targetMetadata);
+
+ frontend::LayerHierarchyBuilder hierarchyBuilder;
+ hierarchyBuilder.update(mLifecycleManager);
float firstFps = 44.0;
float secondFps = 53.0;
- EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(mTarget->getSequence())))
+ EXPECT_CALL(mFrameTimeline, computeFps(UnorderedElementsAre(11)))
.WillOnce(Return(firstFps))
.WillOnce(Return(secondFps));
mFpsReporter->addListener(mFpsListener, kTaskId);
mClock->advanceTime(600ms);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
mClock->advanceTime(200ms);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
mClock->advanceTime(200ms);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
EXPECT_EQ(firstFps, mFpsListener->lastReportedFps);
mClock->advanceTime(200ms);
- mFpsReporter->dispatchLayerFps();
+ mFpsReporter->dispatchLayerFps(hierarchyBuilder.getHierarchy());
EXPECT_EQ(secondFps, mFpsListener->lastReportedFps);
}
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
index 844b96c..93c2829 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
@@ -17,7 +17,7 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -25,35 +25,9 @@
namespace android {
namespace {
-struct FoldableTest : DisplayTransactionTest {
- static constexpr bool kWithMockScheduler = false;
- FoldableTest() : DisplayTransactionTest(kWithMockScheduler) {}
-
- void SetUp() override {
- injectMockScheduler(kInnerDisplayId);
-
- // Inject inner and outer displays with uninitialized power modes.
- constexpr bool kInitPowerMode = false;
- {
- InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
- auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
- injector.setPowerMode(std::nullopt);
- injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
- mInnerDisplay = injector.inject();
- }
- {
- OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
- auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
- injector.setPowerMode(std::nullopt);
- mOuterDisplay = injector.inject();
- }
- }
-
- static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
- static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
-
- sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
-};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct FoldableTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+ kExpectSetPowerModeOnce> {};
TEST_F(FoldableTest, promotesPacesetterOnBoot) {
// When the device boots, the inner display should be the pacesetter.
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index 1583f64..eaf4684 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -17,66 +17,49 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
-#include "DisplayTransactionTestHelpers.h"
+#include "DualDisplayTransactionTest.h"
namespace android {
namespace {
-class InitializeDisplaysTest : public DisplayTransactionTest {};
+constexpr bool kExpectSetPowerModeOnce = false;
+struct InitializeDisplaysTest : DualDisplayTransactionTest<hal::PowerMode::OFF, hal::PowerMode::OFF,
+ kExpectSetPowerModeOnce> {};
-TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) {
- using Case = SimplePrimaryDisplayCase;
-
- // --------------------------------------------------------------------
- // Preconditions
-
- // A primary display is set up
- Case::Display::injectHwcDisplay(this);
- auto primaryDisplay = Case::Display::makeFakeExistingDisplayInjector(this);
- primaryDisplay.inject();
-
- // --------------------------------------------------------------------
- // Call Expectations
-
- // We expect a call to get the active display config.
- Case::Display::setupHwcGetActiveConfigCallExpectations(this);
-
- // We expect a scheduled commit for the display transaction.
- EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+TEST_F(InitializeDisplaysTest, initializesDisplays) {
+ // Scheduled by the display transaction, and by powering on each display.
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3);
EXPECT_CALL(static_cast<mock::VSyncTracker&>(
mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
nextAnticipatedVSyncTimeFrom(_, _))
.WillRepeatedly(Return(0));
- // --------------------------------------------------------------------
- // Invocation
-
FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays());
- // --------------------------------------------------------------------
- // Postconditions
+ for (const auto& display : {mInnerDisplay, mOuterDisplay}) {
+ const auto token = display->getDisplayToken().promote();
+ ASSERT_TRUE(token);
- // The primary display should have a current state
- ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token()));
- const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token());
+ ASSERT_TRUE(hasCurrentDisplayState(token));
+ const auto& state = getCurrentDisplayState(token);
- // The primary display state should be reset
- EXPECT_EQ(ui::DEFAULT_LAYER_STACK, primaryDisplayState.layerStack);
- EXPECT_EQ(ui::ROTATION_0, primaryDisplayState.orientation);
- EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.orientedDisplaySpaceRect);
- EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.layerStackSpaceRect);
+ const ui::LayerStack expectedLayerStack = display == mInnerDisplay
+ ? ui::DEFAULT_LAYER_STACK
+ : ui::LayerStack::fromValue(ui::DEFAULT_LAYER_STACK.id + 1);
- // The width and height should both be zero
- EXPECT_EQ(0u, primaryDisplayState.width);
- EXPECT_EQ(0u, primaryDisplayState.height);
+ EXPECT_EQ(expectedLayerStack, state.layerStack);
+ EXPECT_EQ(ui::ROTATION_0, state.orientation);
+ EXPECT_EQ(Rect::INVALID_RECT, state.orientedDisplaySpaceRect);
+ EXPECT_EQ(Rect::INVALID_RECT, state.layerStackSpaceRect);
- // The display should be set to PowerMode::ON
- ASSERT_TRUE(hasDisplayDevice(primaryDisplay.token()));
- auto displayDevice = primaryDisplay.mutableDisplayDevice();
- EXPECT_EQ(PowerMode::ON, displayDevice->getPowerMode());
+ EXPECT_EQ(0u, state.width);
+ EXPECT_EQ(0u, state.height);
- // The display transaction needed flag should be set.
+ ASSERT_TRUE(hasDisplayDevice(token));
+ EXPECT_EQ(PowerMode::ON, getDisplayDevice(token).getPowerMode());
+ }
+
EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
}
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 4e0b5af..9ef0749 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -750,7 +750,6 @@
static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
static constexpr int32_t DEFAULT_DPI = 320;
static constexpr hal::HWConfigId DEFAULT_ACTIVE_CONFIG = 0;
- static constexpr hal::PowerMode DEFAULT_POWER_MODE = hal::PowerMode::ON;
FakeHwcDisplayInjector(HalDisplayId displayId, hal::DisplayType hwcDisplayType,
bool isPrimary)
@@ -793,7 +792,7 @@
return *this;
}
- auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+ auto& setPowerMode(hal::PowerMode mode) {
mPowerMode = mode;
return *this;
}
@@ -817,9 +816,7 @@
mHwcDisplayType);
display->mutableIsConnected() = true;
- if (mPowerMode) {
- display->setPowerMode(*mPowerMode);
- }
+ display->setPowerMode(mPowerMode);
flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
@@ -885,7 +882,7 @@
int32_t mDpiY = DEFAULT_DPI;
int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
- std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE;
+ hal::PowerMode mPowerMode = hal::PowerMode::ON;
const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
mCapabilities = nullptr;
};
@@ -962,7 +959,7 @@
return *this;
}
- auto& setPowerMode(std::optional<hal::PowerMode> mode) {
+ auto& setPowerMode(hal::PowerMode mode) {
mCreationArgs.initialPowerMode = mode;
return *this;
}