| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * 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 "PointerController" |
| //#define LOG_NDEBUG 0 |
| |
| #include "PointerController.h" |
| |
| #include <SkBlendMode.h> |
| #include <SkCanvas.h> |
| #include <SkColor.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/thread_annotations.h> |
| #include <com_android_input_flags.h> |
| #include <ftl/enum.h> |
| |
| #include <mutex> |
| |
| #include "PointerControllerContext.h" |
| |
| #define INDENT " " |
| #define INDENT2 " " |
| #define INDENT3 " " |
| |
| namespace input_flags = com::android::input::flags; |
| |
| namespace android { |
| |
| namespace { |
| |
| static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); |
| |
| const ui::Transform kIdentityTransform; |
| |
| } // namespace |
| |
| // --- PointerController::DisplayInfoListener --- |
| |
| void PointerController::DisplayInfoListener::onWindowInfosChanged( |
| const gui::WindowInfosUpdate& update) { |
| std::scoped_lock lock(mLock); |
| if (mPointerController == nullptr) return; |
| |
| // PointerController uses DisplayInfoListener's lock. |
| base::ScopedLockAssertion assumeLocked(mPointerController->getLock()); |
| mPointerController->onDisplayInfosChangedLocked(update.displayInfos); |
| } |
| |
| void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { |
| std::scoped_lock lock(mLock); |
| mPointerController = nullptr; |
| } |
| |
| // --- PointerController --- |
| |
| std::shared_ptr<PointerController> PointerController::create( |
| const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, |
| SpriteController& spriteController, bool enabled, ControllerType type) { |
| // using 'new' to access non-public constructor |
| std::shared_ptr<PointerController> controller; |
| switch (type) { |
| case ControllerType::MOUSE: |
| controller = std::shared_ptr<PointerController>( |
| new MousePointerController(policy, looper, spriteController, enabled)); |
| break; |
| case ControllerType::TOUCH: |
| controller = std::shared_ptr<PointerController>( |
| new TouchPointerController(policy, looper, spriteController, enabled)); |
| break; |
| case ControllerType::STYLUS: |
| controller = std::shared_ptr<PointerController>( |
| new StylusPointerController(policy, looper, spriteController, enabled)); |
| break; |
| case ControllerType::LEGACY: |
| default: |
| controller = std::shared_ptr<PointerController>( |
| new PointerController(policy, looper, spriteController, enabled)); |
| break; |
| } |
| |
| /* |
| * Now we need to hook up the constructed PointerController object to its callbacks. |
| * |
| * This must be executed after the constructor but before any other methods on PointerController |
| * in order to ensure that the fully constructed object is visible on the Looper thread, since |
| * that may be a different thread than where the PointerController is initially constructed. |
| * |
| * Unfortunately, this cannot be done as part of the constructor since we need to hand out |
| * weak_ptr's which themselves cannot be constructed until there's at least one shared_ptr. |
| */ |
| |
| controller->mContext.setHandlerController(controller); |
| controller->mContext.setCallbackController(controller); |
| return controller; |
| } |
| |
| PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, |
| const sp<Looper>& looper, SpriteController& spriteController, |
| bool enabled) |
| : PointerController( |
| policy, looper, spriteController, enabled, |
| [](const sp<android::gui::WindowInfosListener>& listener) { |
| auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{}, |
| std::vector<android::gui::DisplayInfo>{}); |
| SurfaceComposerClient::getDefault()->addWindowInfosListener(listener, |
| &initialInfo); |
| return initialInfo.second; |
| }, |
| [](const sp<android::gui::WindowInfosListener>& listener) { |
| SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); |
| }) {} |
| |
| PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, |
| const sp<Looper>& looper, SpriteController& spriteController, |
| bool enabled, |
| const WindowListenerRegisterConsumer& registerListener, |
| WindowListenerUnregisterConsumer unregisterListener) |
| : mEnabled(enabled), |
| mContext(policy, looper, spriteController, *this), |
| mCursorController(mContext), |
| mDisplayInfoListener(sp<DisplayInfoListener>::make(this)), |
| mUnregisterWindowInfosListener(std::move(unregisterListener)) { |
| std::scoped_lock lock(getLock()); |
| mLocked.presentation = Presentation::SPOT; |
| const auto& initialDisplayInfos = registerListener(mDisplayInfoListener); |
| onDisplayInfosChangedLocked(initialDisplayInfos); |
| } |
| |
| PointerController::~PointerController() { |
| mDisplayInfoListener->onPointerControllerDestroyed(); |
| mUnregisterWindowInfosListener(mDisplayInfoListener); |
| mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0}); |
| } |
| |
| std::mutex& PointerController::getLock() const { |
| return mDisplayInfoListener->mLock; |
| } |
| |
| std::optional<FloatRect> PointerController::getBounds() const { |
| if (!mEnabled) return {}; |
| |
| return mCursorController.getBounds(); |
| } |
| |
| void PointerController::move(float deltaX, float deltaY) { |
| if (!mEnabled) return; |
| |
| const int32_t displayId = mCursorController.getDisplayId(); |
| vec2 transformed; |
| { |
| std::scoped_lock lock(getLock()); |
| const auto& transform = getTransformForDisplayLocked(displayId); |
| transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); |
| } |
| mCursorController.move(transformed.x, transformed.y); |
| } |
| |
| void PointerController::setPosition(float x, float y) { |
| if (!mEnabled) return; |
| |
| const int32_t displayId = mCursorController.getDisplayId(); |
| vec2 transformed; |
| { |
| std::scoped_lock lock(getLock()); |
| const auto& transform = getTransformForDisplayLocked(displayId); |
| transformed = transform.transform(x, y); |
| } |
| mCursorController.setPosition(transformed.x, transformed.y); |
| } |
| |
| FloatPoint PointerController::getPosition() const { |
| if (!mEnabled) { |
| return FloatPoint{0, 0}; |
| } |
| |
| const int32_t displayId = mCursorController.getDisplayId(); |
| const auto p = mCursorController.getPosition(); |
| { |
| std::scoped_lock lock(getLock()); |
| const auto& transform = getTransformForDisplayLocked(displayId); |
| return FloatPoint{transform.inverse().transform(p.x, p.y)}; |
| } |
| } |
| |
| int32_t PointerController::getDisplayId() const { |
| if (!mEnabled) return ADISPLAY_ID_NONE; |
| |
| return mCursorController.getDisplayId(); |
| } |
| |
| void PointerController::fade(Transition transition) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| mCursorController.fade(transition); |
| } |
| |
| void PointerController::unfade(Transition transition) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| mCursorController.unfade(transition); |
| } |
| |
| void PointerController::setPresentation(Presentation presentation) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| |
| if (mLocked.presentation == presentation) { |
| return; |
| } |
| |
| mLocked.presentation = presentation; |
| |
| if (ENABLE_POINTER_CHOREOGRAPHER) { |
| // When pointer choreographer is enabled, the presentation mode is only set once when the |
| // PointerController is constructed, before the display viewport is provided. |
| // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer |
| // is permanently enabled. The presentation can be set in the constructor. |
| mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); |
| return; |
| } |
| |
| if (!mCursorController.isViewportValid()) { |
| return; |
| } |
| |
| if (presentation == Presentation::POINTER || presentation == Presentation::STYLUS_HOVER) { |
| // For now, we support stylus hover using the mouse cursor implementation. |
| // TODO: Add proper support for stylus hover icons. |
| mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); |
| |
| mCursorController.getAdditionalMouseResources(); |
| clearSpotsLocked(); |
| } |
| } |
| |
| void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, |
| BitSet32 spotIdBits, int32_t displayId) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; |
| const ui::Transform& transform = getTransformForDisplayLocked(displayId); |
| |
| for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { |
| const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()]; |
| |
| const vec2 xy = transform.transform(spotCoords[index].getXYValue()); |
| outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); |
| outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); |
| |
| float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); |
| outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); |
| } |
| |
| auto it = mLocked.spotControllers.find(displayId); |
| if (it == mLocked.spotControllers.end()) { |
| mLocked.spotControllers.try_emplace(displayId, displayId, mContext); |
| } |
| mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits); |
| } |
| |
| void PointerController::clearSpots() { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| clearSpotsLocked(); |
| } |
| |
| void PointerController::clearSpotsLocked() { |
| for (auto& [displayId, spotController] : mLocked.spotControllers) { |
| spotController.clearSpots(); |
| } |
| } |
| |
| void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) { |
| mContext.setInactivityTimeout(inactivityTimeout); |
| } |
| |
| void PointerController::reloadPointerResources() { |
| std::scoped_lock lock(getLock()); |
| |
| for (auto& [displayId, spotController] : mLocked.spotControllers) { |
| spotController.reloadSpotResources(); |
| } |
| |
| if (mCursorController.resourcesLoaded()) { |
| bool getAdditionalMouseResources = false; |
| if (mLocked.presentation == PointerController::Presentation::POINTER || |
| mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) { |
| getAdditionalMouseResources = true; |
| } |
| mCursorController.reloadPointerResources(getAdditionalMouseResources); |
| } |
| } |
| |
| void PointerController::setDisplayViewport(const DisplayViewport& viewport) { |
| struct PointerDisplayChangeArgs { |
| int32_t displayId; |
| FloatPoint cursorPosition; |
| }; |
| std::optional<PointerDisplayChangeArgs> pointerDisplayChanged; |
| |
| { // acquire lock |
| std::scoped_lock lock(getLock()); |
| |
| bool getAdditionalMouseResources = false; |
| if (mLocked.presentation == PointerController::Presentation::POINTER || |
| mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) { |
| getAdditionalMouseResources = true; |
| } |
| mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources); |
| if (viewport.displayId != mLocked.pointerDisplayId) { |
| mLocked.pointerDisplayId = viewport.displayId; |
| pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()}; |
| } |
| } // release lock |
| |
| if (pointerDisplayChanged) { |
| // Notify the policy without holding the pointer controller lock. |
| mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId, |
| pointerDisplayChanged->cursorPosition); |
| } |
| } |
| |
| void PointerController::updatePointerIcon(PointerIconStyle iconId) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| mCursorController.updatePointerIcon(iconId); |
| } |
| |
| void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { |
| if (!mEnabled) return; |
| |
| std::scoped_lock lock(getLock()); |
| mCursorController.setCustomPointerIcon(icon); |
| } |
| |
| void PointerController::doInactivityTimeout() { |
| fade(Transition::GRADUAL); |
| } |
| |
| void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) { |
| std::unordered_set<int32_t> displayIdSet; |
| for (const DisplayViewport& viewport : viewports) { |
| displayIdSet.insert(viewport.displayId); |
| } |
| |
| std::scoped_lock lock(getLock()); |
| for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { |
| int32_t displayId = it->first; |
| if (!displayIdSet.count(displayId)) { |
| /* |
| * Ensures that an in-progress animation won't dereference |
| * a null pointer to TouchSpotController. |
| */ |
| mContext.removeAnimationCallback(displayId); |
| it = mLocked.spotControllers.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void PointerController::onDisplayInfosChangedLocked( |
| const std::vector<gui::DisplayInfo>& displayInfo) { |
| mLocked.mDisplayInfos = displayInfo; |
| } |
| |
| const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { |
| const auto& di = mLocked.mDisplayInfos; |
| auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { |
| return info.displayId == displayId; |
| }); |
| return it != di.end() ? it->transform : kIdentityTransform; |
| } |
| |
| std::string PointerController::dump() { |
| if (!mEnabled) { |
| return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n"; |
| } |
| |
| std::string dump = INDENT "PointerController:\n"; |
| std::scoped_lock lock(getLock()); |
| dump += StringPrintf(INDENT2 "Presentation: %s\n", |
| ftl::enum_string(mLocked.presentation).c_str()); |
| dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId); |
| dump += StringPrintf(INDENT2 "Viewports:\n"); |
| for (const auto& info : mLocked.mDisplayInfos) { |
| info.dump(dump, INDENT3); |
| } |
| dump += INDENT2 "Spot Controllers:\n"; |
| for (const auto& [_, spotController] : mLocked.spotControllers) { |
| spotController.dump(dump, INDENT3); |
| } |
| return dump; |
| } |
| |
| // --- MousePointerController --- |
| |
| MousePointerController::MousePointerController(const sp<PointerControllerPolicyInterface>& policy, |
| const sp<Looper>& looper, |
| SpriteController& spriteController, bool enabled) |
| : PointerController(policy, looper, spriteController, enabled) { |
| PointerController::setPresentation(Presentation::POINTER); |
| } |
| |
| MousePointerController::~MousePointerController() { |
| MousePointerController::fade(Transition::IMMEDIATE); |
| } |
| |
| // --- TouchPointerController --- |
| |
| TouchPointerController::TouchPointerController(const sp<PointerControllerPolicyInterface>& policy, |
| const sp<Looper>& looper, |
| SpriteController& spriteController, bool enabled) |
| : PointerController(policy, looper, spriteController, enabled) { |
| PointerController::setPresentation(Presentation::SPOT); |
| } |
| |
| TouchPointerController::~TouchPointerController() { |
| TouchPointerController::clearSpots(); |
| } |
| |
| // --- StylusPointerController --- |
| |
| StylusPointerController::StylusPointerController(const sp<PointerControllerPolicyInterface>& policy, |
| const sp<Looper>& looper, |
| SpriteController& spriteController, bool enabled) |
| : PointerController(policy, looper, spriteController, enabled) { |
| PointerController::setPresentation(Presentation::STYLUS_HOVER); |
| } |
| |
| StylusPointerController::~StylusPointerController() { |
| StylusPointerController::fade(Transition::IMMEDIATE); |
| } |
| |
| } // namespace android |