| /* | 
 |  * 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 <android-base/stringprintf.h> | 
 | #include <input/PrintTools.h> | 
 | #include <log/log.h> | 
 |  | 
 | #include <mutex> | 
 |  | 
 | #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> 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<Spot*>& 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<Spot*>& 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> 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<Spot*>& 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<bool(nsecs_t)> 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 |