michael@0: /* michael@0: * Copyright (C) 2010 The Android Open Source Project michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #define LOG_TAG "PointerController" michael@0: michael@0: //#define LOG_NDEBUG 0 michael@0: michael@0: // Log debug messages about pointer updates michael@0: #define DEBUG_POINTER_UPDATES 0 michael@0: michael@0: #include "PointerController.h" michael@0: michael@0: #include "cutils_log.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: namespace android { michael@0: michael@0: // --- PointerController --- michael@0: michael@0: // Time to wait before starting the fade when the pointer is inactive. michael@0: static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds michael@0: static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds michael@0: michael@0: // Time to wait between animation frames. michael@0: static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60; michael@0: michael@0: // Time to spend fading out the spot completely. michael@0: static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms michael@0: michael@0: // Time to spend fading out the pointer completely. michael@0: static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms michael@0: michael@0: michael@0: // --- PointerController --- michael@0: michael@0: PointerController::PointerController(const sp& policy, michael@0: const sp& looper, const sp& spriteController) : michael@0: mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { michael@0: mHandler = new WeakMessageHandler(this); michael@0: michael@0: AutoMutex _l(mLock); michael@0: michael@0: mLocked.animationPending = false; michael@0: michael@0: mLocked.displayWidth = -1; michael@0: mLocked.displayHeight = -1; michael@0: mLocked.displayOrientation = DISPLAY_ORIENTATION_0; michael@0: michael@0: mLocked.presentation = PRESENTATION_POINTER; michael@0: mLocked.presentationChanged = false; michael@0: michael@0: mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL; michael@0: michael@0: mLocked.pointerFadeDirection = 0; michael@0: mLocked.pointerX = 0; michael@0: mLocked.pointerY = 0; michael@0: mLocked.pointerAlpha = 0.0f; // pointer is initially faded michael@0: mLocked.pointerSprite = mSpriteController->createSprite(); michael@0: mLocked.pointerIconChanged = false; michael@0: michael@0: mLocked.buttonState = 0; michael@0: michael@0: loadResources(); michael@0: } michael@0: michael@0: PointerController::~PointerController() { michael@0: mLooper->removeMessages(mHandler); michael@0: michael@0: AutoMutex _l(mLock); michael@0: michael@0: mLocked.pointerSprite.clear(); michael@0: michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: delete mLocked.spots.itemAt(i); michael@0: } michael@0: mLocked.spots.clear(); michael@0: mLocked.recycledSprites.clear(); michael@0: } michael@0: michael@0: bool PointerController::getBounds(float* outMinX, float* outMinY, michael@0: float* outMaxX, float* outMaxY) const { michael@0: AutoMutex _l(mLock); michael@0: michael@0: return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY); michael@0: } michael@0: michael@0: bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, michael@0: float* outMaxX, float* outMaxY) const { michael@0: if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: *outMinX = 0; michael@0: *outMinY = 0; michael@0: switch (mLocked.displayOrientation) { michael@0: case DISPLAY_ORIENTATION_90: michael@0: case DISPLAY_ORIENTATION_270: michael@0: *outMaxX = mLocked.displayHeight - 1; michael@0: *outMaxY = mLocked.displayWidth - 1; michael@0: break; michael@0: default: michael@0: *outMaxX = mLocked.displayWidth - 1; michael@0: *outMaxY = mLocked.displayHeight - 1; michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void PointerController::move(float deltaX, float deltaY) { michael@0: #if DEBUG_POINTER_UPDATES michael@0: ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); michael@0: #endif michael@0: if (deltaX == 0.0f && deltaY == 0.0f) { michael@0: return; michael@0: } michael@0: michael@0: AutoMutex _l(mLock); michael@0: michael@0: setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); michael@0: } michael@0: michael@0: void PointerController::setButtonState(int32_t buttonState) { michael@0: #if DEBUG_POINTER_UPDATES michael@0: ALOGD("Set button state 0x%08x", buttonState); michael@0: #endif michael@0: AutoMutex _l(mLock); michael@0: michael@0: if (mLocked.buttonState != buttonState) { michael@0: mLocked.buttonState = buttonState; michael@0: } michael@0: } michael@0: michael@0: int32_t PointerController::getButtonState() const { michael@0: AutoMutex _l(mLock); michael@0: michael@0: return mLocked.buttonState; michael@0: } michael@0: michael@0: void PointerController::setPosition(float x, float y) { michael@0: #if DEBUG_POINTER_UPDATES michael@0: ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); michael@0: #endif michael@0: AutoMutex _l(mLock); michael@0: michael@0: setPositionLocked(x, y); michael@0: } michael@0: michael@0: void PointerController::setPositionLocked(float x, float y) { michael@0: float minX, minY, maxX, maxY; michael@0: if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { michael@0: if (x <= minX) { michael@0: mLocked.pointerX = minX; michael@0: } else if (x >= maxX) { michael@0: mLocked.pointerX = maxX; michael@0: } else { michael@0: mLocked.pointerX = x; michael@0: } michael@0: if (y <= minY) { michael@0: mLocked.pointerY = minY; michael@0: } else if (y >= maxY) { michael@0: mLocked.pointerY = maxY; michael@0: } else { michael@0: mLocked.pointerY = y; michael@0: } michael@0: updatePointerLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::getPosition(float* outX, float* outY) const { michael@0: AutoMutex _l(mLock); michael@0: michael@0: *outX = mLocked.pointerX; michael@0: *outY = mLocked.pointerY; michael@0: } michael@0: michael@0: void PointerController::fade(Transition transition) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: // Remove the inactivity timeout, since we are fading now. michael@0: removeInactivityTimeoutLocked(); michael@0: michael@0: // Start fading. michael@0: if (transition == TRANSITION_IMMEDIATE) { michael@0: mLocked.pointerFadeDirection = 0; michael@0: mLocked.pointerAlpha = 0.0f; michael@0: updatePointerLocked(); michael@0: } else { michael@0: mLocked.pointerFadeDirection = -1; michael@0: startAnimationLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::unfade(Transition transition) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: // Always reset the inactivity timer. michael@0: resetInactivityTimeoutLocked(); michael@0: michael@0: // Start unfading. michael@0: if (transition == TRANSITION_IMMEDIATE) { michael@0: mLocked.pointerFadeDirection = 0; michael@0: mLocked.pointerAlpha = 1.0f; michael@0: updatePointerLocked(); michael@0: } else { michael@0: mLocked.pointerFadeDirection = 1; michael@0: startAnimationLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::setPresentation(Presentation presentation) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: if (mLocked.presentation != presentation) { michael@0: mLocked.presentation = presentation; michael@0: mLocked.presentationChanged = true; michael@0: michael@0: if (presentation != PRESENTATION_SPOT) { michael@0: fadeOutAndReleaseAllSpotsLocked(); michael@0: } michael@0: michael@0: updatePointerLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::setSpots(const PointerCoords* spotCoords, michael@0: const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { michael@0: #if DEBUG_POINTER_UPDATES michael@0: ALOGD("setSpots: idBits=%08x", spotIdBits.value); michael@0: for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { michael@0: uint32_t id = idBits.firstMarkedBit(); michael@0: idBits.clearBit(id); michael@0: const PointerCoords& c = spotCoords[spotIdToIndex[id]]; michael@0: ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id, michael@0: c.getAxisValue(AMOTION_EVENT_AXIS_X), michael@0: c.getAxisValue(AMOTION_EVENT_AXIS_Y), michael@0: c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); michael@0: } michael@0: #endif michael@0: michael@0: AutoMutex _l(mLock); michael@0: michael@0: mSpriteController->openTransaction(); michael@0: michael@0: // Add or move spots for fingers that are down. michael@0: for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { michael@0: uint32_t id = idBits.clearFirstMarkedBit(); michael@0: const PointerCoords& c = spotCoords[spotIdToIndex[id]]; michael@0: const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0 michael@0: ? mResources.spotTouch : mResources.spotHover; michael@0: float x = c.getAxisValue(AMOTION_EVENT_AXIS_X); michael@0: float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y); michael@0: michael@0: Spot* spot = getSpotLocked(id); michael@0: if (!spot) { michael@0: spot = createAndAddSpotLocked(id); michael@0: } michael@0: michael@0: spot->updateSprite(&icon, x, y); michael@0: } michael@0: michael@0: // Remove spots for fingers that went up. michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: Spot* spot = mLocked.spots.itemAt(i); michael@0: if (spot->id != Spot::INVALID_ID michael@0: && !spotIdBits.hasBit(spot->id)) { michael@0: fadeOutAndReleaseSpotLocked(spot); michael@0: } michael@0: } michael@0: michael@0: mSpriteController->closeTransaction(); michael@0: } michael@0: michael@0: void PointerController::clearSpots() { michael@0: #if DEBUG_POINTER_UPDATES michael@0: ALOGD("clearSpots"); michael@0: #endif michael@0: michael@0: AutoMutex _l(mLock); michael@0: michael@0: fadeOutAndReleaseAllSpotsLocked(); michael@0: } michael@0: michael@0: void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: if (mLocked.inactivityTimeout != inactivityTimeout) { michael@0: mLocked.inactivityTimeout = inactivityTimeout; michael@0: resetInactivityTimeoutLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: // Adjust to use the display's unrotated coordinate frame. michael@0: if (orientation == DISPLAY_ORIENTATION_90 michael@0: || orientation == DISPLAY_ORIENTATION_270) { michael@0: int32_t temp = height; michael@0: height = width; michael@0: width = temp; michael@0: } michael@0: michael@0: if (mLocked.displayWidth != width || mLocked.displayHeight != height) { michael@0: mLocked.displayWidth = width; michael@0: mLocked.displayHeight = height; michael@0: michael@0: float minX, minY, maxX, maxY; michael@0: if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { michael@0: mLocked.pointerX = (minX + maxX) * 0.5f; michael@0: mLocked.pointerY = (minY + maxY) * 0.5f; michael@0: } else { michael@0: mLocked.pointerX = 0; michael@0: mLocked.pointerY = 0; michael@0: } michael@0: michael@0: fadeOutAndReleaseAllSpotsLocked(); michael@0: } michael@0: michael@0: if (mLocked.displayOrientation != orientation) { michael@0: // Apply offsets to convert from the pixel top-left corner position to the pixel center. michael@0: // This creates an invariant frame of reference that we can easily rotate when michael@0: // taking into account that the pointer may be located at fractional pixel offsets. michael@0: float x = mLocked.pointerX + 0.5f; michael@0: float y = mLocked.pointerY + 0.5f; michael@0: float temp; michael@0: michael@0: // Undo the previous rotation. michael@0: switch (mLocked.displayOrientation) { michael@0: case DISPLAY_ORIENTATION_90: michael@0: temp = x; michael@0: x = mLocked.displayWidth - y; michael@0: y = temp; michael@0: break; michael@0: case DISPLAY_ORIENTATION_180: michael@0: x = mLocked.displayWidth - x; michael@0: y = mLocked.displayHeight - y; michael@0: break; michael@0: case DISPLAY_ORIENTATION_270: michael@0: temp = x; michael@0: x = y; michael@0: y = mLocked.displayHeight - temp; michael@0: break; michael@0: } michael@0: michael@0: // Perform the new rotation. michael@0: switch (orientation) { michael@0: case DISPLAY_ORIENTATION_90: michael@0: temp = x; michael@0: x = y; michael@0: y = mLocked.displayWidth - temp; michael@0: break; michael@0: case DISPLAY_ORIENTATION_180: michael@0: x = mLocked.displayWidth - x; michael@0: y = mLocked.displayHeight - y; michael@0: break; michael@0: case DISPLAY_ORIENTATION_270: michael@0: temp = x; michael@0: x = mLocked.displayHeight - y; michael@0: y = temp; michael@0: break; michael@0: } michael@0: michael@0: // Apply offsets to convert from the pixel center to the pixel top-left corner position michael@0: // and save the results. michael@0: mLocked.pointerX = x - 0.5f; michael@0: mLocked.pointerY = y - 0.5f; michael@0: mLocked.displayOrientation = orientation; michael@0: } michael@0: michael@0: updatePointerLocked(); michael@0: } michael@0: michael@0: void PointerController::setPointerIcon(const SpriteIcon& icon) { michael@0: AutoMutex _l(mLock); michael@0: michael@0: mLocked.pointerIcon = icon.copy(); michael@0: mLocked.pointerIconChanged = true; michael@0: michael@0: updatePointerLocked(); michael@0: } michael@0: michael@0: void PointerController::handleMessage(const Message& message) { michael@0: switch (message.what) { michael@0: case MSG_ANIMATE: michael@0: doAnimate(); michael@0: break; michael@0: case MSG_INACTIVITY_TIMEOUT: michael@0: doInactivityTimeout(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void PointerController::doAnimate() { michael@0: AutoMutex _l(mLock); michael@0: michael@0: bool keepAnimating = false; michael@0: mLocked.animationPending = false; michael@0: nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime; michael@0: michael@0: // Animate pointer fade. michael@0: if (mLocked.pointerFadeDirection < 0) { michael@0: mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION; michael@0: if (mLocked.pointerAlpha <= 0.0f) { michael@0: mLocked.pointerAlpha = 0.0f; michael@0: mLocked.pointerFadeDirection = 0; michael@0: } else { michael@0: keepAnimating = true; michael@0: } michael@0: updatePointerLocked(); michael@0: } else if (mLocked.pointerFadeDirection > 0) { michael@0: mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION; michael@0: if (mLocked.pointerAlpha >= 1.0f) { michael@0: mLocked.pointerAlpha = 1.0f; michael@0: mLocked.pointerFadeDirection = 0; michael@0: } else { michael@0: keepAnimating = true; michael@0: } michael@0: updatePointerLocked(); michael@0: } michael@0: michael@0: // Animate spots that are fading out and being removed. michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: Spot* spot = mLocked.spots.itemAt(i); michael@0: if (spot->id == Spot::INVALID_ID) { michael@0: spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; michael@0: if (spot->alpha <= 0) { michael@0: mLocked.spots.removeAt(i--); michael@0: releaseSpotLocked(spot); michael@0: } else { michael@0: spot->sprite->setAlpha(spot->alpha); michael@0: keepAnimating = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (keepAnimating) { michael@0: startAnimationLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::doInactivityTimeout() { michael@0: fade(TRANSITION_GRADUAL); michael@0: } michael@0: michael@0: void PointerController::startAnimationLocked() { michael@0: if (!mLocked.animationPending) { michael@0: mLocked.animationPending = true; michael@0: mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC); michael@0: mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE)); michael@0: } michael@0: } michael@0: michael@0: void PointerController::resetInactivityTimeoutLocked() { michael@0: mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); michael@0: michael@0: nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT michael@0: ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL; michael@0: mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT); michael@0: } michael@0: michael@0: void PointerController::removeInactivityTimeoutLocked() { michael@0: mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); michael@0: } michael@0: michael@0: void PointerController::updatePointerLocked() { michael@0: mSpriteController->openTransaction(); michael@0: michael@0: mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); michael@0: mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); michael@0: michael@0: if (mLocked.pointerAlpha > 0) { michael@0: mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); michael@0: mLocked.pointerSprite->setVisible(true); michael@0: } else { michael@0: mLocked.pointerSprite->setVisible(false); michael@0: } michael@0: michael@0: if (mLocked.pointerIconChanged || mLocked.presentationChanged) { michael@0: mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER michael@0: ? mLocked.pointerIcon : mResources.spotAnchor); michael@0: mLocked.pointerIconChanged = false; michael@0: mLocked.presentationChanged = false; michael@0: } michael@0: michael@0: mSpriteController->closeTransaction(); michael@0: } michael@0: michael@0: PointerController::Spot* PointerController::getSpotLocked(uint32_t id) { michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: Spot* spot = mLocked.spots.itemAt(i); michael@0: if (spot->id == id) { michael@0: return spot; michael@0: } michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) { michael@0: // Remove spots until we have fewer than MAX_SPOTS remaining. michael@0: while (mLocked.spots.size() >= MAX_SPOTS) { michael@0: Spot* spot = removeFirstFadingSpotLocked(); michael@0: if (!spot) { michael@0: spot = mLocked.spots.itemAt(0); michael@0: mLocked.spots.removeAt(0); michael@0: } michael@0: releaseSpotLocked(spot); michael@0: } michael@0: michael@0: // Obtain a sprite from the recycled pool. michael@0: sp sprite; michael@0: if (! mLocked.recycledSprites.isEmpty()) { michael@0: sprite = mLocked.recycledSprites.top(); michael@0: mLocked.recycledSprites.pop(); michael@0: } else { michael@0: sprite = mSpriteController->createSprite(); michael@0: } michael@0: michael@0: // Return the new spot. michael@0: Spot* spot = new Spot(id, sprite); michael@0: mLocked.spots.push(spot); michael@0: return spot; michael@0: } michael@0: michael@0: PointerController::Spot* PointerController::removeFirstFadingSpotLocked() { michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: Spot* spot = mLocked.spots.itemAt(i); michael@0: if (spot->id == Spot::INVALID_ID) { michael@0: mLocked.spots.removeAt(i); michael@0: return spot; michael@0: } michael@0: } michael@0: return NULL; michael@0: } michael@0: michael@0: void PointerController::releaseSpotLocked(Spot* spot) { michael@0: spot->sprite->clearIcon(); michael@0: michael@0: if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { michael@0: mLocked.recycledSprites.push(spot->sprite); michael@0: } michael@0: michael@0: delete spot; michael@0: } michael@0: michael@0: void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) { michael@0: if (spot->id != Spot::INVALID_ID) { michael@0: spot->id = Spot::INVALID_ID; michael@0: startAnimationLocked(); michael@0: } michael@0: } michael@0: michael@0: void PointerController::fadeOutAndReleaseAllSpotsLocked() { michael@0: for (size_t i = 0; i < mLocked.spots.size(); i++) { michael@0: Spot* spot = mLocked.spots.itemAt(i); michael@0: fadeOutAndReleaseSpotLocked(spot); michael@0: } michael@0: } michael@0: michael@0: void PointerController::loadResources() { michael@0: mPolicy->loadPointerResources(&mResources); michael@0: } michael@0: michael@0: michael@0: // --- PointerController::Spot --- michael@0: michael@0: void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) { michael@0: sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); michael@0: sprite->setAlpha(alpha); michael@0: sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); michael@0: sprite->setPosition(x, y); michael@0: michael@0: this->x = x; michael@0: this->y = y; michael@0: michael@0: if (icon != lastIcon) { michael@0: lastIcon = icon; michael@0: if (icon) { michael@0: sprite->setIcon(*icon); michael@0: sprite->setVisible(true); michael@0: } else { michael@0: sprite->setVisible(false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: } // namespace android