diff options
45 files changed, 1792 insertions, 223 deletions
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc index 2e0c95a5fe..66df0deeb3 100644 --- a/cmds/atrace/atrace.rc +++ b/cmds/atrace/atrace.rc @@ -297,8 +297,18 @@ on late-init write /sys/kernel/debug/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size" # allow creating event triggers - chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger chmod 0666 /sys/kernel/tracing/events/kmem/rss_stat/trigger + chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger + + # allow enabling rss_stat_throttled + chmod 0666 /sys/kernel/tracing/events/synthetic/rss_stat_throttled/enable + chmod 0666 /sys/kernel/debug/tracing/events/synthetic/rss_stat_throttled/enable + +on late-init && property:ro.boot.fastboot.boottrace=enabled + setprop debug.atrace.tags.enableflags 802922 + setprop persist.traced.enable 0 + write /sys/kernel/debug/tracing/tracing_on 1 + write /sys/kernel/tracing/tracing_on 1 on late-init && property:ro.boot.fastboot.boottrace=enabled setprop debug.atrace.tags.enableflags 802922 diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 1c52792cf6..a1be542d7b 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -38,6 +38,7 @@ #include <binder/IBinder.h> #include <binder/Parcelable.h> #include <input/Input.h> +#include <input/InputVerifier.h> #include <sys/stat.h> #include <ui/Transform.h> #include <utils/BitSet.h> @@ -444,6 +445,7 @@ public: private: std::shared_ptr<InputChannel> mChannel; + InputVerifier mInputVerifier; }; /* diff --git a/include/input/InputVerifier.h b/include/input/InputVerifier.h new file mode 100644 index 0000000000..d4589f53b5 --- /dev/null +++ b/include/input/InputVerifier.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 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 <input/Input.h> +#include <map> + +namespace android { + +/* + * Crash if the provided touch stream is inconsistent. + * + * TODO(b/211379801): Add support for hover events: + * - No hover move without enter + * - No touching pointers when hover enter + * - No hovering pointers when touching + * - Only 1 hovering pointer max + */ +class InputVerifier { +public: + InputVerifier(const std::string& name); + + void processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags); + +private: + const std::string mName; + std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTER_ID + 1>> mTouchingPointerIdsByDevice; + void ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const; +}; + +} // namespace android diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h new file mode 100644 index 0000000000..13ffb581b4 --- /dev/null +++ b/include/input/VirtualInputDevice.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 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 <android-base/unique_fd.h> + +namespace android { + +enum class UinputAction { + RELEASE = 0, + PRESS = 1, + MOVE = 2, + CANCEL = 3, +}; + +class VirtualInputDevice { +public: + VirtualInputDevice(android::base::unique_fd fd); + virtual ~VirtualInputDevice(); + +protected: + const android::base::unique_fd mFd; + bool writeInputEvent(uint16_t type, uint16_t code, int32_t value); + bool writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map<int, int>& evKeyCodeMapping, + const std::map<int, UinputAction>& actionMapping); +}; + +class VirtualKeyboard : public VirtualInputDevice { +public: + static const std::map<int, int> KEY_CODE_MAPPING; + // Expose to share with VirtualDpad. + static const std::map<int, UinputAction> KEY_ACTION_MAPPING; + VirtualKeyboard(android::base::unique_fd fd); + virtual ~VirtualKeyboard() override; + bool writeKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualDpad : public VirtualInputDevice { +public: + static const std::map<int, int> DPAD_KEY_CODE_MAPPING; + VirtualDpad(android::base::unique_fd fd); + virtual ~VirtualDpad() override; + bool writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction); +}; + +class VirtualMouse : public VirtualInputDevice { +public: + VirtualMouse(android::base::unique_fd fd); + virtual ~VirtualMouse() override; + bool writeButtonEvent(int32_t androidButtonCode, int32_t androidAction); + // TODO(b/259554911): changing float parameters to int32_t. + bool writeRelativeEvent(float relativeX, float relativeY); + bool writeScrollEvent(float xAxisMovement, float yAxisMovement); + +private: + static const std::map<int, UinputAction> BUTTON_ACTION_MAPPING; + static const std::map<int, int> BUTTON_CODE_MAPPING; +}; + +class VirtualTouchscreen : public VirtualInputDevice { +public: + VirtualTouchscreen(android::base::unique_fd fd); + virtual ~VirtualTouchscreen() override; + // TODO(b/259554911): changing float parameters to int32_t. + bool writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, float locationX, + float locationY, float pressure, float majorAxisSize); + +private: + static const std::map<int, UinputAction> TOUCH_ACTION_MAPPING; + static const std::map<int, int> TOOL_TYPE_MAPPING; + + /* The set of active touch pointers on this device. + * We only allow pointer id to go up to MAX_POINTERS because the maximum slots of virtual + * touchscreen is set up with MAX_POINTERS. Note that in other cases Android allows pointer id + * to go up to MAX_POINTERS_ID. + */ + std::bitset<MAX_POINTERS> mActivePointers{}; + bool isValidPointerId(int32_t pointerId, UinputAction uinputAction); + bool handleTouchDown(int32_t pointerId); + bool handleTouchUp(int32_t pointerId); +}; +} // namespace android diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index c4c8ffb656..24642d2f50 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -547,7 +547,8 @@ cc_library { // Do not expand the visibility. visibility: [ ":__subpackages__", - "//packages/modules/Virtualization:__subpackages__", + "//packages/modules/Virtualization/javalib/jni", + "//packages/modules/Virtualization/vm_payload", "//device/google/cuttlefish/shared/minidroid:__subpackages__", ], } diff --git a/libs/binder/OWNERS b/libs/binder/OWNERS index f954e74eba..bb17683a23 100644 --- a/libs/binder/OWNERS +++ b/libs/binder/OWNERS @@ -1,6 +1,4 @@ # Bug component: 32456 -ctate@google.com -hackbod@google.com maco@google.com smoreland@google.com tkjos@google.com diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index 9d82c143f5..cf8b13ffa4 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -486,6 +486,17 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId, mSyncedFrameNumbers.erase(callbackId.framenumber); } +static ui::Size getBufferSize(const BufferItem& item) { + uint32_t bufWidth = item.mGraphicBuffer->getWidth(); + uint32_t bufHeight = item.mGraphicBuffer->getHeight(); + + // Take the buffer's orientation into account + if (item.mTransform & ui::Transform::ROT_90) { + std::swap(bufWidth, bufHeight); + } + return ui::Size(bufWidth, bufHeight); +} + status_t BLASTBufferQueue::acquireNextBufferLocked( const std::optional<SurfaceComposerClient::Transaction*> transaction) { // Check if we have frames available and we have not acquired the maximum number of buffers. @@ -563,7 +574,12 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback. incStrong((void*)transactionCallbackThunk); - mSize = mRequestedSize; + // Only update mSize for destination bounds if the incoming buffer matches the requested size. + // Otherwise, it could cause stretching since the destination bounds will update before the + // buffer with the new size is acquired. + if (mRequestedSize == getBufferSize(bufferItem)) { + mSize = mRequestedSize; + } Rect crop = computeCrop(bufferItem); mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(), bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform, @@ -834,14 +850,7 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) { return false; } - uint32_t bufWidth = item.mGraphicBuffer->getWidth(); - uint32_t bufHeight = item.mGraphicBuffer->getHeight(); - - // Take the buffer's orientation into account - if (item.mTransform & ui::Transform::ROT_90) { - std::swap(bufWidth, bufHeight); - } - ui::Size bufferSize(bufWidth, bufHeight); + ui::Size bufferSize = getBufferSize(item); if (mRequestedSize != mSize && mRequestedSize == bufferSize) { return false; } diff --git a/libs/input/Android.bp b/libs/input/Android.bp index fd4fc16deb..48cb72cfb7 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -47,6 +47,7 @@ cc_library { "Input.cpp", "InputDevice.cpp", "InputEventLabels.cpp", + "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", "KeyLayoutMap.cpp", @@ -57,6 +58,7 @@ cc_library { "TouchVideoFrame.cpp", "VelocityControl.cpp", "VelocityTracker.cpp", + "VirtualInputDevice.cpp", "VirtualKeyMap.cpp", ], @@ -124,6 +126,8 @@ cc_library { enabled: false, }, include_dirs: [ + "bionic/libc/kernel/android/uapi/", + "bionic/libc/kernel/uapi", "frameworks/native/libs/arect/include", ], }, diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 9f0a314041..d1cd50ccff 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -76,6 +76,14 @@ static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; */ static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; +/** + * Crash if the events that are getting sent to the InputPublisher are inconsistent. + * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG" + */ +static bool verifyEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO); +} + template<typename T> inline static T min(const T& a, const T& b) { return a < b ? a : b; @@ -492,7 +500,8 @@ base::unique_fd InputChannel::dupFd() const { // --- InputPublisher --- -InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) : mChannel(channel) {} +InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) + : mChannel(channel), mInputVerifier(channel->getName()) {} InputPublisher::~InputPublisher() { } @@ -555,6 +564,10 @@ status_t InputPublisher::publishMotionEvent( mChannel->getName().c_str(), action); ATRACE_NAME(message.c_str()); } + if (verifyEvents()) { + mInputVerifier.processMovement(deviceId, action, pointerCount, pointerProperties, + pointerCoords, flags); + } if (DEBUG_TRANSPORT_ACTIONS) { std::string transformString; transform.dump(transformString, "transform", " "); diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp new file mode 100644 index 0000000000..eb758045cc --- /dev/null +++ b/libs/input/InputVerifier.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "InputVerifier" + +#include <android-base/logging.h> +#include <input/InputVerifier.h> + +namespace android { + +/** + * Log all of the movements that are sent to this verifier. Helps to identify the streams that lead + * to inconsistent events. + * Enable this via "adb shell setprop log.tag.InputVerifierLogEvents DEBUG" + */ +static bool logEvents() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "LogEvents", ANDROID_LOG_INFO); +} + +// --- InputVerifier --- + +InputVerifier::InputVerifier(const std::string& name) : mName(name){}; + +void InputVerifier::processMovement(int32_t deviceId, int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, int32_t flags) { + if (logEvents()) { + LOG(ERROR) << "Processing " << MotionEvent::actionToString(action) << " for device " + << deviceId << " (" << pointerCount << " pointer" + << (pointerCount == 1 ? "" : "s") << ") on " << mName; + } + + switch (MotionEvent::getActionMasked(action)) { + case AMOTION_EVENT_ACTION_DOWN: { + auto [it, inserted] = mTouchingPointerIdsByDevice.insert({deviceId, {}}); + if (!inserted) { + LOG(FATAL) << "Got ACTION_DOWN, but already have touching pointers " << it->second + << " for device " << deviceId << " on " << mName; + } + it->second.set(pointerProperties[0].id); + break; + } + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_DOWN, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.set(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_MOVE: { + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "MOVE"); + break; + } + case AMOTION_EVENT_ACTION_POINTER_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got POINTER_UP, but no touching pointers for device " << deviceId + << " on " << mName; + } + it->second.reset(pointerProperties[MotionEvent::getActionIndex(action)].id); + break; + } + case AMOTION_EVENT_ACTION_UP: { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got ACTION_UP, but no record for deviceId " << deviceId << " on " + << mName; + } + const auto& [_, touchingPointerIds] = *it; + if (touchingPointerIds.count() != 1) { + LOG(FATAL) << "Got ACTION_UP, but we have pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + const int32_t pointerId = pointerProperties[0].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got ACTION_UP, but pointerId " << pointerId + << " is not touching. Touching pointers: " << touchingPointerIds + << " for deviceId " << deviceId << " on " << mName; + } + mTouchingPointerIdsByDevice.erase(it); + break; + } + case AMOTION_EVENT_ACTION_CANCEL: { + if ((flags & AMOTION_EVENT_FLAG_CANCELED) != AMOTION_EVENT_FLAG_CANCELED) { + LOG(FATAL) << "For ACTION_CANCEL, must set FLAG_CANCELED"; + } + ensureTouchingPointersMatch(deviceId, pointerCount, pointerProperties, "CANCEL"); + mTouchingPointerIdsByDevice.erase(deviceId); + break; + } + } +} + +void InputVerifier::ensureTouchingPointersMatch(int32_t deviceId, uint32_t pointerCount, + const PointerProperties* pointerProperties, + const char* action) const { + auto it = mTouchingPointerIdsByDevice.find(deviceId); + if (it == mTouchingPointerIdsByDevice.end()) { + LOG(FATAL) << "Got " << action << ", but no touching pointers for device " << deviceId + << " on " << mName; + } + const auto& [_, touchingPointerIds] = *it; + for (size_t i = 0; i < pointerCount; i++) { + const int32_t pointerId = pointerProperties[i].id; + if (!touchingPointerIds.test(pointerId)) { + LOG(FATAL) << "Got " << action << " for pointerId " << pointerId + << " but the touching pointers are " << touchingPointerIds << " on " + << mName; + } + } +}; + +} // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp new file mode 100644 index 0000000000..3c1f2b6b56 --- /dev/null +++ b/libs/input/VirtualInputDevice.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (C) 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. + */ + +#define LOG_TAG "VirtualInputDevice" + +#include <android/input.h> +#include <android/keycodes.h> +#include <fcntl.h> +#include <input/Input.h> +#include <input/VirtualInputDevice.h> +#include <linux/uinput.h> +#include <math.h> +#include <utils/Log.h> + +#include <map> +#include <string> + +using android::base::unique_fd; + +/** + * Log debug messages about native virtual input devices. + * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG" + */ +static bool isDebug() { + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); +} + +namespace android { +VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {} +VirtualInputDevice::~VirtualInputDevice() { + ioctl(mFd, UI_DEV_DESTROY); +} + +bool VirtualInputDevice::writeInputEvent(uint16_t type, uint16_t code, int32_t value) { + struct input_event ev = {.type = type, .code = code, .value = value}; + return TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(struct input_event))) == sizeof(ev); +} + +/** Utility method to write keyboard key events or mouse button events. */ +bool VirtualInputDevice::writeEvKeyEvent(int32_t androidCode, int32_t androidAction, + const std::map<int, int>& evKeyCodeMapping, + const std::map<int, UinputAction>& actionMapping) { + auto evKeyCodeIterator = evKeyCodeMapping.find(androidCode); + if (evKeyCodeIterator == evKeyCodeMapping.end()) { + ALOGE("Unsupported native EV keycode for android code %d", androidCode); + return false; + } + auto actionIterator = actionMapping.find(androidAction); + if (actionIterator == actionMapping.end()) { + return false; + } + if (!writeInputEvent(EV_KEY, static_cast<uint16_t>(evKeyCodeIterator->second), + static_cast<int32_t>(actionIterator->second))) { + return false; + } + if (!writeInputEvent(EV_SYN, SYN_REPORT, 0)) { + return false; + } + return true; +} + +// --- VirtualKeyboard --- +const std::map<int, UinputAction> VirtualKeyboard::KEY_ACTION_MAPPING = { + {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE}, +}; +// Keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map<int, int> VirtualKeyboard::KEY_CODE_MAPPING = { + {AKEYCODE_0, KEY_0}, + {AKEYCODE_1, KEY_1}, + {AKEYCODE_2, KEY_2}, + {AKEYCODE_3, KEY_3}, + {AKEYCODE_4, KEY_4}, + {AKEYCODE_5, KEY_5}, + {AKEYCODE_6, KEY_6}, + {AKEYCODE_7, KEY_7}, + {AKEYCODE_8, KEY_8}, + {AKEYCODE_9, KEY_9}, + {AKEYCODE_A, KEY_A}, + {AKEYCODE_B, KEY_B}, + {AKEYCODE_C, KEY_C}, + {AKEYCODE_D, KEY_D}, + {AKEYCODE_E, KEY_E}, + {AKEYCODE_F, KEY_F}, + {AKEYCODE_G, KEY_G}, + {AKEYCODE_H, KEY_H}, + {AKEYCODE_I, KEY_I}, + {AKEYCODE_J, KEY_J}, + {AKEYCODE_K, KEY_K}, + {AKEYCODE_L, KEY_L}, + {AKEYCODE_M, KEY_M}, + {AKEYCODE_N, KEY_N}, + {AKEYCODE_O, KEY_O}, + {AKEYCODE_P, KEY_P}, + {AKEYCODE_Q, KEY_Q}, + {AKEYCODE_R, KEY_R}, + {AKEYCODE_S, KEY_S}, + {AKEYCODE_T, KEY_T}, + {AKEYCODE_U, KEY_U}, + {AKEYCODE_V, KEY_V}, + {AKEYCODE_W, KEY_W}, + {AKEYCODE_X, KEY_X}, + {AKEYCODE_Y, KEY_Y}, + {AKEYCODE_Z, KEY_Z}, + {AKEYCODE_GRAVE, KEY_GRAVE}, + {AKEYCODE_MINUS, KEY_MINUS}, + {AKEYCODE_EQUALS, KEY_EQUAL}, + {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE}, + {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE}, + {AKEYCODE_BACKSLASH, KEY_BACKSLASH}, + {AKEYCODE_SEMICOLON, KEY_SEMICOLON}, + {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE}, + {AKEYCODE_COMMA, KEY_COMMA}, + {AKEYCODE_PERIOD, KEY_DOT}, + {AKEYCODE_SLASH, KEY_SLASH}, + {AKEYCODE_ALT_LEFT, KEY_LEFTALT}, + {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT}, + {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL}, + {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL}, + {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT}, + {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT}, + {AKEYCODE_META_LEFT, KEY_LEFTMETA}, + {AKEYCODE_META_RIGHT, KEY_RIGHTMETA}, + {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK}, + {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK}, + {AKEYCODE_NUM_LOCK, KEY_NUMLOCK}, + {AKEYCODE_ENTER, KEY_ENTER}, + {AKEYCODE_TAB, KEY_TAB}, + {AKEYCODE_SPACE, KEY_SPACE}, + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_MOVE_END, KEY_END}, + {AKEYCODE_MOVE_HOME, KEY_HOME}, + {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN}, + {AKEYCODE_PAGE_UP, KEY_PAGEUP}, + {AKEYCODE_DEL, KEY_BACKSPACE}, + {AKEYCODE_FORWARD_DEL, KEY_DELETE}, + {AKEYCODE_INSERT, KEY_INSERT}, + {AKEYCODE_ESCAPE, KEY_ESC}, + {AKEYCODE_BREAK, KEY_PAUSE}, + {AKEYCODE_F1, KEY_F1}, + {AKEYCODE_F2, KEY_F2}, + {AKEYCODE_F3, KEY_F3}, + {AKEYCODE_F4, KEY_F4}, + {AKEYCODE_F5, KEY_F5}, + {AKEYCODE_F6, KEY_F6}, + {AKEYCODE_F7, KEY_F7}, + {AKEYCODE_F8, KEY_F8}, + {AKEYCODE_F9, KEY_F9}, + {AKEYCODE_F10, KEY_F10}, + {AKEYCODE_F11, KEY_F11}, + {AKEYCODE_F12, KEY_F12}, + {AKEYCODE_BACK, KEY_BACK}, + {AKEYCODE_FORWARD, KEY_FORWARD}, + {AKEYCODE_NUMPAD_1, KEY_KP1}, + {AKEYCODE_NUMPAD_2, KEY_KP2}, + {AKEYCODE_NUMPAD_3, KEY_KP3}, + {AKEYCODE_NUMPAD_4, KEY_KP4}, + {AKEYCODE_NUMPAD_5, KEY_KP5}, + {AKEYCODE_NUMPAD_6, KEY_KP6}, + {AKEYCODE_NUMPAD_7, KEY_KP7}, + {AKEYCODE_NUMPAD_8, KEY_KP8}, + {AKEYCODE_NUMPAD_9, KEY_KP9}, + {AKEYCODE_NUMPAD_0, KEY_KP0}, + {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS}, + {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS}, + {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK}, + {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH}, + {AKEYCODE_NUMPAD_DOT, KEY_KPDOT}, + {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER}, + {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL}, + {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA}, +}; +VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualKeyboard::~VirtualKeyboard() {} + +bool VirtualKeyboard::writeKeyEvent(int32_t androidKeyCode, int32_t androidAction) { + return writeEvKeyEvent(androidKeyCode, androidAction, KEY_CODE_MAPPING, KEY_ACTION_MAPPING); +} + +// --- VirtualDpad --- +// Dpad keycode mapping from https://source.android.com/devices/input/keyboard-devices +const std::map<int, int> VirtualDpad::DPAD_KEY_CODE_MAPPING = { + // clang-format off + {AKEYCODE_DPAD_DOWN, KEY_DOWN}, + {AKEYCODE_DPAD_UP, KEY_UP}, + {AKEYCODE_DPAD_LEFT, KEY_LEFT}, + {AKEYCODE_DPAD_RIGHT, KEY_RIGHT}, + {AKEYCODE_DPAD_CENTER, KEY_SELECT}, + {AKEYCODE_BACK, KEY_BACK}, + // clang-format on +}; + +VirtualDpad::VirtualDpad(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualDpad::~VirtualDpad() {} + +bool VirtualDpad::writeDpadKeyEvent(int32_t androidKeyCode, int32_t androidAction) { + return writeEvKeyEvent(androidKeyCode, androidAction, DPAD_KEY_CODE_MAPPING, + VirtualKeyboard::KEY_ACTION_MAPPING); +} + +// --- VirtualMouse --- +const std::map<int, UinputAction> VirtualMouse::BUTTON_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE}, +}; + +// Button code mapping from https://source.android.com/devices/input/touch-devices +const std::map<int, int> VirtualMouse::BUTTON_CODE_MAPPING = { + // clang-format off + {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT}, + {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT}, + {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, + {AMOTION_EVENT_BUTTON_BACK, BTN_BACK}, + {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD}, + // clang-format on +}; + +VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualMouse::~VirtualMouse() {} + +bool VirtualMouse::writeButtonEvent(int32_t androidButtonCode, int32_t androidAction) { + return writeEvKeyEvent(androidButtonCode, androidAction, BUTTON_CODE_MAPPING, + BUTTON_ACTION_MAPPING); +} + +bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY) { + return writeInputEvent(EV_REL, REL_X, relativeX) && writeInputEvent(EV_REL, REL_Y, relativeY) && + writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement) { + return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement) && + writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement) && + writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +// --- VirtualTouchscreen --- +const std::map<int, UinputAction> VirtualTouchscreen::TOUCH_ACTION_MAPPING = { + {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS}, + {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE}, + {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE}, + {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL}, +}; +// Tool type mapping from https://source.android.com/devices/input/touch-devices +const std::map<int, int> VirtualTouchscreen::TOOL_TYPE_MAPPING = { + {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER}, + {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM}, +}; + +VirtualTouchscreen::VirtualTouchscreen(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +VirtualTouchscreen::~VirtualTouchscreen() {} + +bool VirtualTouchscreen::isValidPointerId(int32_t pointerId, UinputAction uinputAction) { + if (pointerId < -1 || pointerId >= (int)MAX_POINTERS) { + ALOGE("Virtual touch event has invalid pointer id %d; value must be between -1 and %zu", + pointerId, MAX_POINTERS - 0); + return false; + } + + if (uinputAction == UinputAction::PRESS && mActivePointers.test(pointerId)) { + ALOGE("Repetitive action DOWN event received on a pointer %d that is already down.", + pointerId); + return false; + } + if (uinputAction == UinputAction::RELEASE && !mActivePointers.test(pointerId)) { + ALOGE("PointerId %d action UP received with no prior action DOWN on touchscreen %d.", + pointerId, mFd.get()); + return false; + } + return true; +} + +bool VirtualTouchscreen::writeTouchEvent(int32_t pointerId, int32_t toolType, int32_t action, + float locationX, float locationY, float pressure, + float majorAxisSize) { + auto actionIterator = TOUCH_ACTION_MAPPING.find(action); + if (actionIterator == TOUCH_ACTION_MAPPING.end()) { + return false; + } + UinputAction uinputAction = actionIterator->second; + if (!isValidPointerId(pointerId, uinputAction)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_SLOT, pointerId)) { + return false; + } + auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType); + if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_TOOL_TYPE, + static_cast<int32_t>(toolTypeIterator->second))) { + return false; + } + if (uinputAction == UinputAction::PRESS && !handleTouchDown(pointerId)) { + return false; + } + if (uinputAction == UinputAction::RELEASE && !handleTouchUp(pointerId)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_X, locationX)) { + return false; + } + if (!writeInputEvent(EV_ABS, ABS_MT_POSITION_Y, locationY)) { + return false; + } + if (!isnan(pressure)) { + if (!writeInputEvent(EV_ABS, ABS_MT_PRESSURE, pressure)) { + return false; + } + } + if (!isnan(majorAxisSize)) { + if (!writeInputEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) { + return false; + } + } + return writeInputEvent(EV_SYN, SYN_REPORT, 0); +} + +bool VirtualTouchscreen::handleTouchUp(int32_t pointerId) { + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(-1))) { + return false; + } + // When a pointer is no longer in touch, remove the pointer id from the corresponding + // entry in the unreleased touches map. + mActivePointers.reset(pointerId); + ALOGD_IF(isDebug(), "Pointer %d erased from the touchscreen %d", pointerId, mFd.get()); + + // Only sends the BTN UP event when there's no pointers on the touchscreen. + if (mActivePointers.none()) { + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::RELEASE))) { + return false; + } + ALOGD_IF(isDebug(), "No pointers on touchscreen %d, BTN UP event sent.", mFd.get()); + } + return true; +} + +bool VirtualTouchscreen::handleTouchDown(int32_t pointerId) { + // When a new pointer is down on the touchscreen, add the pointer id in the corresponding + // entry in the unreleased touches map. + if (mActivePointers.none()) { + // Only sends the BTN Down event when the first pointer on the touchscreen is down. + if (!writeInputEvent(EV_KEY, BTN_TOUCH, static_cast<int32_t>(UinputAction::PRESS))) { + return false; + } + ALOGD_IF(isDebug(), "First pointer %d down under touchscreen %d, BTN DOWN event sent", + pointerId, mFd.get()); + } + + mActivePointers.set(pointerId); + ALOGD_IF(isDebug(), "Added pointer %d under touchscreen %d in the map", pointerId, mFd.get()); + if (!writeInputEvent(EV_ABS, ABS_MT_TRACKING_ID, static_cast<int32_t>(pointerId))) { + return false; + } + return true; +} + +} // namespace android diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp index 2278d391b5..e2aac8c2a3 100644 --- a/libs/sensor/ISensorServer.cpp +++ b/libs/sensor/ISensorServer.cpp @@ -67,7 +67,11 @@ public: v.setCapacity(n); while (n) { n--; - reply.read(s); + if(reply.read(s) != OK) { + ALOGE("Failed to read reply from getSensorList"); + v.clear(); + break; + } v.add(s); } return v; @@ -85,7 +89,11 @@ public: v.setCapacity(n); while (n) { n--; - reply.read(s); + if(reply.read(s) != OK) { + ALOGE("Failed to read reply from getDynamicSensorList"); + v.clear(); + break; + } v.add(s); } return v; diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp index fb895f59b9..b6ea77deb5 100644 --- a/libs/sensor/Sensor.cpp +++ b/libs/sensor/Sensor.cpp @@ -628,7 +628,13 @@ bool Sensor::unflattenString8(void const*& buffer, size_t& size, String8& output return false; } outputString8.setTo(static_cast<char const*>(buffer), len); + + if (size < FlattenableUtils::align<4>(len)) { + ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not."); + return false; + } FlattenableUtils::advance(buffer, size, FlattenableUtils::align<4>(len)); + return true; } diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp index 27482768f2..44a208d4ff 100644 --- a/libs/sensor/SensorManager.cpp +++ b/libs/sensor/SensorManager.cpp @@ -92,6 +92,16 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName) return *sensorManager; } +void SensorManager::removeInstanceForPackage(const String16& packageName) { + Mutex::Autolock _l(sLock); + auto iterator = sPackageInstances.find(packageName); + if (iterator != sPackageInstances.end()) { + SensorManager* sensorManager = iterator->second; + delete sensorManager; + sPackageInstances.erase(iterator); + } +} + SensorManager::SensorManager(const String16& opPackageName) : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) { Mutex::Autolock _l(mLock); @@ -166,6 +176,11 @@ status_t SensorManager::assertStateLocked() { mSensors = mSensorServer->getSensorList(mOpPackageName); size_t count = mSensors.size(); + if (count == 0) { + ALOGE("Failed to get Sensor list"); + mSensorServer.clear(); + return UNKNOWN_ERROR; + } mSensorList = static_cast<Sensor const**>(malloc(count * sizeof(Sensor*))); LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL"); diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h index 0798da292a..c31f648a49 100644 --- a/libs/sensor/include/sensor/SensorManager.h +++ b/libs/sensor/include/sensor/SensorManager.h @@ -54,6 +54,7 @@ class SensorManager : public ASensorManager { public: static SensorManager& getInstanceForPackage(const String16& packageName); + static void removeInstanceForPackage(const String16& packageName); ~SensorManager(); ssize_t getSensorList(Sensor const* const** list); diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h index bf6189d7af..a2e4115bd6 100644 --- a/services/batteryservice/include/batteryservice/BatteryService.h +++ b/services/batteryservice/include/batteryservice/BatteryService.h @@ -37,7 +37,6 @@ enum { BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE - BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH }; struct BatteryProperties { diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index ce7c882f7d..621543a1ee 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -23,11 +23,17 @@ #include <cutils/atomic.h> #include <inttypes.h> -using android::base::GetBoolProperty; using android::base::StringPrintf; namespace android::inputdispatcher { +static const bool DEBUGGABLE = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry) { return {{VerifiedInputEvent::Type::KEY, entry.deviceId, entry.eventTime, entry.source, entry.displayId}, @@ -172,7 +178,7 @@ KeyEntry::KeyEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t sou KeyEntry::~KeyEntry() {} std::string KeyEntry::getDescription() const { - if (!GetBoolProperty("ro.debuggable", false)) { + if (!DEBUGGABLE) { return "KeyEvent"; } return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32 @@ -242,7 +248,7 @@ MotionEntry::MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32 MotionEntry::~MotionEntry() {} std::string MotionEntry::getDescription() const { - if (!GetBoolProperty("ro.debuggable", false)) { + if (!DEBUGGABLE) { return "MotionEvent"; } std::string msg; @@ -292,7 +298,7 @@ std::string SensorEntry::getDescription() const { deviceId, inputEventSourceToString(source).c_str(), ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp); - if (!GetBoolProperty("ro.debuggable", false)) { + if (DEBUGGABLE) { for (size_t i = 0; i < values.size(); i++) { if (i > 0) { msg += ", "; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9d5bbbdd11..c9f7512d47 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2184,18 +2184,20 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE); + // If pointers are already down, let's finish the current gesture and ignore the new events + // from another device. However, if the new event is a down event, let's cancel the current + // touch and let the new one take over. + if (switchedDevice && wasDown && !isDown) { + LOG(INFO) << "Dropping event because a pointer for device " << oldState->deviceId + << " is already down in display " << displayId << ": " << entry.getDescription(); + // TODO(b/211379801): test multiple simultaneous input streams. + outInjectionResult = InputEventInjectionResult::FAILED; + return {}; // wrong device + } + if (newGesture) { - // If pointers are already down, let's finish the current gesture and ignore the new events - // from another device. - if (switchedDevice && wasDown) { - ALOGI("Dropping event because a pointer for a different device is already down " - "in display %" PRId32, - displayId); - // TODO: test multiple simultaneous input streams. - outInjectionResult = InputEventInjectionResult::FAILED; - return {}; // wrong device - } - tempTouchState.clearWindowsWithoutPointers(); + // If a new gesture is starting, clear the touch state completely. + tempTouchState.reset(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2203,7 +2205,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( ALOGI("Dropping move event because a pointer for a different device is already active " "in display %" PRId32, displayId); - // TODO: test multiple simultaneous input streams. + // TODO(b/211379801): test multiple simultaneous input streams. outInjectionResult = InputEventInjectionResult::FAILED; return {}; // wrong device } @@ -2317,6 +2319,10 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN || maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN; + // TODO(b/211379801): Currently, even if pointerIds are empty (hover case), we would + // still add a window to the touch state. We should avoid doing that, but some of the + // later checks ("at least one foreground window") rely on this in order to dispatch + // the event properly, so that needs to be updated, possibly by looking at InputTargets. tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, isDownOrPointerDown ? std::make_optional(entry.eventTime) @@ -2369,10 +2375,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { - ALOGD_IF(DEBUG_FOCUS, - "Dropping event because the pointer is not down or we previously " - "dropped the pointer down event in display %" PRId32 ": %s", - displayId, entry.getDescription().c_str()); + LOG(INFO) << "Dropping event because the pointer is not down or we previously " + "dropped the pointer down event in display " + << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } @@ -2530,7 +2535,6 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } // Success! Output targets from the touch state. - tempTouchState.clearWindowsWithoutPointers(); for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. @@ -2570,14 +2574,13 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } else if (maskedAction == AMOTION_EVENT_ACTION_UP) { // Pointer went up. tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id); - tempTouchState.clearWindowsWithoutPointers(); } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) { // All pointers up or canceled. tempTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { // First pointer went down. - if (oldState && oldState->isDown()) { - ALOGD("Conflicting pointer actions: Down received while already down."); + if (oldState && (oldState->isDown() || oldState->hasHoveringPointers())) { + ALOGD("Conflicting pointer actions: Down received while already down or hovering."); *outConflictingPointerActions = true; } } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { @@ -2600,6 +2603,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { if (displayId >= 0) { + tempTouchState.clearWindowsWithoutPointers(); mTouchStatesByDisplay[displayId] = tempTouchState; } else { mTouchStatesByDisplay.erase(displayId); @@ -2741,7 +2745,8 @@ void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHa if (displayInfoIt != mDisplayInfos.end()) { inputTarget.displayTransform = displayInfoIt->second.transform; } else { - ALOGE("DisplayInfo not found for window on display: %d", windowInfo->displayId); + // DisplayInfo not found for this window on display windowInfo->displayId. + // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed. } inputTargets.push_back(inputTarget); it = inputTargets.end() - 1; @@ -3048,9 +3053,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry); if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) { - LOG_ALWAYS_FATAL_IF(!inputTarget.firstDownTimeInTarget.has_value(), - "Splitting motion events requires a down time to be set for the " - "target"); + if (!inputTarget.firstDownTimeInTarget.has_value()) { + logDispatchStateLocked(); + LOG(FATAL) << "Splitting motion events requires a down time to be set for the " + "target on connection " + << connection->getInputChannelName() << " for " + << originalMotionEntry.getDescription(); + } std::unique_ptr<MotionEntry> splitMotionEntry = splitMotionEvent(originalMotionEntry, inputTarget.pointerIds, inputTarget.firstDownTimeInTarget.value()); @@ -3931,8 +3940,8 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent( // in this way. ALOGW("Dropping split motion event because the pointer count is %d but " "we expected there to be %zu pointers. This probably means we received " - "a broken sequence of pointer ids from the input device.", - splitPointerCount, pointerIds.count()); + "a broken sequence of pointer ids from the input device: %s", + splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str()); return nullptr; } diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index ad5a7fde07..94f38131b6 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -28,10 +28,6 @@ InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerato InputState::~InputState() {} -bool InputState::isNeutral() const { - return mKeyMementos.empty() && mMotionMementos.empty(); -} - bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const { for (const MotionMemento& memento : mMotionMementos) { if (memento.deviceId == deviceId && memento.source == source && @@ -251,10 +247,19 @@ void InputState::addMotionMemento(const MotionEntry& entry, int32_t flags, bool } void InputState::MotionMemento::setPointers(const MotionEntry& entry) { - pointerCount = entry.pointerCount; + pointerCount = 0; for (uint32_t i = 0; i < entry.pointerCount; i++) { - pointerProperties[i].copyFrom(entry.pointerProperties[i]); - pointerCoords[i].copyFrom(entry.pointerCoords[i]); + if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) { + // In POINTER_UP events, the pointer is leaving. Since the action is not stored, + // this departing pointer should not be recorded. + const uint8_t actionIndex = MotionEvent::getActionIndex(entry.action); + if (i == actionIndex) { + continue; + } + } + pointerProperties[pointerCount].copyFrom(entry.pointerProperties[i]); + pointerCoords[pointerCount].copyFrom(entry.pointerCoords[i]); + pointerCount++; } } diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index 42d8cc6af3..d788e47429 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -34,9 +34,6 @@ public: explicit InputState(const IdGenerator& idGenerator); ~InputState(); - // Returns true if there is no state to be canceled. - bool isNeutral() const; - // Returns true if the specified source is known to have received a hover enter // motion event. bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const; diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 425847183e..9c443f14cf 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -35,6 +35,7 @@ void TouchState::removeTouchedPointer(int32_t pointerId) { for (TouchedWindow& touchedWindow : windows) { touchedWindow.removeTouchingPointer(pointerId); } + clearWindowsWithoutPointers(); } void TouchState::removeTouchedPointerFromWindow( @@ -42,6 +43,7 @@ void TouchState::removeTouchedPointerFromWindow( for (TouchedWindow& touchedWindow : windows) { if (touchedWindow.windowHandle == windowHandle) { touchedWindow.removeTouchingPointer(pointerId); + clearWindowsWithoutPointers(); return; } } @@ -51,6 +53,7 @@ void TouchState::clearHoveringPointers() { for (TouchedWindow& touchedWindow : windows) { touchedWindow.clearHoveringPointers(); } + clearWindowsWithoutPointers(); } void TouchState::clearWindowsWithoutPointers() { @@ -135,7 +138,7 @@ void TouchState::cancelPointersForWindowsExcept(std::bitset<MAX_POINTER_ID + 1> w.pointerIds &= ~pointerIds; } }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); + clearWindowsWithoutPointers(); } /** @@ -164,7 +167,7 @@ void TouchState::cancelPointersForNonPilferingWindows() { w.pilferedPointerIds ^ allPilferedPointerIds; w.pointerIds &= ~pilferedByOtherWindows; }); - std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.none(); }); + clearWindowsWithoutPointers(); } sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const { @@ -216,6 +219,11 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return window.pointerIds.any(); }); } +bool TouchState::hasHoveringPointers() const { + return std::any_of(windows.begin(), windows.end(), + [](const TouchedWindow& window) { return window.hasHoveringPointers(); }); +} + std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, int32_t pointerId) const { std::set<sp<WindowInfoHandle>> out; @@ -231,9 +239,7 @@ void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoverin for (TouchedWindow& window : windows) { window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); } - std::erase_if(windows, [](const TouchedWindow& w) { - return w.pointerIds.none() && !w.hasHoveringPointers(); - }); + clearWindowsWithoutPointers(); } std::string TouchState::dump() const { diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 6e965d8c96..a20080f534 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -71,6 +71,7 @@ struct TouchState { const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Whether any of the windows are currently being touched bool isDown() const; + bool hasHoveringPointers() const; std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( int32_t deviceId, int32_t pointerId) const; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 9f32311e14..8ab6748bfe 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -16,6 +16,7 @@ #include "../Macros.h" +#include <limits> #include <optional> #include <android/input.h> @@ -30,6 +31,76 @@ namespace android { namespace { +// Describes a segment of the acceleration curve. +struct CurveSegment { + // The maximum pointer speed which this segment should apply. The last segment in a curve should + // always set this to infinity. + double maxPointerSpeedMmPerS; + double slope; + double intercept; +}; + +const std::vector<CurveSegment> segments = { + {10.922, 3.19, 0}, + {31.750, 4.79, -17.526}, + {98.044, 7.28, -96.52}, + {std::numeric_limits<double>::infinity(), 15.04, -857.758}, +}; + +const std::vector<double> sensitivityFactors = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20}; + +std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, + size_t propertySize) { + LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size()); + std::vector<double> output(propertySize, 0); + + // The Gestures library uses functions of the following form to define curve segments, where a, + // b, and c can be specified by us: + // output_speed(input_speed_mm) = a * input_speed_mm ^ 2 + b * input_speed_mm + c + // + // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.) + // + // We are trying to implement the following function, where slope and intercept are the + // parameters specified in the `segments` array above: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm) + // Where "gain" is a multiplier applied to the input speed to produce the output speed: + // output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm) + // + // To put our function in the library's form, we substitute it into the function above: + // output_speed(input_speed_mm) = + // input_speed_mm * (0.64 * (sensitivityFactor / 10) * + // (slope + 25.4 * intercept / input_speed_mm)) + // then expand the brackets so that input_speed_mm cancels out for the intercept term: + // gain(input_speed_mm) = + // 0.64 * (sensitivityFactor / 10) * slope * input_speed_mm + + // 0.64 * (sensitivityFactor / 10) * intercept + // + // This gives us the following parameters for the Gestures library function form: + // a = 0 + // b = 0.64 * (sensitivityFactor / 10) * slope + // c = 0.64 * (sensitivityFactor / 10) * intercept + + double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10; + + size_t i = 0; + for (CurveSegment seg : segments) { + // The library's curve format consists of four doubles per segment: + // * maximum pointer speed for the segment (mm/s) + // * multiplier for the x² term (a.k.a. "a" or "sqr") + // * multiplier for the x term (a.k.a. "b" or "mul") + // * the intercept (a.k.a. "c" or "int") + // (see struct CurveSegment in the library's AccelFilterInterpreter) + output[i + 0] = seg.maxPointerSpeedMmPerS; + output[i + 1] = 0; + output[i + 2] = commonFactor * seg.slope; + output[i + 3] = commonFactor * seg.intercept; + i += 4; + } + + return output; +} + short getMaxTouchCount(const InputDeviceContext& context) { if (context.hasScanCode(BTN_TOOL_QUINTTAP)) return 5; if (context.hasScanCode(BTN_TOOL_QUADTAP)) return 4; @@ -147,10 +218,12 @@ std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when, mGestureConverter.setOrientation(orientation); } if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS)) { - // TODO(b/265798483): load an Android-specific acceleration curve instead of mapping to one - // of five ChromeOS curves. - const int pointerSensitivity = (config->touchpadPointerSpeed + 7) / 3 + 1; - mPropertyProvider.getProperty("Pointer Sensitivity").setIntValues({pointerSensitivity}); + mPropertyProvider.getProperty("Use Custom Touchpad Pointer Accel Curve") + .setBoolValues({true}); + GesturesProp accelCurveProp = mPropertyProvider.getProperty("Pointer Accel Curve"); + accelCurveProp.setRealValues( + createAccelerationCurveForSensitivity(config->touchpadPointerSpeed, + accelCurveProp.getCount())); mPropertyProvider.getProperty("Invert Scrolling") .setBoolValues({config->touchpadNaturalScrollingEnabled}); mPropertyProvider.getProperty("Tap Enable") diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index e71cdce498..df1787b8e3 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -58,8 +58,23 @@ static constexpr int32_t SECOND_DEVICE_ID = 2; static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr int32_t SECOND_DISPLAY_ID = 1; +static constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +static constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +static constexpr int32_t ACTION_HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; +static constexpr int32_t ACTION_HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; static constexpr int32_t ACTION_OUTSIDE = AMOTION_EVENT_ACTION_OUTSIDE; static constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; +/** + * The POINTER_DOWN(0) is an unusual, but valid, action. It just means that the new pointer in the + * MotionEvent is at the index 0 rather than 1 (or later). That is, the pointer id=0 (which is at + * index 0) is the new pointer going down. The same pointer could have been placed at a different + * index, and the action would become POINTER_1_DOWN, 2, etc..; these would all be valid. In + * general, we try to place pointer id = 0 at the index 0. Of course, this is not possible if + * pointer id=0 leaves but the pointer id=1 remains. + */ +static constexpr int32_t POINTER_0_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); static constexpr int32_t POINTER_2_DOWN = @@ -145,6 +160,10 @@ MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") { return arg.getDisplayId() == displayId; } +MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") { + return arg.getDeviceId() == deviceId; +} + MATCHER_P(WithSource, source, "InputEvent with specified source") { *result_listener << "expected source " << inputEventSourceToString(source) << ", but got " << inputEventSourceToString(arg.getSource()); @@ -163,6 +182,10 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified coordinates") { return arg.getX(/*pointerIndex=*/0) == x && arg.getY(/*pointerIndex=*/0) == y; } +MATCHER_P(WithPointerCount, pointerCount, "MotionEvent with specified number of pointers") { + return arg.getPointerCount() == pointerCount; +} + MATCHER_P(WithPointers, pointers, "MotionEvent with specified pointers") { // Build a map for the received pointers, by pointer id std::map<int32_t /*pointerId*/, PointF> actualPointers; @@ -1922,6 +1945,48 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance } /** + * Two fingers down on the window, and lift off the first finger. + * Next, cancel the gesture to the window by removing the window. Make sure that the CANCEL event + * contains a single pointer. + */ +TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + NotifyMotionArgs args; + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + // Second touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .build())); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(110).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Remove the window. The gesture should be canceled + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + const std::map<int32_t, PointF> expectedPointers{{1, PointF{110, 100}}}; + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers))); +} + +/** * Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above, * with the following differences: * After ACTION_DOWN, Wallpaper window hangs up its channel, which forces the dispatcher to @@ -2029,8 +2094,17 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN) + .displayId(ADISPLAY_ID_DEFAULT) + .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) + .pointer(PointerBuilder(/* id */ 1, + AMOTION_EVENT_TOOL_TYPE_FINGER) + .x(100) + .y(100)) + .build(), + INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); @@ -2365,6 +2439,165 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { } /** + * Two windows: a window on the left and a window on the right. + * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains + * down. Then, on the left window, also place second touch pointer down. + * This test tries to reproduce a crash. + * In the buggy implementation, second pointer down on the left window would cause a crash. + */ +TEST_F(InputDispatcherTest, MultiDeviceSplitTouch) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> leftWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + leftWindow->setFrame(Rect(0, 0, 200, 200)); + + sp<FakeWindowHandle> rightWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + rightWindow->setFrame(Rect(200, 0, 400, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Start hovering over the left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Mouse down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // First touch pointer down on right window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + leftWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Second touch pointer down on left window + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); + // This MOVE event is not necessary (doesn't carry any new information), but it's there in the + // current implementation. + const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}}; + rightWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers))); + + leftWindow->assertNoEvents(); + rightWindow->assertNoEvents(); +} + +/** + * On a single window, use two different devices: mouse and touch. + * Touch happens first, with two pointers going down, and then the first pointer leaving. + * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL. + * Finally, a second touch pointer goes down again. Ensure the second touch pointer is ignored, + * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not + * represent a new gesture. + */ +TEST_F(InputDispatcherTest, MixedTouchAndMouseWithPointerDown) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 400, 400)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // First touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .build())); + // Second touch pointer down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + // First touch pointer lifts. The second one remains down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_0_UP)); + + // Mouse down. The touch should be canceled + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(1u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(320).y(100)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Second touch pointer down. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_0_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(300).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(350).y(100)) + .build())); + // The pointer_down event should be ignored + window->assertNoEvents(); +} + +/** * This test is similar to the test above, but the sequence of injected events is different. * * Two windows: a window on the left and a window on the right. @@ -2541,6 +2774,182 @@ TEST_F(InputDispatcherTest, StylusHoverAndTouchTap) { } /** + * A spy window above a window with no input channel. + * Start hovering with a stylus device, and then tap with it. + * Ensure spy window receives the entire sequence. + */ +TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setNoInputChannel(true); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + NotifyMotionArgs args; + + // Start hovering with stylus + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // Stylus touches down + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Stylus goes up + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_UP)); + + // Again hover + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); + // Stop hovering + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** + * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream. + * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse + * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active. + * While the mouse is down, new move events from the touch device should be ignored. + */ +TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> spyWindow = + sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + spyWindow->setFrame(Rect(0, 0, 200, 200)); + spyWindow->setTrustedOverlay(true); + spyWindow->setSpy(true); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 200, 200)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Hover a bit with mouse first + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Start touching + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(55).y(55)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Pilfer the stream + EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken())); + window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(60).y(60)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Mouse down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId))); + spyWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(100).y(100)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Mouse move! + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(110).y(110)) + .build())); + spyWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + + // Touch move! + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(65).y(65)) + .build())); + + // No more events + spyWindow->assertNoEvents(); + window->assertNoEvents(); +} + +/** * On the display, have a single window, and also an area where there's no window. * First pointer touches the "no window" area of the screen. Second pointer touches the window. * Make sure that the window receives the second pointer, and first pointer is simply ignored. @@ -2649,8 +3058,8 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { window1->assertNoEvents(); // Now move the pointer on the first window - mDispatcher->notifyMotion( - &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}}))); + mDispatcher->notifyMotion(&( + args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}, {150, 50}}))); mDispatcher->waitForIdle(); window1->consumeMotionEvent(WithDownTime(downTimeForWindow1)); @@ -2705,7 +3114,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(300) .y(400)) .build())); - windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + windowLeft->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2750,7 +3160,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .x(900) .y(400)) .build())); - windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // No more events @@ -2758,6 +3167,70 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { windowRight->assertNoEvents(); } +/** + * Put two fingers down (and don't release them) and click the mouse button. + * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the + * currently active gesture should be canceled, and the new one should proceed. + */ +TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 600, 800)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t touchDeviceId = 4; + const int32_t mouseDeviceId = 6; + NotifyMotionArgs args; + + // Two pointers down + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .build())); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(100).y(100)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(120).y(120)) + .build())); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); + + // Inject a series of mouse events for a mouse click + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId), + WithPointerCount(2u))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId))); + + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .buttonState(AMOTION_EVENT_BUTTON_PRIMARY) + .actionButton(AMOTION_EVENT_BUTTON_PRIMARY) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(300).y(400)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS)); + + // Try to send more touch events while the mouse is down. Since it's a continuation of an + // already canceled gesture, it should be ignored. + mDispatcher->notifyMotion(&( + args = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(101).y(101)) + .pointer(PointerBuilder(1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(121).y(121)) + .build())); + window->assertNoEvents(); +} + TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -2940,7 +3413,8 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); + window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2984,7 +3458,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .x(300) .y(400)) .build())); - window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); + window->assertNoEvents(); } /** @@ -3017,6 +3491,42 @@ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { } /** + * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT. + */ +TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(Rect(0, 0, 100, 100)); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + const int32_t mouseDeviceId = 7; + const int32_t touchDeviceId = 4; + NotifyMotionArgs args; + + // Start hovering with the mouse + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .deviceId(mouseDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE).x(10).y(10)) + .build())); + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId))); + + // Touch goes down + mDispatcher->notifyMotion( + &(args = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(touchDeviceId) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50)) + .build())); + + window->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId))); + window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId))); +} + +/** * Inject a mouse hover event followed by a tap from touchscreen. * The tap causes a HOVER_EXIT event to be generated because the current event * stream's source has been switched. @@ -5042,6 +5552,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID); monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID); + // Lift up the touch from the second display + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID)) + << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; + windowInSecondary->consumeMotionUp(SECOND_DISPLAY_ID); + monitorInSecondary.consumeMotionUp(SECOND_DISPLAY_ID); + // Test inject a non-pointer motion event. // If specific a display, it will dispatch to the focused window of particular display, // or it will dispatch to the focused window of focused display. diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp index b7aecdf1bd..08e00b4401 100644 --- a/services/sensorservice/aidl/SensorManager.cpp +++ b/services/sensorservice/aidl/SensorManager.cpp @@ -59,6 +59,9 @@ SensorManagerAidl::~SensorManagerAidl() { if (mPollThread.joinable()) { mPollThread.join(); } + + ::android::SensorManager::removeInstanceForPackage( + String16(ISensorManager::descriptor)); } ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type, diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp index f04712c534..3d148e103a 100644 --- a/services/sensorservice/hidl/SensorManager.cpp +++ b/services/sensorservice/hidl/SensorManager.cpp @@ -60,6 +60,9 @@ SensorManager::~SensorManager() { if (mPollThread.joinable()) { mPollThread.join(); } + + ::android::SensorManager::removeInstanceForPackage( + String16(ISensorManager::descriptor)); } // Methods from ::android::frameworks::sensorservice::V1_0::ISensorManager follow. diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp index eb6d7e4cfe..b78b92b698 100644 --- a/services/surfaceflinger/Scheduler/EventThread.cpp +++ b/services/surfaceflinger/Scheduler/EventThread.cpp @@ -384,23 +384,13 @@ VsyncEventData EventThread::getLatestVsyncEventData( return vsyncEventData; } -void EventThread::onScreenReleased() { +void EventThread::enableSyntheticVsync(bool enable) { std::lock_guard<std::mutex> lock(mMutex); - if (!mVSyncState || mVSyncState->synthetic) { + if (!mVSyncState || mVSyncState->synthetic == enable) { return; } - mVSyncState->synthetic = true; - mCondition.notify_all(); -} - -void EventThread::onScreenAcquired() { - std::lock_guard<std::mutex> lock(mMutex); - if (!mVSyncState || !mVSyncState->synthetic) { - return; - } - - mVSyncState->synthetic = false; + mVSyncState->synthetic = enable; mCondition.notify_all(); } diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h index 347dc4afd5..5037860482 100644 --- a/services/surfaceflinger/Scheduler/EventThread.h +++ b/services/surfaceflinger/Scheduler/EventThread.h @@ -106,11 +106,8 @@ public: virtual sp<EventThreadConnection> createEventConnection( ResyncCallback, EventRegistrationFlags eventRegistration = {}) const = 0; - // called before the screen is turned off from main thread - virtual void onScreenReleased() = 0; - - // called after the screen is turned on from main thread - virtual void onScreenAcquired() = 0; + // Feed clients with fake VSYNC, e.g. while the display is off. + virtual void enableSyntheticVsync(bool) = 0; virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0; @@ -159,11 +156,7 @@ public: VsyncEventData getLatestVsyncEventData( const sp<EventThreadConnection>& connection) const override; - // called before the screen is turned off from main thread - void onScreenReleased() override; - - // called after the screen is turned on from main thread - void onScreenAcquired() override; + void enableSyntheticVsync(bool) override; void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 4f5842a67a..5052e6e257 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -32,7 +32,6 @@ #include <scheduler/Seamlessness.h> #include "DisplayHardware/DisplayMode.h" -#include "DisplayHardware/HWComposer.h" #include "Scheduler/OneShotTimer.h" #include "Scheduler/StrongTyping.h" #include "ThreadContext.h" @@ -297,6 +296,8 @@ public: RefreshRateSelector(const RefreshRateSelector&) = delete; RefreshRateSelector& operator=(const RefreshRateSelector&) = delete; + const DisplayModes& displayModes() const { return mDisplayModes; } + // Returns whether switching modes (refresh rate or resolution) is possible. // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only // differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default, diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index dab01ba48d..0daabe2197 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -265,24 +265,16 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis thread->onHotplugReceived(displayId, connected); } -void Scheduler::onScreenAcquired(ConnectionHandle handle) { +void Scheduler::enableSyntheticVsync(bool enable) { + // TODO(b/241285945): Remove connection handles. + const ConnectionHandle handle = mAppConnectionHandle; android::EventThread* thread; { std::lock_guard<std::mutex> lock(mConnectionsLock); RETURN_IF_INVALID_HANDLE(handle); thread = mConnections[handle].thread.get(); } - thread->onScreenAcquired(); -} - -void Scheduler::onScreenReleased(ConnectionHandle handle) { - android::EventThread* thread; - { - std::lock_guard<std::mutex> lock(mConnectionsLock); - RETURN_IF_INVALID_HANDLE(handle); - thread = mConnections[handle].thread.get(); - } - thread->onScreenReleased(); + thread->enableSyntheticVsync(enable); } void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index a340919a65..67f4daa643 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -157,8 +157,8 @@ public: void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected); void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock); void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&); - void onScreenAcquired(ConnectionHandle); - void onScreenReleased(ConnectionHandle); + + void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext); void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId) EXCLUDES(mConnectionsLock); diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index d88f1d1f0b..a32acc7509 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -20,6 +20,7 @@ #include <string> #include <ThreadContext.h> +#include <android-base/thread_annotations.h> #include <ftl/enum.h> #include <ftl/optional.h> #include <scheduler/Features.h> @@ -27,6 +28,7 @@ namespace android { class EventThreadTest; +class VsyncScheduleTest; } namespace android::fuzz { @@ -96,6 +98,7 @@ public: private: friend class TestableScheduler; friend class android::EventThreadTest; + friend class android::VsyncScheduleTest; friend class android::fuzz::SchedulerFuzzer; using TrackerPtr = std::unique_ptr<VsyncTracker>; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f842de1440..c91ad49c9a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5212,7 +5212,6 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: return; } - const bool isActiveDisplay = displayId == mActiveDisplayId; const bool isInternalDisplay = mPhysicalDisplays.get(displayId) .transform(&PhysicalDisplay::isInternal) .value_or(false); @@ -5249,10 +5248,10 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: ALOGW("Couldn't set SCHED_FIFO on display on: %s\n", strerror(errno)); } getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && mode != hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && mode != hal::PowerMode::DOZE_SUSPEND) { setHWCVsyncEnabled(displayId, mScheduler->getVsyncSchedule().getPendingHardwareVsyncState()); - mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(false); mScheduler->resyncToHardwareVsync(true, refreshRate); } @@ -5266,9 +5265,9 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) { ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno)); } - if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) { mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(); } // Make sure HWVsync is disabled before turning off the display @@ -5280,18 +5279,18 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) { // Update display while dozing getHwComposer().setPowerMode(displayId, mode); - if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { + if (displayId == mActiveDisplayId && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) { ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON."); mVisibleRegionsDirty = true; scheduleRepaint(); - mScheduler->onScreenAcquired(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(false); mScheduler->resyncToHardwareVsync(true, refreshRate); } } else if (mode == hal::PowerMode::DOZE_SUSPEND) { // Leave display going to doze - if (isActiveDisplay) { + if (displayId == mActiveDisplayId) { mScheduler->disableHardwareVsync(true); - mScheduler->onScreenReleased(mAppConnectionHandle); + mScheduler->enableSyntheticVsync(); } getHwComposer().setPowerMode(displayId, mode); } else { @@ -5299,7 +5298,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal: getHwComposer().setPowerMode(displayId, mode); } - if (isActiveDisplay) { + if (displayId == mActiveDisplayId) { mTimeStats->setPowerMode(mode); mRefreshRateStats->setPowerMode(mode); mScheduler->setDisplayPowerMode(mode); diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp index 61fb29a964..80486a2b09 100644 --- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp +++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp @@ -109,8 +109,7 @@ void SchedulerFuzzer::fuzzEventThread() { thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()); thread->registerDisplayEventConnection(connection); - thread->onScreenAcquired(); - thread->onScreenReleased(); + thread->enableSyntheticVsync(mFdp.ConsumeBool()); dump<android::impl::EventThread>(thread.get(), &mFdp); } diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 87c3c65a74..eac9edc6dd 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -105,6 +105,7 @@ cc_test { "SurfaceFlinger_DisplayTransactionCommitTest.cpp", "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp", "SurfaceFlinger_HotplugTest.cpp", + "SurfaceFlinger_MultiDisplayLeaderTest.cpp", "SurfaceFlinger_NotifyPowerBoostTest.cpp", "SurfaceFlinger_OnInitializeDisplaysTest.cpp", "SurfaceFlinger_PowerHintTest.cpp", @@ -133,6 +134,7 @@ cc_test { "VSyncPredictorTest.cpp", "VSyncReactorTest.cpp", "VsyncConfigurationTest.cpp", + "VsyncScheduleTest.cpp", ], } diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h index 23a8bd1274..57d1d9c4d2 100644 --- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h +++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h @@ -503,14 +503,16 @@ struct PrimaryDisplay { static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid; }; -template <bool hasIdentificationData> -struct ExternalDisplay { - static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External; +template <ui::DisplayConnectionType connectionType, bool hasIdentificationData> +struct SecondaryDisplay { + static constexpr auto CONNECTION_TYPE = connectionType; static constexpr Primary PRIMARY = Primary::FALSE; static constexpr uint8_t PORT = 254; static constexpr HWDisplayId HWC_DISPLAY_ID = 1002; static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData; - static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid; + static constexpr auto GET_IDENTIFICATION_DATA = + connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid + : getExternalEdid; }; struct TertiaryDisplay { @@ -521,7 +523,16 @@ struct TertiaryDisplay { }; using PrimaryDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160>; -using ExternalDisplayVariant = PhysicalDisplayVariant<ExternalDisplay<false>, 1920, 1280>; + +using InnerDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<true>, 1840, 2208>; +using OuterDisplayVariant = + PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal, true>, 1080, + 2092>; + +using ExternalDisplayVariant = + PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External, false>, 1920, + 1280>; + using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200>; // A virtual display not supported by the HWC. diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 4b15385fa8..422fa1ca7e 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -122,12 +122,6 @@ TEST_F(SchedulerTest, invalidConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0); mScheduler->onHotplugReceived(handle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0); - mScheduler->onScreenAcquired(handle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0); - mScheduler->onScreenReleased(handle); - std::string output; EXPECT_CALL(*mEventThread, dump(_)).Times(0); mScheduler->dump(handle, output); @@ -147,12 +141,6 @@ TEST_F(SchedulerTest, validConnectionHandle) { EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1); mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false); - EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1); - mScheduler->onScreenAcquired(mConnectionHandle); - - EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1); - mScheduler->onScreenReleased(mConnectionHandle); - std::string output("dump"); EXPECT_CALL(*mEventThread, dump(output)).Times(1); mScheduler->dump(mConnectionHandle, output); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index f436a58238..019502f0fd 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -50,7 +50,7 @@ public: mFlinger.configureAndCommit(); mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(kModes, kModeId60, std::move(selectorPtr)) + .setRefreshRateSelector(std::move(selectorPtr)) .inject(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp new file mode 100644 index 0000000000..9c5894306c --- /dev/null +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_MultiDisplayLeaderTest.cpp @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "DisplayTransactionTestHelpers.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace android { +namespace { + +struct MultiDisplayLeaderTest : DisplayTransactionTest { + static constexpr bool kWithMockScheduler = false; + MultiDisplayLeaderTest() : DisplayTransactionTest(kWithMockScheduler) {} +}; + +TEST_F(MultiDisplayLeaderTest, foldable) { + injectMockScheduler(InnerDisplayVariant::DISPLAY_ID::get()); + + // Inject inner and outer displays with uninitialized power modes. + sp<DisplayDevice> innerDisplay, outerDisplay; + constexpr bool kInitPowerMode = false; + { + InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); + auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()); + innerDisplay = injector.inject(); + } + { + OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this); + auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this); + injector.setPowerMode(std::nullopt); + outerDisplay = injector.inject(); + } + + // When the device boots, the inner display should be the leader. + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // ...and should still be after powering on. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the leader after folding. + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId()); + + // The inner display should become the leader after unfolding. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::OFF); + mFlinger.setPowerModeInternal(innerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The inner display should stay the leader if both are powered on. + // TODO(b/256196556): The leader should depend on the displays' VSYNC phases. + mFlinger.setPowerModeInternal(outerDisplay, PowerMode::ON); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), innerDisplay->getPhysicalId()); + + // The outer display should become the leader if designated. + mFlinger.scheduler()->setLeaderDisplay(outerDisplay->getPhysicalId()); + ASSERT_EQ(mFlinger.scheduler()->leaderDisplayId(), outerDisplay->getPhysicalId()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp index a0aaa684f9..17b4714988 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp @@ -59,47 +59,34 @@ struct DozeNotSupportedVariant { }; struct EventThreadBaseSupportedVariant { - static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) { - // The callback should not be notified to toggle VSYNC. + static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) { + // Expect no change to hardware nor synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0); - - // The event thread should not be notified. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0); - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0); } }; struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // These calls are only expected for the primary display. - - // Instead expect no calls. - setupVsyncAndEventThreadNoCallExpectations(test); + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + setupVsyncNoCallExpectations(test); } }; struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant { - static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to enable VSYNC. + static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to enable hardware VSYNC and disable synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1); - - // The event thread should be notified that the screen was acquired. - EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1); } - static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) { - // The callback should be notified to disable VSYNC. + static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) { + // Expect to disable hardware VSYNC and enable synthetic VSYNC. EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1); - - // The event thread should not be notified that the screen was released. - EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1); + EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1); } }; @@ -133,7 +120,7 @@ struct TransitionOffToOnVariant : public TransitionVariantCommon<PowerMode::OFF, template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -148,7 +135,7 @@ struct TransitionOffToDozeSuspendVariant template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupRepaintEverythingCallExpectations(test); } @@ -160,7 +147,7 @@ struct TransitionOffToDozeSuspendVariant struct TransitionOnToOffVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::OFF> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -173,7 +160,7 @@ struct TransitionDozeSuspendToOffVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::OFF> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF); } @@ -185,7 +172,7 @@ struct TransitionDozeSuspendToOffVariant struct TransitionOnToDozeVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } }; @@ -194,7 +181,7 @@ struct TransitionDozeSuspendToDozeVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::DOZE> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE); } @@ -203,7 +190,7 @@ struct TransitionDozeSuspendToDozeVariant struct TransitionDozeToOnVariant : public TransitionVariantCommon<PowerMode::DOZE, PowerMode::ON> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } }; @@ -212,7 +199,7 @@ struct TransitionDozeSuspendToOnVariant : public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::ON> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test); + Case::EventThread::setupEnableVsyncCallExpectations(test); Case::DispSync::setupResetModelCallExpectations(test); Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON); } @@ -222,7 +209,7 @@ struct TransitionOnToDozeSuspendVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE_SUSPEND> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test); + Case::EventThread::setupDisableVsyncCallExpectations(test); Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND); } }; @@ -231,7 +218,7 @@ struct TransitionOnToUnknownVariant : public TransitionVariantCommon<PowerMode::ON, static_cast<PowerMode>(POWER_MODE_LEET)> { template <typename Case> static void setupCallExpectations(DisplayTransactionTest* test) { - Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test); + Case::EventThread::setupVsyncNoCallExpectations(test); Case::setupNoComposerPowerModeCallExpectations(test); } }; @@ -484,38 +471,5 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownExternalDispla transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>(); } -// TODO(b/262417075) -TEST_F(SetPowerModeInternalTest, DISABLED_designatesLeaderDisplay) { - using Case = SimplePrimaryDisplayCase; - - // -------------------------------------------------------------------- - // Preconditions - - // Inject a primary display with uninitialized power mode. - constexpr bool kInitPowerMode = false; - Case::Display::injectHwcDisplay<kInitPowerMode>(this); - auto injector = Case::Display::makeFakeExistingDisplayInjector(this); - injector.setPowerMode(std::nullopt); - const auto display = injector.inject(); - - // -------------------------------------------------------------------- - // Invocation - - // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been - // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal` - // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s). - constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42); - ASSERT_NE(display->getPhysicalId(), kPlaceholderId); - mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId); - - mFlinger.setPowerModeInternal(display, PowerMode::ON); - - // -------------------------------------------------------------------- - // Postconditions - - // The primary display should be designated as the leader. - EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId()); -} - } // namespace } // namespace android diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index bd3f3cae98..48c5d48c98 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -91,6 +91,7 @@ public: Scheduler::setLeaderDisplay(displayId); } + auto& mutableAppConnectionHandle() { return mAppConnectionHandle; } auto& mutableVsyncModulator() { return *mVsyncModulator; } auto& mutableLayerHistory() { return mLayerHistory; } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index dbde3b16d2..448803817a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -252,7 +252,10 @@ public: mScheduler->initVsync(mScheduler->getVsyncSchedule().getDispatch(), *mTokenManager, 0ms); - mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread)); + mScheduler->mutableAppConnectionHandle() = + mScheduler->createConnection(std::move(appEventThread)); + + mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle(); mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread)); resetScheduler(mScheduler); } @@ -801,16 +804,16 @@ public: return mFlinger.mutableDisplays().get(mDisplayToken)->get(); } - // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`. - // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`. - // - // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove - // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API. - auto& setDisplayModes( - DisplayModes modes, DisplayModeId activeModeId, - std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr = nullptr) { + auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { mDisplayModes = std::move(modes); mCreationArgs.activeModeId = activeModeId; + mCreationArgs.refreshRateSelector = nullptr; + return *this; + } + + auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { + mDisplayModes = selectorPtr->displayModes(); + mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId(); mCreationArgs.refreshRateSelector = std::move(selectorPtr); return *this; } diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp new file mode 100644 index 0000000000..652d3132f9 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include <ftl/fake_guard.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <log/log.h> + +#include <scheduler/Fps.h> +#include "Scheduler/VsyncSchedule.h" +#include "ThreadContext.h" +#include "mock/MockSchedulerCallback.h" +#include "mock/MockVSyncDispatch.h" +#include "mock/MockVSyncTracker.h" +#include "mock/MockVsyncController.h" + +using testing::_; + +namespace android { + +class VsyncScheduleTest : public testing::Test { +protected: + VsyncScheduleTest(); + ~VsyncScheduleTest() override; + + scheduler::mock::SchedulerCallback mCallback; + const std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule = + std::unique_ptr<scheduler::VsyncSchedule>( + new scheduler::VsyncSchedule(std::make_unique<mock::VSyncTracker>(), + std::make_unique<mock::VSyncDispatch>(), + std::make_unique<mock::VsyncController>())); + + mock::VsyncController& getController() { + return *static_cast<mock::VsyncController*>(&mVsyncSchedule->getController()); + } +}; + +VsyncScheduleTest::VsyncScheduleTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); +} + +VsyncScheduleTest::~VsyncScheduleTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); +} + +namespace { + +using namespace testing; + +TEST_F(VsyncScheduleTest, InitiallyDisallowed) { + ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, EnableDoesNothingWhenDisallowed) { + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed) { + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, MakeAllowed) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, EnableWorksWhenDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, EnableWorksOnce) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); + + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + mVsyncSchedule->enableHardwareVsync(mCallback); +} + +TEST_F(VsyncScheduleTest, AllowedIsSticky) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */)); +} + +TEST_F(VsyncScheduleTest, EnableDisable) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + EXPECT_CALL(mCallback, setVsyncEnabled(true)); + + mVsyncSchedule->enableHardwareVsync(mCallback); + + EXPECT_CALL(mCallback, setVsyncEnabled(false)); + mVsyncSchedule->disableHardwareVsync(mCallback, false /* disallow */); +} + +TEST_F(VsyncScheduleTest, StartPeriodTransition) { + // Note: startPeriodTransition is only called when hardware vsyncs are + // allowed. + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + + const Period period = (60_Hz).getPeriod(); + + EXPECT_CALL(mCallback, setVsyncEnabled(true)); + EXPECT_CALL(getController(), startPeriodTransition(period.ns())); + + mVsyncSchedule->startPeriodTransition(mCallback, period); +} + +TEST_F(VsyncScheduleTest, StartPeriodTransitionAlreadyEnabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + EXPECT_CALL(getController(), startPeriodTransition(period.ns())); + + mVsyncSchedule->startPeriodTransition(mCallback, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleDisallowed) { + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleDisabled) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleReturnsTrue) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(_)).Times(0); + EXPECT_CALL(getController(), + addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _)) + .WillOnce(Return(true)); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, AddResyncSampleReturnsFalse) { + ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */)); + mVsyncSchedule->enableHardwareVsync(mCallback); + + const Period period = (60_Hz).getPeriod(); + const auto timestamp = TimePoint::now(); + + EXPECT_CALL(mCallback, setVsyncEnabled(false)); + EXPECT_CALL(getController(), + addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _)) + .WillOnce(Return(false)); + + mVsyncSchedule->addResyncSample(mCallback, timestamp, period); +} + +TEST_F(VsyncScheduleTest, PendingState) FTL_FAKE_GUARD(kMainThreadContext) { + ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState()); + mVsyncSchedule->setPendingHardwareVsyncState(true); + ASSERT_TRUE(mVsyncSchedule->getPendingHardwareVsyncState()); + + mVsyncSchedule->setPendingHardwareVsyncState(false); + ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState()); +} + +} // namespace +} // namespace android diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h index f8567bd636..8026a7accd 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h +++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h @@ -31,8 +31,7 @@ public: MOCK_CONST_METHOD2(createEventConnection, sp<EventThreadConnection>(ResyncCallback, EventRegistrationFlags)); - MOCK_METHOD0(onScreenReleased, void()); - MOCK_METHOD0(onScreenAcquired, void()); + MOCK_METHOD(void, enableSyntheticVsync, (bool), (override)); MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool)); MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &)); MOCK_METHOD2(onFrameRateOverridesChanged, diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 7d4b159e5e..103beb5fc0 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -18,7 +18,7 @@ #include <gmock/gmock.h> -#include "Scheduler/Scheduler.h" +#include "Scheduler/ISchedulerCallback.h" namespace android::scheduler::mock { |