/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "TouchSpotController" // Log debug messages about pointer updates #define DEBUG_SPOT_UPDATES 0 #include "TouchSpotController.h" #include #include #include #include #define INDENT " " #define INDENT2 " " namespace { // Time to spend fading out the spot completely. const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms } // namespace namespace android { // --- Spot --- void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); sprite->setPosition(x, y); sprite->setDisplayId(displayId); this->x = x; this->y = y; if (icon != mLastIcon) { mLastIcon = icon; if (icon) { sprite->setIcon(*icon); sprite->setVisible(true); } else { sprite->setVisible(false); } } } void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const { out += prefix; base::StringAppendF(&out, "Spot{id=%" PRIx32 ", alpha=%f, scale=%f, pos=[%f, %f]}\n", id, alpha, scale, x, y); } // --- TouchSpotController --- TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context) : mDisplayId(displayId), mContext(context) { mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId); } TouchSpotController::~TouchSpotController() { std::scoped_lock lock(mLock); size_t numSpots = mLocked.displaySpots.size(); for (size_t i = 0; i < numSpots; i++) { delete mLocked.displaySpots[i]; } mLocked.displaySpots.clear(); } void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { #if DEBUG_SPOT_UPDATES ALOGD("setSpots: idBits=%08x", spotIdBits.value); for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { uint32_t id = idBits.firstMarkedBit(); idBits.clearBit(id); const PointerCoords& c = spotCoords[spotIdToIndex[id]]; ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId); } #endif std::scoped_lock lock(mLock); sp spriteController = mContext.getSpriteController(); spriteController->openTransaction(); // Add or move spots for fingers that are down. for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { uint32_t id = idBits.clearFirstMarkedBit(); const PointerCoords& c = spotCoords[spotIdToIndex[id]]; const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0 ? mResources.spotTouch : mResources.spotHover; float x = c.getAxisValue(AMOTION_EVENT_AXIS_X); float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y); Spot* spot = getSpot(id, mLocked.displaySpots); if (!spot) { spot = createAndAddSpotLocked(id, mLocked.displaySpots); } spot->updateSprite(&icon, x, y, mDisplayId); } for (Spot* spot : mLocked.displaySpots) { if (spot->id != Spot::INVALID_ID && !spotIdBits.hasBit(spot->id)) { fadeOutAndReleaseSpotLocked(spot); } } spriteController->closeTransaction(); } void TouchSpotController::clearSpots() { #if DEBUG_SPOT_UPDATES ALOGD("clearSpots"); #endif std::scoped_lock lock(mLock); fadeOutAndReleaseAllSpotsLocked(); } TouchSpotController::Spot* TouchSpotController::getSpot(uint32_t id, const std::vector& spots) { for (size_t i = 0; i < spots.size(); i++) { Spot* spot = spots[i]; if (spot->id == id) { return spot; } } return nullptr; } TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t id, std::vector& spots) REQUIRES(mLock) { // Remove spots until we have fewer than MAX_SPOTS remaining. while (spots.size() >= MAX_SPOTS) { Spot* spot = removeFirstFadingSpotLocked(spots); if (!spot) { spot = spots[0]; spots.erase(spots.begin()); } releaseSpotLocked(spot); } // Obtain a sprite from the recycled pool. sp sprite; if (!mLocked.recycledSprites.empty()) { sprite = mLocked.recycledSprites.back(); mLocked.recycledSprites.pop_back(); } else { sprite = mContext.getSpriteController()->createSprite(); } // Return the new spot. Spot* spot = new Spot(id, sprite); spots.push_back(spot); return spot; } TouchSpotController::Spot* TouchSpotController::removeFirstFadingSpotLocked( std::vector& spots) REQUIRES(mLock) { for (size_t i = 0; i < spots.size(); i++) { Spot* spot = spots[i]; if (spot->id == Spot::INVALID_ID) { spots.erase(spots.begin() + i); return spot; } } return NULL; } void TouchSpotController::releaseSpotLocked(Spot* spot) REQUIRES(mLock) { spot->sprite->clearIcon(); if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { mLocked.recycledSprites.push_back(spot->sprite); } delete spot; } void TouchSpotController::fadeOutAndReleaseSpotLocked(Spot* spot) REQUIRES(mLock) { if (spot->id != Spot::INVALID_ID) { spot->id = Spot::INVALID_ID; startAnimationLocked(); } } void TouchSpotController::fadeOutAndReleaseAllSpotsLocked() REQUIRES(mLock) { size_t numSpots = mLocked.displaySpots.size(); for (size_t i = 0; i < numSpots; i++) { Spot* spot = mLocked.displaySpots[i]; fadeOutAndReleaseSpotLocked(spot); } } void TouchSpotController::reloadSpotResources() { mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId); } bool TouchSpotController::doAnimations(nsecs_t timestamp) { std::scoped_lock lock(mLock); bool keepAnimating = doFadingAnimationLocked(timestamp); if (!keepAnimating) { /* * We know that this callback will be removed before another * is added. mLock in PointerAnimator will not be released * until after this is removed, and adding another callback * requires that lock. Thus it's safe to set mLocked.animating * here. */ mLocked.animating = false; } return keepAnimating; } bool TouchSpotController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) { bool keepAnimating = false; nsecs_t animationTime = mContext.getAnimationTime(); nsecs_t frameDelay = timestamp - animationTime; size_t numSpots = mLocked.displaySpots.size(); for (size_t i = 0; i < numSpots;) { Spot* spot = mLocked.displaySpots[i]; if (spot->id == Spot::INVALID_ID) { spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; if (spot->alpha <= 0) { mLocked.displaySpots.erase(mLocked.displaySpots.begin() + i); releaseSpotLocked(spot); numSpots--; continue; } else { spot->sprite->setAlpha(spot->alpha); keepAnimating = true; } } ++i; } return keepAnimating; } void TouchSpotController::startAnimationLocked() REQUIRES(mLock) { using namespace std::placeholders; if (mLocked.animating) { return; } mLocked.animating = true; std::function func = std::bind(&TouchSpotController::doAnimations, this, _1); mContext.addAnimationCallback(mDisplayId, func); } void TouchSpotController::dump(std::string& out, const char* prefix) const { using base::StringAppendF; out += prefix; out += "SpotController:\n"; out += prefix; StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId); std::scoped_lock lock(mLock); out += prefix; StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating)); out += prefix; out += INDENT "Spots:\n"; std::string spotPrefix = prefix; spotPrefix += INDENT2; for (const auto& spot : mLocked.displaySpots) { spot->dump(out, spotPrefix.c_str()); } } } // namespace android