blob: f7c685ff8ba6555252b47595888c8d89578ee85e [file] [log] [blame]
/*
* 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 <log/log.h>
#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkPaint.h>
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);
}
}
}
// --- 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);
}
} // namespace android