diff --git a/widget/gonk/Makefile.in b/widget/gonk/Makefile.in index 3acf3ea3b09..390bb275789 100644 --- a/widget/gonk/Makefile.in +++ b/widget/gonk/Makefile.in @@ -74,6 +74,8 @@ CPPSRCS = \ KeyLayoutMap.cpp \ PixelFormat.cpp \ VirtualKeyMap.cpp \ + PointerController.cpp \ + SpriteController.cpp \ PropertyMap.cpp \ Unicode.cpp \ Timers.cpp \ diff --git a/widget/gonk/libui/InputReader.cpp b/widget/gonk/libui/InputReader.cpp index 07cbf09f57b..589ac24e88f 100644 --- a/widget/gonk/libui/InputReader.cpp +++ b/widget/gonk/libui/InputReader.cpp @@ -422,12 +422,10 @@ InputDevice* InputReader::createDeviceLocked(int32_t deviceId, device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType)); } -#if 0 // Cursor-like devices. if (classes & INPUT_DEVICE_CLASS_CURSOR) { device->addMapper(new CursorInputMapper(device)); } -#endif // Touchscreens and touchpad devices. if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) { @@ -2004,7 +2002,6 @@ void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState, // --- CursorInputMapper --- -#ifdef HAVE_ANDROID_OS CursorInputMapper::CursorInputMapper(InputDevice* device) : InputMapper(device) { } @@ -2021,12 +2018,10 @@ void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) { if (mParameters.mode == Parameters::MODE_POINTER) { float minX, minY, maxX, maxY; -#if 0 if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f); info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f); } -#endif } else { info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale); info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale); @@ -2078,7 +2073,7 @@ void CursorInputMapper::configure(nsecs_t when, mYPrecision = 1.0f; mXScale = 1.0f; mYScale = 1.0f; - //mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); break; case Parameters::MODE_NAVIGATION: mSource = AINPUT_SOURCE_TRACKBALL; @@ -2225,7 +2220,6 @@ void CursorInputMapper::sync(nsecs_t when) { mPointerVelocityControl.move(when, &deltaX, &deltaY); -#if 0 if (mPointerController != NULL) { if (moved || scrolled || buttonsChanged) { mPointerController->setPresentation( @@ -2247,10 +2241,9 @@ void CursorInputMapper::sync(nsecs_t when) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); } else { -#endif pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY); -// } + } pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); @@ -2273,8 +2266,7 @@ void CursorInputMapper::sync(nsecs_t when) { int32_t motionEventAction; if (downChanged) { motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP; - //} else if (down || mPointerController == NULL) { - } else if (1) { + } else if (down || mPointerController == NULL) { motionEventAction = AMOTION_EVENT_ACTION_MOVE; } else { motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE; @@ -2286,7 +2278,6 @@ void CursorInputMapper::sync(nsecs_t when) { getListener()->notifyMotion(&args); // Send hover move after UP to tell the application that the mouse is hovering now. -#if 0 if (motionEventAction == AMOTION_EVENT_ACTION_UP && mPointerController != NULL) { NotifyMotionArgs hoverArgs(when, getDeviceId(), mSource, policyFlags, @@ -2295,7 +2286,6 @@ void CursorInputMapper::sync(nsecs_t when) { 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, downTime); getListener()->notifyMotion(&hoverArgs); } -#endif // Send scroll events. if (scrolled) { @@ -2332,7 +2322,6 @@ void CursorInputMapper::fadePointer() { } } -#endif // HAVE_ANDROID_OS // --- TouchInputMapper --- @@ -2701,13 +2690,11 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) { // Create pointer controller if needed. if (mDeviceMode == DEVICE_MODE_POINTER || (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) { -#if 0 if (mPointerController == NULL) { mPointerController = getPolicy()->obtainPointerController(getDeviceId()); } -#endif } else { - //mPointerController.clear(); + mPointerController.clear(); } bool orientationChanged = mSurfaceOrientation != orientation; @@ -3295,12 +3282,10 @@ void TouchInputMapper::reset(nsecs_t when) { mPointerGesture.reset(); mPointerSimple.reset(); -#if 0 if (mPointerController != NULL) { mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); mPointerController->clearSpots(); } -#endif InputMapper::reset(when); } @@ -3437,7 +3422,6 @@ void TouchInputMapper::sync(nsecs_t when) { dispatchPointerUsage(when, policyFlags, pointerUsage); } else { -#if 0 if (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches && mPointerController != NULL) { mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT); @@ -3448,7 +3432,6 @@ void TouchInputMapper::sync(nsecs_t when) { mCurrentCookedPointerData.idToIndex, mCurrentCookedPointerData.touchingIdBits); } -#endif dispatchHoverExit(when, policyFlags); dispatchTouches(when, policyFlags); @@ -3982,7 +3965,6 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag } // Update the pointer presentation and spots. -#if 0 if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT); if (finishPreviousGesture || cancelPreviousGesture) { @@ -4026,7 +4008,6 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag } break; } -#endif // Send events! int32_t metaState = getContext()->getGlobalMetaState(); @@ -4139,7 +4120,6 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag // the pointer is hovering again even if the user is not currently touching // the touch pad. This ensures that a view will receive a fresh hover enter // event after a tap. -#if 0 float x, y; mPointerController->getPosition(&x, &y); @@ -4158,7 +4138,6 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, mPointerGesture.downTime); getListener()->notifyMotion(&args); -#endif } // Update state. @@ -4198,12 +4177,10 @@ void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) mPointerVelocityControl.reset(); // Remove any current spots. -#if 0 if (mPointerController != NULL) { mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); mPointerController->clearSpots(); } -#endif } bool TouchInputMapper::preparePointerGestures(nsecs_t when, @@ -4393,13 +4370,13 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, // Move the pointer using a relative motion. // When using spots, the click will occur at the position of the anchor // spot and all other spots will move there. - //mPointerController->move(deltaX, deltaY); + mPointerController->move(deltaX, deltaY); } else { mPointerVelocityControl.reset(); } float x, y; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG; mPointerGesture.currentGestureIdBits.clear(); @@ -4426,7 +4403,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, && lastFingerCount == 1) { if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) { float x, y; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { #if DEBUG_GESTURES @@ -4494,7 +4471,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { float x, y; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; @@ -4530,7 +4507,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, // Move the pointer using a relative motion. // When using spots, the hover or drag will occur at the position of the anchor spot. - //mPointerController->move(deltaX, deltaY); + mPointerController->move(deltaX, deltaY); } else { mPointerVelocityControl.reset(); } @@ -4553,7 +4530,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, } float x, y; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); @@ -4627,8 +4604,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, mCurrentRawPointerData.getCentroidOfTouchingPointers( &mPointerGesture.referenceTouchX, &mPointerGesture.referenceTouchY); - //mPointerController->getPosition(&mPointerGesture.referenceGestureX, - // &mPointerGesture.referenceGestureY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); } // Clear the reference deltas for fingers not yet included in the reference calculation. @@ -4920,7 +4897,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, } } - //mPointerController->setButtonState(mCurrentButtonState); + mPointerController->setButtonState(mCurrentButtonState); #if DEBUG_GESTURES ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " @@ -4967,12 +4944,12 @@ void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) uint32_t index = mCurrentCookedPointerData.idToIndex[id]; float x = mCurrentCookedPointerData.pointerCoords[index].getX(); float y = mCurrentCookedPointerData.pointerCoords[index].getY(); - //mPointerController->setPosition(x, y); + mPointerController->setPosition(x, y); hovering = mCurrentCookedPointerData.hoveringIdBits.hasBit(id); down = !hovering; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); mPointerSimple.currentCoords.copyFrom(mCurrentCookedPointerData.pointerCoords[index]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); @@ -5011,7 +4988,7 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) rotateDelta(mSurfaceOrientation, &deltaX, &deltaY); mPointerVelocityControl.move(when, &deltaX, &deltaY); - //mPointerController->move(deltaX, deltaY); + mPointerController->move(deltaX, deltaY); } else { mPointerVelocityControl.reset(); } @@ -5020,7 +4997,7 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) hovering = !down; float x, y; - //mPointerController->getPosition(&x, &y); + mPointerController->getPosition(&x, &y); mPointerSimple.currentCoords.copyFrom( mCurrentCookedPointerData.pointerCoords[currentIndex]); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -5050,7 +5027,6 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, bool down, bool hovering) { int32_t metaState = getContext()->getGlobalMetaState(); -#if 0 if (mPointerController != NULL) { if (down || hovering) { mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER); @@ -5061,7 +5037,6 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags, mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); } } -#endif if (mPointerSimple.down && !down) { mPointerSimple.down = false; @@ -5240,11 +5215,9 @@ bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties } void TouchInputMapper::fadePointer() { -#if 0 if (mPointerController != NULL) { mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); } -#endif } bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) { diff --git a/widget/gonk/libui/InputReader.h b/widget/gonk/libui/InputReader.h index 16537e61122..7bd9958c6bc 100644 --- a/widget/gonk/libui/InputReader.h +++ b/widget/gonk/libui/InputReader.h @@ -18,9 +18,7 @@ #define _UI_INPUT_READER_H #include "EventHub.h" -#ifdef HAVE_ANDROID_OS #include "PointerController.h" -#endif #include "InputListener.h" #include "Input.h" @@ -209,10 +207,8 @@ public: /* Gets the input reader configuration. */ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0; -#ifdef HAVE_ANDROID_OS /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */ virtual sp obtainPointerController(int32_t deviceId) = 0; -#endif }; @@ -938,7 +934,6 @@ private: }; -#ifdef HAVE_ANDROID_OS class CursorInputMapper : public InputMapper { public: CursorInputMapper(InputDevice* device); @@ -1002,7 +997,6 @@ private: void sync(nsecs_t when); }; -#endif // HAVE_ANDROID_OS class TouchInputMapper : public InputMapper { @@ -1176,10 +1170,8 @@ protected: // The time the primary pointer last went down. nsecs_t mDownTime; -#ifdef HAVE_ANDROID_OS // The pointer controller, or null if the device is not a pointer. sp mPointerController; -#endif Vector mVirtualKeys; diff --git a/widget/gonk/libui/PointerController.cpp b/widget/gonk/libui/PointerController.cpp new file mode 100644 index 00000000000..fc828a6afee --- /dev/null +++ b/widget/gonk/libui/PointerController.cpp @@ -0,0 +1,601 @@ +/* + * 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 + +// Log debug messages about pointer updates +#define DEBUG_POINTER_UPDATES 0 + +#include "PointerController.h" + +#include + +#include +#include +#include +#include +#include + +namespace android { + +// --- PointerController --- + +// Time to wait before starting the fade when the pointer is inactive. +static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds +static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds + +// Time to wait between animation frames. +static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60; + +// Time to spend fading out the spot completely. +static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms + +// Time to spend fading out the pointer completely. +static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms + + +// --- PointerController --- + +PointerController::PointerController(const sp& policy, + const sp& looper, const sp& spriteController) : + mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { + mHandler = new WeakMessageHandler(this); + + AutoMutex _l(mLock); + + mLocked.animationPending = false; + + mLocked.displayWidth = -1; + mLocked.displayHeight = -1; + mLocked.displayOrientation = DISPLAY_ORIENTATION_0; + + mLocked.presentation = PRESENTATION_POINTER; + mLocked.presentationChanged = false; + + mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL; + + mLocked.pointerFadeDirection = 0; + mLocked.pointerX = 0; + mLocked.pointerY = 0; + mLocked.pointerAlpha = 0.0f; // pointer is initially faded + mLocked.pointerSprite = mSpriteController->createSprite(); + mLocked.pointerIconChanged = false; + + mLocked.buttonState = 0; + + loadResources(); +} + +PointerController::~PointerController() { + mLooper->removeMessages(mHandler); + + AutoMutex _l(mLock); + + mLocked.pointerSprite.clear(); + + for (size_t i = 0; i < mLocked.spots.size(); i++) { + delete mLocked.spots.itemAt(i); + } + mLocked.spots.clear(); + mLocked.recycledSprites.clear(); +} + +bool PointerController::getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const { + AutoMutex _l(mLock); + + return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY); +} + +bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const { + if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { + return false; + } + + *outMinX = 0; + *outMinY = 0; + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + case DISPLAY_ORIENTATION_270: + *outMaxX = mLocked.displayHeight - 1; + *outMaxY = mLocked.displayWidth - 1; + break; + default: + *outMaxX = mLocked.displayWidth - 1; + *outMaxY = mLocked.displayHeight - 1; + break; + } + return true; +} + +void PointerController::move(float deltaX, float deltaY) { +#if DEBUG_POINTER_UPDATES + ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); +#endif + if (deltaX == 0.0f && deltaY == 0.0f) { + return; + } + + AutoMutex _l(mLock); + + setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); +} + +void PointerController::setButtonState(int32_t buttonState) { +#if DEBUG_POINTER_UPDATES + ALOGD("Set button state 0x%08x", buttonState); +#endif + AutoMutex _l(mLock); + + if (mLocked.buttonState != buttonState) { + mLocked.buttonState = buttonState; + } +} + +int32_t PointerController::getButtonState() const { + AutoMutex _l(mLock); + + return mLocked.buttonState; +} + +void PointerController::setPosition(float x, float y) { +#if DEBUG_POINTER_UPDATES + ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); +#endif + AutoMutex _l(mLock); + + setPositionLocked(x, y); +} + +void PointerController::setPositionLocked(float x, float y) { + float minX, minY, maxX, maxY; + if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { + if (x <= minX) { + mLocked.pointerX = minX; + } else if (x >= maxX) { + mLocked.pointerX = maxX; + } else { + mLocked.pointerX = x; + } + if (y <= minY) { + mLocked.pointerY = minY; + } else if (y >= maxY) { + mLocked.pointerY = maxY; + } else { + mLocked.pointerY = y; + } + updatePointerLocked(); + } +} + +void PointerController::getPosition(float* outX, float* outY) const { + AutoMutex _l(mLock); + + *outX = mLocked.pointerX; + *outY = mLocked.pointerY; +} + +void PointerController::fade(Transition transition) { + AutoMutex _l(mLock); + + // Remove the inactivity timeout, since we are fading now. + removeInactivityTimeoutLocked(); + + // Start fading. + if (transition == TRANSITION_IMMEDIATE) { + mLocked.pointerFadeDirection = 0; + mLocked.pointerAlpha = 0.0f; + updatePointerLocked(); + } else { + mLocked.pointerFadeDirection = -1; + startAnimationLocked(); + } +} + +void PointerController::unfade(Transition transition) { + AutoMutex _l(mLock); + + // Always reset the inactivity timer. + resetInactivityTimeoutLocked(); + + // Start unfading. + if (transition == TRANSITION_IMMEDIATE) { + mLocked.pointerFadeDirection = 0; + mLocked.pointerAlpha = 1.0f; + updatePointerLocked(); + } else { + mLocked.pointerFadeDirection = 1; + startAnimationLocked(); + } +} + +void PointerController::setPresentation(Presentation presentation) { + AutoMutex _l(mLock); + + if (mLocked.presentation != presentation) { + mLocked.presentation = presentation; + mLocked.presentationChanged = true; + + if (presentation != PRESENTATION_SPOT) { + fadeOutAndReleaseAllSpotsLocked(); + } + + updatePointerLocked(); + } +} + +void PointerController::setSpots(const PointerCoords* spotCoords, + const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { +#if DEBUG_POINTER_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", id, + c.getAxisValue(AMOTION_EVENT_AXIS_X), + c.getAxisValue(AMOTION_EVENT_AXIS_Y), + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } +#endif + + AutoMutex _l(mLock); + + mSpriteController->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 = getSpotLocked(id); + if (!spot) { + spot = createAndAddSpotLocked(id); + } + + spot->updateSprite(&icon, x, y); + } + + // Remove spots for fingers that went up. + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id != Spot::INVALID_ID + && !spotIdBits.hasBit(spot->id)) { + fadeOutAndReleaseSpotLocked(spot); + } + } + + mSpriteController->closeTransaction(); +} + +void PointerController::clearSpots() { +#if DEBUG_POINTER_UPDATES + ALOGD("clearSpots"); +#endif + + AutoMutex _l(mLock); + + fadeOutAndReleaseAllSpotsLocked(); +} + +void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) { + AutoMutex _l(mLock); + + if (mLocked.inactivityTimeout != inactivityTimeout) { + mLocked.inactivityTimeout = inactivityTimeout; + resetInactivityTimeoutLocked(); + } +} + +void PointerController::setDisplaySize(int32_t width, int32_t height) { + AutoMutex _l(mLock); + + if (mLocked.displayWidth != width || mLocked.displayHeight != height) { + mLocked.displayWidth = width; + mLocked.displayHeight = height; + + float minX, minY, maxX, maxY; + if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { + mLocked.pointerX = (minX + maxX) * 0.5f; + mLocked.pointerY = (minY + maxY) * 0.5f; + } else { + mLocked.pointerX = 0; + mLocked.pointerY = 0; + } + + fadeOutAndReleaseAllSpotsLocked(); + updatePointerLocked(); + } +} + +void PointerController::setDisplayOrientation(int32_t orientation) { + AutoMutex _l(mLock); + + if (mLocked.displayOrientation != orientation) { + // Apply offsets to convert from the pixel top-left corner position to the pixel center. + // This creates an invariant frame of reference that we can easily rotate when + // taking into account that the pointer may be located at fractional pixel offsets. + float x = mLocked.pointerX + 0.5f; + float y = mLocked.pointerY + 0.5f; + float temp; + + // Undo the previous rotation. + switch (mLocked.displayOrientation) { + case DISPLAY_ORIENTATION_90: + temp = x; + x = mLocked.displayWidth - y; + y = temp; + break; + case DISPLAY_ORIENTATION_180: + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; + break; + case DISPLAY_ORIENTATION_270: + temp = x; + x = y; + y = mLocked.displayHeight - temp; + break; + } + + // Perform the new rotation. + switch (orientation) { + case DISPLAY_ORIENTATION_90: + temp = x; + x = y; + y = mLocked.displayWidth - temp; + break; + case DISPLAY_ORIENTATION_180: + x = mLocked.displayWidth - x; + y = mLocked.displayHeight - y; + break; + case DISPLAY_ORIENTATION_270: + temp = x; + x = mLocked.displayHeight - y; + y = temp; + break; + } + + // Apply offsets to convert from the pixel center to the pixel top-left corner position + // and save the results. + mLocked.pointerX = x - 0.5f; + mLocked.pointerY = y - 0.5f; + mLocked.displayOrientation = orientation; + + updatePointerLocked(); + } +} + +void PointerController::setPointerIcon(const SpriteIcon& icon) { + AutoMutex _l(mLock); + + mLocked.pointerIcon = icon.copy(); + mLocked.pointerIconChanged = true; + + updatePointerLocked(); +} + +void PointerController::handleMessage(const Message& message) { + switch (message.what) { + case MSG_ANIMATE: + doAnimate(); + break; + case MSG_INACTIVITY_TIMEOUT: + doInactivityTimeout(); + break; + } +} + +void PointerController::doAnimate() { + AutoMutex _l(mLock); + + bool keepAnimating = false; + mLocked.animationPending = false; + nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime; + + // Animate pointer fade. + if (mLocked.pointerFadeDirection < 0) { + mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION; + if (mLocked.pointerAlpha <= 0.0f) { + mLocked.pointerAlpha = 0.0f; + mLocked.pointerFadeDirection = 0; + } else { + keepAnimating = true; + } + updatePointerLocked(); + } else if (mLocked.pointerFadeDirection > 0) { + mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION; + if (mLocked.pointerAlpha >= 1.0f) { + mLocked.pointerAlpha = 1.0f; + mLocked.pointerFadeDirection = 0; + } else { + keepAnimating = true; + } + updatePointerLocked(); + } + + // Animate spots that are fading out and being removed. + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == Spot::INVALID_ID) { + spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; + if (spot->alpha <= 0) { + mLocked.spots.removeAt(i--); + releaseSpotLocked(spot); + } else { + spot->sprite->setAlpha(spot->alpha); + keepAnimating = true; + } + } + } + + if (keepAnimating) { + startAnimationLocked(); + } +} + +void PointerController::doInactivityTimeout() { + fade(TRANSITION_GRADUAL); +} + +void PointerController::startAnimationLocked() { + if (!mLocked.animationPending) { + mLocked.animationPending = true; + mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC); + mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE)); + } +} + +void PointerController::resetInactivityTimeoutLocked() { + mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); + + nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT + ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL; + mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT); +} + +void PointerController::removeInactivityTimeoutLocked() { + mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); +} + +void PointerController::updatePointerLocked() { + mSpriteController->openTransaction(); + + mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); + mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + + if (mLocked.pointerAlpha > 0) { + mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); + mLocked.pointerSprite->setVisible(true); + } else { + mLocked.pointerSprite->setVisible(false); + } + + if (mLocked.pointerIconChanged || mLocked.presentationChanged) { + mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER + ? mLocked.pointerIcon : mResources.spotAnchor); + mLocked.pointerIconChanged = false; + mLocked.presentationChanged = false; + } + + mSpriteController->closeTransaction(); +} + +PointerController::Spot* PointerController::getSpotLocked(uint32_t id) { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == id) { + return spot; + } + } + return NULL; +} + +PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) { + // Remove spots until we have fewer than MAX_SPOTS remaining. + while (mLocked.spots.size() >= MAX_SPOTS) { + Spot* spot = removeFirstFadingSpotLocked(); + if (!spot) { + spot = mLocked.spots.itemAt(0); + mLocked.spots.removeAt(0); + } + releaseSpotLocked(spot); + } + + // Obtain a sprite from the recycled pool. + sp sprite; + if (! mLocked.recycledSprites.isEmpty()) { + sprite = mLocked.recycledSprites.top(); + mLocked.recycledSprites.pop(); + } else { + sprite = mSpriteController->createSprite(); + } + + // Return the new spot. + Spot* spot = new Spot(id, sprite); + mLocked.spots.push(spot); + return spot; +} + +PointerController::Spot* PointerController::removeFirstFadingSpotLocked() { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == Spot::INVALID_ID) { + mLocked.spots.removeAt(i); + return spot; + } + } + return NULL; +} + +void PointerController::releaseSpotLocked(Spot* spot) { + spot->sprite->clearIcon(); + + if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { + mLocked.recycledSprites.push(spot->sprite); + } + + delete spot; +} + +void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) { + if (spot->id != Spot::INVALID_ID) { + spot->id = Spot::INVALID_ID; + startAnimationLocked(); + } +} + +void PointerController::fadeOutAndReleaseAllSpotsLocked() { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + fadeOutAndReleaseSpotLocked(spot); + } +} + +void PointerController::loadResources() { + mPolicy->loadPointerResources(&mResources); +} + + +// --- PointerController::Spot --- + +void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) { + sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); + sprite->setAlpha(alpha); + sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); + sprite->setPosition(x, y); + + this->x = x; + this->y = y; + + if (icon != lastIcon) { + lastIcon = icon; + if (icon) { + sprite->setIcon(*icon); + sprite->setVisible(true); + } else { + sprite->setVisible(false); + } + } +} + +} // namespace android diff --git a/widget/gonk/libui/PointerController.h b/widget/gonk/libui/PointerController.h new file mode 100644 index 00000000000..98c44c2cf29 --- /dev/null +++ b/widget/gonk/libui/PointerController.h @@ -0,0 +1,266 @@ +/* + * 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. + */ + +#ifndef _UI_POINTER_CONTROLLER_H +#define _UI_POINTER_CONTROLLER_H + +#include "SpriteController.h" + +#include "DisplayInfo.h" +#include "Input.h" +#include +#include +#include "String8.h" + +#include + +namespace android { + +/** + * Interface for tracking a mouse / touch pad pointer and touch pad spots. + * + * The spots are sprites on screen that visually represent the positions of + * fingers + * + * The pointer controller is responsible for providing synchronization and for tracking + * display orientation changes if needed. + */ +class PointerControllerInterface : public virtual RefBase { +protected: + PointerControllerInterface() { } + virtual ~PointerControllerInterface() { } + +public: + /* Gets the bounds of the region that the pointer can traverse. + * Returns true if the bounds are available. */ + virtual bool getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const = 0; + + /* Move the pointer. */ + virtual void move(float deltaX, float deltaY) = 0; + + /* Sets a mask that indicates which buttons are pressed. */ + virtual void setButtonState(int32_t buttonState) = 0; + + /* Gets a mask that indicates which buttons are pressed. */ + virtual int32_t getButtonState() const = 0; + + /* Sets the absolute location of the pointer. */ + virtual void setPosition(float x, float y) = 0; + + /* Gets the absolute location of the pointer. */ + virtual void getPosition(float* outX, float* outY) const = 0; + + enum Transition { + // Fade/unfade immediately. + TRANSITION_IMMEDIATE, + // Fade/unfade gradually. + TRANSITION_GRADUAL, + }; + + /* Fades the pointer out now. */ + virtual void fade(Transition transition) = 0; + + /* Makes the pointer visible if it has faded out. + * The pointer never unfades itself automatically. This method must be called + * by the client whenever the pointer is moved or a button is pressed and it + * wants to ensure that the pointer becomes visible again. */ + virtual void unfade(Transition transition) = 0; + + enum Presentation { + // Show the mouse pointer. + PRESENTATION_POINTER, + // Show spots and a spot anchor in place of the mouse pointer. + PRESENTATION_SPOT, + }; + + /* Sets the mode of the pointer controller. */ + virtual void setPresentation(Presentation presentation) = 0; + + /* Sets the spots for the current gesture. + * The spots are not subject to the inactivity timeout like the pointer + * itself it since they are expected to remain visible for so long as + * the fingers are on the touch pad. + * + * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant. + * For spotCoords, pressure != 0 indicates that the spot's location is being + * pressed (not hovering). + */ + virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits) = 0; + + /* Removes all spots. */ + virtual void clearSpots() = 0; +}; + + +/* + * Pointer resources. + */ +struct PointerResources { + SpriteIcon spotHover; + SpriteIcon spotTouch; + SpriteIcon spotAnchor; +}; + + +/* + * Pointer controller policy interface. + * + * The pointer controller policy is used by the pointer controller to interact with + * the Window Manager and other system components. + * + * The actual implementation is partially supported by callbacks into the DVM + * via JNI. This interface is also mocked in the unit tests. + */ +class PointerControllerPolicyInterface : public virtual RefBase { +protected: + PointerControllerPolicyInterface() { } + virtual ~PointerControllerPolicyInterface() { } + +public: + virtual void loadPointerResources(PointerResources* outResources) = 0; +}; + + +/* + * Tracks pointer movements and draws the pointer sprite to a surface. + * + * Handles pointer acceleration and animation. + */ +class PointerController : public PointerControllerInterface, public MessageHandler { +protected: + virtual ~PointerController(); + +public: + enum InactivityTimeout { + INACTIVITY_TIMEOUT_NORMAL = 0, + INACTIVITY_TIMEOUT_SHORT = 1, + }; + + PointerController(const sp& policy, + const sp& looper, const sp& spriteController); + + virtual bool getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const; + virtual void move(float deltaX, float deltaY); + virtual void setButtonState(int32_t buttonState); + virtual int32_t getButtonState() const; + virtual void setPosition(float x, float y); + virtual void getPosition(float* outX, float* outY) const; + virtual void fade(Transition transition); + virtual void unfade(Transition transition); + + virtual void setPresentation(Presentation presentation); + virtual void setSpots(const PointerCoords* spotCoords, + const uint32_t* spotIdToIndex, BitSet32 spotIdBits); + virtual void clearSpots(); + + void setDisplaySize(int32_t width, int32_t height); + void setDisplayOrientation(int32_t orientation); + void setPointerIcon(const SpriteIcon& icon); + void setInactivityTimeout(InactivityTimeout inactivityTimeout); + +private: + static const size_t MAX_RECYCLED_SPRITES = 12; + static const size_t MAX_SPOTS = 12; + + enum { + MSG_ANIMATE, + MSG_INACTIVITY_TIMEOUT, + }; + + struct Spot { + static const uint32_t INVALID_ID = 0xffffffff; + + uint32_t id; + sp sprite; + float alpha; + float scale; + float x, y; + + inline Spot(uint32_t id, const sp& sprite) + : id(id), sprite(sprite), alpha(1.0f), scale(1.0f), + x(0.0f), y(0.0f), lastIcon(NULL) { } + + void updateSprite(const SpriteIcon* icon, float x, float y); + + private: + const SpriteIcon* lastIcon; + }; + + mutable Mutex mLock; + + sp mPolicy; + sp mLooper; + sp mSpriteController; + sp mHandler; + + PointerResources mResources; + + struct Locked { + bool animationPending; + nsecs_t animationTime; + + int32_t displayWidth; + int32_t displayHeight; + int32_t displayOrientation; + + InactivityTimeout inactivityTimeout; + + Presentation presentation; + bool presentationChanged; + + int32_t pointerFadeDirection; + float pointerX; + float pointerY; + float pointerAlpha; + sp pointerSprite; + SpriteIcon pointerIcon; + bool pointerIconChanged; + + int32_t buttonState; + + Vector spots; + Vector > recycledSprites; + } mLocked; + + bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; + void setPositionLocked(float x, float y); + + void handleMessage(const Message& message); + void doAnimate(); + void doInactivityTimeout(); + + void startAnimationLocked(); + + void resetInactivityTimeoutLocked(); + void removeInactivityTimeoutLocked(); + void updatePointerLocked(); + + Spot* getSpotLocked(uint32_t id); + Spot* createAndAddSpotLocked(uint32_t id); + Spot* removeFirstFadingSpotLocked(); + void releaseSpotLocked(Spot* spot); + void fadeOutAndReleaseSpotLocked(Spot* spot); + void fadeOutAndReleaseAllSpotsLocked(); + + void loadResources(); +}; + +} // namespace android + +#endif // _UI_POINTER_CONTROLLER_H diff --git a/widget/gonk/libui/SpriteController.cpp b/widget/gonk/libui/SpriteController.cpp new file mode 100644 index 00000000000..86045018220 --- /dev/null +++ b/widget/gonk/libui/SpriteController.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2011 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 "Sprites" + +//#define LOG_NDEBUG 0 + +#include "SpriteController.h" + +#include "cutils_log.h" +#include "String8.h" + +#include +#include +#include +#include +#include + +namespace android { + +// --- SpriteController --- + +SpriteController::SpriteController(const sp& looper, int32_t overlayLayer) : + mLooper(looper), mOverlayLayer(overlayLayer) { + mHandler = new WeakMessageHandler(this); + + mLocked.transactionNestingCount = 0; + mLocked.deferredSpriteUpdate = false; +} + +SpriteController::~SpriteController() { + mLooper->removeMessages(mHandler); + +#ifdef HAVE_ANDROID_OS + if (mSurfaceComposerClient != NULL) { + mSurfaceComposerClient->dispose(); + mSurfaceComposerClient.clear(); + } +#endif +} + +sp SpriteController::createSprite() { + return new SpriteImpl(this); +} + +void SpriteController::openTransaction() { + AutoMutex _l(mLock); + + mLocked.transactionNestingCount += 1; +} + +void SpriteController::closeTransaction() { + AutoMutex _l(mLock); + + LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0, + "Sprite closeTransaction() called but there is no open sprite transaction"); + + mLocked.transactionNestingCount -= 1; + if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) { + mLocked.deferredSpriteUpdate = false; + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } +} + +void SpriteController::invalidateSpriteLocked(const sp& sprite) { + bool wasEmpty = mLocked.invalidatedSprites.isEmpty(); + mLocked.invalidatedSprites.push(sprite); + if (wasEmpty) { + if (mLocked.transactionNestingCount != 0) { + mLocked.deferredSpriteUpdate = true; + } else { + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } + } +} + +#ifdef HAVE_ANDROID_OS +void SpriteController::disposeSurfaceLocked(const sp& surfaceControl) { + bool wasEmpty = mLocked.disposedSurfaces.isEmpty(); + mLocked.disposedSurfaces.push(surfaceControl); + if (wasEmpty) { + mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES)); + } +} +#endif + +void SpriteController::handleMessage(const Message& message) { + switch (message.what) { + case MSG_UPDATE_SPRITES: + doUpdateSprites(); + break; + case MSG_DISPOSE_SURFACES: + doDisposeSurfaces(); + break; + } +} + +void SpriteController::doUpdateSprites() { + // Collect information about sprite updates. + // Each sprite update record includes a reference to its associated sprite so we can + // be certain the sprites will not be deleted while this function runs. Sprites + // may invalidate themselves again during this time but we will handle those changes + // in the next iteration. + Vector updates; + size_t numSprites; + { // acquire lock + AutoMutex _l(mLock); + + numSprites = mLocked.invalidatedSprites.size(); + for (size_t i = 0; i < numSprites; i++) { + const sp& sprite = mLocked.invalidatedSprites.itemAt(i); + + updates.push(SpriteUpdate(sprite, sprite->getStateLocked())); + sprite->resetDirtyLocked(); + } + mLocked.invalidatedSprites.clear(); + } // release lock + + // Create missing surfaces. + bool surfaceChanged = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + +#ifdef HAVE_ANDROID_OS + if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) { + update.state.surfaceWidth = update.state.icon.bitmap.width(); + update.state.surfaceHeight = update.state.icon.bitmap.height(); + update.state.surfaceDrawn = false; + update.state.surfaceVisible = false; + update.state.surfaceControl = obtainSurface( + update.state.surfaceWidth, update.state.surfaceHeight); + if (update.state.surfaceControl != NULL) { + update.surfaceChanged = surfaceChanged = true; + } + } +#endif + } + + // Resize sprites if needed, inside a global transaction. + bool haveGlobalTransaction = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + +#ifdef HAVE_ANDROID_OS + if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { + int32_t desiredWidth = update.state.icon.bitmap.width(); + int32_t desiredHeight = update.state.icon.bitmap.height(); + if (update.state.surfaceWidth < desiredWidth + || update.state.surfaceHeight < desiredHeight) { + if (!haveGlobalTransaction) { + SurfaceComposerClient::openGlobalTransaction(); + haveGlobalTransaction = true; + } + + status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight); + if (status) { + ALOGE("Error %d resizing sprite surface from %dx%d to %dx%d", + status, update.state.surfaceWidth, update.state.surfaceHeight, + desiredWidth, desiredHeight); + } else { + update.state.surfaceWidth = desiredWidth; + update.state.surfaceHeight = desiredHeight; + update.state.surfaceDrawn = false; + update.surfaceChanged = surfaceChanged = true; + + if (update.state.surfaceVisible) { + status = update.state.surfaceControl->hide(); + if (status) { + ALOGE("Error %d hiding sprite surface after resize.", status); + } else { + update.state.surfaceVisible = false; + } + } + } + } + } +#endif + } +#ifdef HAVE_ANDROID_OS + if (haveGlobalTransaction) { + SurfaceComposerClient::closeGlobalTransaction(); + } +#endif + + // Redraw sprites if needed. + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) { + update.state.surfaceDrawn = false; + update.surfaceChanged = surfaceChanged = true; + } + +#ifdef HAVE_ANDROID_OS + if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn + && update.state.wantSurfaceVisible()) { + sp surface = update.state.surfaceControl->getSurface(); + Surface::SurfaceInfo surfaceInfo; + status_t status = surface->lock(&surfaceInfo); + if (status) { + ALOGE("Error %d locking sprite surface before drawing.", status); + } else { + SkBitmap surfaceBitmap; + ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format); + surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, + surfaceInfo.w, surfaceInfo.h, bpr); + surfaceBitmap.setPixels(surfaceInfo.bits); + + SkCanvas surfaceCanvas; + surfaceCanvas.setBitmapDevice(surfaceBitmap); + + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint); + + if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0, + surfaceInfo.w, update.state.icon.bitmap.height(), paint); + } + if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(), + surfaceInfo.w, surfaceInfo.h, paint); + } + + status = surface->unlockAndPost(); + if (status) { + ALOGE("Error %d unlocking and posting sprite surface after drawing.", status); + } else { + update.state.surfaceDrawn = true; + update.surfaceChanged = surfaceChanged = true; + } + } + } +#endif + } + + // Set sprite surface properties and make them visible. + bool haveTransaction = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible() + && update.state.surfaceDrawn; + bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible; + bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible; +#ifdef HAVE_ANDROID_OS + if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden + || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA + | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER + | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { + status_t status; + if (!haveTransaction) { + SurfaceComposerClient::openGlobalTransaction(); + haveTransaction = true; + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) { + status = update.state.surfaceControl->setAlpha(update.state.alpha); + if (status) { + ALOGE("Error %d setting sprite surface alpha.", status); + } + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & (DIRTY_POSITION + | DIRTY_HOTSPOT)))) { + status = update.state.surfaceControl->setPosition( + update.state.positionX - update.state.icon.hotSpotX, + update.state.positionY - update.state.icon.hotSpotY); + if (status) { + ALOGE("Error %d setting sprite surface position.", status); + } + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible + || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) { + status = update.state.surfaceControl->setMatrix( + update.state.transformationMatrix.dsdx, + update.state.transformationMatrix.dtdx, + update.state.transformationMatrix.dsdy, + update.state.transformationMatrix.dtdy); + if (status) { + ALOGE("Error %d setting sprite surface transformation matrix.", status); + } + } + + int32_t surfaceLayer = mOverlayLayer + update.state.layer; + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) { + status = update.state.surfaceControl->setLayer(surfaceLayer); + if (status) { + ALOGE("Error %d setting sprite surface layer.", status); + } + } + + if (becomingVisible) { + status = update.state.surfaceControl->show(surfaceLayer); + if (status) { + ALOGE("Error %d showing sprite surface.", status); + } else { + update.state.surfaceVisible = true; + update.surfaceChanged = surfaceChanged = true; + } + } else if (becomingHidden) { + status = update.state.surfaceControl->hide(); + if (status) { + ALOGE("Error %d hiding sprite surface.", status); + } else { + update.state.surfaceVisible = false; + update.surfaceChanged = surfaceChanged = true; + } + } + } +#endif + } + +#ifdef HAVE_ANDROID_OS + if (haveTransaction) { + SurfaceComposerClient::closeGlobalTransaction(); + } +#endif + + // If any surfaces were changed, write back the new surface properties to the sprites. + if (surfaceChanged) { // acquire lock + AutoMutex _l(mLock); + + for (size_t i = 0; i < numSprites; i++) { + const SpriteUpdate& update = updates.itemAt(i); + +#ifdef HAVE_ANDROID_OS + if (update.surfaceChanged) { + update.sprite->setSurfaceLocked(update.state.surfaceControl, + update.state.surfaceWidth, update.state.surfaceHeight, + update.state.surfaceDrawn, update.state.surfaceVisible); + } +#endif + } + } // release lock + + // Clear the sprite update vector outside the lock. It is very important that + // we do not clear sprite references inside the lock since we could be releasing + // the last remaining reference to the sprite here which would result in the + // sprite being deleted and the lock being reacquired by the sprite destructor + // while already held. + updates.clear(); +} + +void SpriteController::doDisposeSurfaces() { +#ifdef HAVE_ANDROID_OS + // Collect disposed surfaces. + Vector > disposedSurfaces; + { // acquire lock + AutoMutex _l(mLock); + + disposedSurfaces = mLocked.disposedSurfaces; + mLocked.disposedSurfaces.clear(); + } // release lock + + // Release the last reference to each surface outside of the lock. + // We don't want the surfaces to be deleted while we are holding our lock. + disposedSurfaces.clear(); +#endif +} + +void SpriteController::ensureSurfaceComposerClient() { +#ifdef HAVE_ANDROID_OS + if (mSurfaceComposerClient == NULL) { + mSurfaceComposerClient = new SurfaceComposerClient(); + } +#endif +} + +#ifdef HAVE_ANDROID_OS +sp SpriteController::obtainSurface(int32_t width, int32_t height) { + ensureSurfaceComposerClient(); + + sp surfaceControl = mSurfaceComposerClient->createSurface( + String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888); + if (surfaceControl == NULL || !surfaceControl->isValid() + || !surfaceControl->getSurface()->isValid()) { + ALOGE("Error creating sprite surface."); + return NULL; + } + return surfaceControl; +} +#endif + + +// --- SpriteController::SpriteImpl --- + +SpriteController::SpriteImpl::SpriteImpl(const sp controller) : + mController(controller) { +} + +SpriteController::SpriteImpl::~SpriteImpl() { + AutoMutex _m(mController->mLock); + +#ifdef HAVE_ANDROID_OS + // Let the controller take care of deleting the last reference to sprite + // surfaces so that we do not block the caller on an IPC here. + if (mLocked.state.surfaceControl != NULL) { + mController->disposeSurfaceLocked(mLocked.state.surfaceControl); + mLocked.state.surfaceControl.clear(); + } +#endif +} + +void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { + AutoMutex _l(mController->mLock); + +#ifdef HAVE_ANDROID_OS + uint32_t dirty; + if (icon.isValid()) { + icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config); + + if (!mLocked.state.icon.isValid() + || mLocked.state.icon.hotSpotX != icon.hotSpotX + || mLocked.state.icon.hotSpotY != icon.hotSpotY) { + mLocked.state.icon.hotSpotX = icon.hotSpotX; + mLocked.state.icon.hotSpotY = icon.hotSpotY; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + } else { + dirty = DIRTY_BITMAP; + } + } else if (mLocked.state.icon.isValid()) { + mLocked.state.icon.bitmap.reset(); + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + } else { + return; // setting to invalid icon and already invalid so nothing to do + } + + invalidateLocked(dirty); +#endif +} + +void SpriteController::SpriteImpl::setVisible(bool visible) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.visible != visible) { + mLocked.state.visible = visible; + invalidateLocked(DIRTY_VISIBILITY); + } +} + +void SpriteController::SpriteImpl::setPosition(float x, float y) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.positionX != x || mLocked.state.positionY != y) { + mLocked.state.positionX = x; + mLocked.state.positionY = y; + invalidateLocked(DIRTY_POSITION); + } +} + +void SpriteController::SpriteImpl::setLayer(int32_t layer) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.layer != layer) { + mLocked.state.layer = layer; + invalidateLocked(DIRTY_LAYER); + } +} + +void SpriteController::SpriteImpl::setAlpha(float alpha) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.alpha != alpha) { + mLocked.state.alpha = alpha; + invalidateLocked(DIRTY_ALPHA); + } +} + +void SpriteController::SpriteImpl::setTransformationMatrix( + const SpriteTransformationMatrix& matrix) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.transformationMatrix != matrix) { + mLocked.state.transformationMatrix = matrix; + invalidateLocked(DIRTY_TRANSFORMATION_MATRIX); + } +} + +void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { + bool wasDirty = mLocked.state.dirty; + mLocked.state.dirty |= dirty; + + if (!wasDirty) { + mController->invalidateSpriteLocked(this); + } +} + +} // namespace android diff --git a/widget/gonk/libui/SpriteController.h b/widget/gonk/libui/SpriteController.h new file mode 100644 index 00000000000..4502f4e3739 --- /dev/null +++ b/widget/gonk/libui/SpriteController.h @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _UI_SPRITES_H +#define _UI_SPRITES_H + +#include +#include + +#ifdef HAVE_ANDROID_OS +#include +#include +#include + +#include +#endif + +namespace android { + +/* + * Transformation matrix for a sprite. + */ +struct SpriteTransformationMatrix { + inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { } + inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) : + dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { } + + float dsdx; + float dtdx; + float dsdy; + float dtdy; + + inline bool operator== (const SpriteTransformationMatrix& other) { + return dsdx == other.dsdx + && dtdx == other.dtdx + && dsdy == other.dsdy + && dtdy == other.dtdy; + } + + inline bool operator!= (const SpriteTransformationMatrix& other) { + return !(*this == other); + } +}; + +/* + * Icon that a sprite displays, including its hotspot. + */ +struct SpriteIcon { + inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { } +#ifdef HAVE_ANDROID_OS + inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) : + bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } + + SkBitmap bitmap; +#endif + float hotSpotX; + float hotSpotY; + + inline SpriteIcon copy() const { +#ifdef HAVE_ANDROID_OS + SkBitmap bitmapCopy; + bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config); + return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY); +#else + return SpriteIcon(); +#endif + } + + inline void reset() { +#ifdef HAVE_ANDROID_OS + bitmap.reset(); + hotSpotX = 0; + hotSpotY = 0; +#endif + } + + inline bool isValid() const { +#ifdef HAVE_ANDROID_OS + return !bitmap.isNull() && !bitmap.empty(); +#else + return false; +#endif + } +}; + +/* + * A sprite is a simple graphical object that is displayed on-screen above other layers. + * The basic sprite class is an interface. + * The implementation is provided by the sprite controller. + */ +class Sprite : public RefBase { +protected: + Sprite() { } + virtual ~Sprite() { } + +public: + enum { + // The base layer for pointer sprites. + BASE_LAYER_POINTER = 0, // reserve space for 1 pointer + + // The base layer for spot sprites. + BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots + }; + + /* Sets the bitmap that is drawn by the sprite. + * The sprite retains a copy of the bitmap for subsequent rendering. */ + virtual void setIcon(const SpriteIcon& icon) = 0; + + inline void clearIcon() { + setIcon(SpriteIcon()); + } + + /* Sets whether the sprite is visible. */ + virtual void setVisible(bool visible) = 0; + + /* Sets the sprite position on screen, relative to the sprite's hot spot. */ + virtual void setPosition(float x, float y) = 0; + + /* Sets the layer of the sprite, relative to the system sprite overlay layer. + * Layer 0 is the overlay layer, > 0 appear above this layer. */ + virtual void setLayer(int32_t layer) = 0; + + /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */ + virtual void setAlpha(float alpha) = 0; + + /* Sets the sprite transformation matrix. */ + virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; +}; + +/* + * Displays sprites on the screen. + * + * This interface is used by PointerController and SpotController to draw pointers or + * spot representations of fingers. It is not intended for general purpose use + * by other components. + * + * All sprite position updates and rendering is performed asynchronously. + * + * Clients are responsible for animating sprites by periodically updating their properties. + */ +class SpriteController : public MessageHandler { +protected: + virtual ~SpriteController(); + +public: + SpriteController(const sp& looper, int32_t overlayLayer); + + /* Creates a new sprite, initially invisible. */ + sp createSprite(); + + /* Opens or closes a transaction to perform a batch of sprite updates as part of + * a single operation such as setPosition and setAlpha. It is not necessary to + * open a transaction when updating a single property. + * Calls to openTransaction() nest and must be matched by an equal number + * of calls to closeTransaction(). */ + void openTransaction(); + void closeTransaction(); + +private: + enum { + MSG_UPDATE_SPRITES, + MSG_DISPOSE_SURFACES, + }; + + enum { + DIRTY_BITMAP = 1 << 0, + DIRTY_ALPHA = 1 << 1, + DIRTY_POSITION = 1 << 2, + DIRTY_TRANSFORMATION_MATRIX = 1 << 3, + DIRTY_LAYER = 1 << 4, + DIRTY_VISIBILITY = 1 << 5, + DIRTY_HOTSPOT = 1 << 6, + }; + + /* Describes the state of a sprite. + * This structure is designed so that it can be copied during updates so that + * surfaces can be resized and redrawn without blocking the client by holding a lock + * on the sprites for a long time. + * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */ + struct SpriteState { + inline SpriteState() : + dirty(0), visible(false), + positionX(0), positionY(0), layer(0), alpha(1.0f), + surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { + } + + uint32_t dirty; + + SpriteIcon icon; + bool visible; + float positionX; + float positionY; + int32_t layer; + float alpha; + SpriteTransformationMatrix transformationMatrix; + +#ifdef HAVE_ANDROID_OS + sp surfaceControl; +#endif + int32_t surfaceWidth; + int32_t surfaceHeight; + bool surfaceDrawn; + bool surfaceVisible; + + inline bool wantSurfaceVisible() const { + return visible && alpha > 0.0f && icon.isValid(); + } + }; + + /* Client interface for a sprite. + * Requests acquire a lock on the controller, update local state and request the + * controller to invalidate the sprite. + * The real heavy lifting of creating, resizing and redrawing surfaces happens + * asynchronously with no locks held except in short critical section to copy + * the sprite state before the work and update the sprite surface control afterwards. + */ + class SpriteImpl : public Sprite { + protected: + virtual ~SpriteImpl(); + + public: + SpriteImpl(const sp controller); + + virtual void setIcon(const SpriteIcon& icon); + virtual void setVisible(bool visible); + virtual void setPosition(float x, float y); + virtual void setLayer(int32_t layer); + virtual void setAlpha(float alpha); + virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); + + inline const SpriteState& getStateLocked() const { + return mLocked.state; + } + + inline void resetDirtyLocked() { + mLocked.state.dirty = 0; + } + +#ifdef HAVE_ANDROID_OS + inline void setSurfaceLocked(const sp& surfaceControl, + int32_t width, int32_t height, bool drawn, bool visible) { + mLocked.state.surfaceControl = surfaceControl; + mLocked.state.surfaceWidth = width; + mLocked.state.surfaceHeight = height; + mLocked.state.surfaceDrawn = drawn; + mLocked.state.surfaceVisible = visible; + } +#endif + + private: + sp mController; + + struct Locked { + SpriteState state; + } mLocked; // guarded by mController->mLock + + void invalidateLocked(uint32_t dirty); + }; + + /* Stores temporary information collected during the sprite update cycle. */ + struct SpriteUpdate { + inline SpriteUpdate() : surfaceChanged(false) { } + inline SpriteUpdate(const sp sprite, const SpriteState& state) : + sprite(sprite), state(state), surfaceChanged(false) { + } + + sp sprite; + SpriteState state; + bool surfaceChanged; + }; + + mutable Mutex mLock; + + sp mLooper; + const int32_t mOverlayLayer; + sp mHandler; + +#ifdef HAVE_ANDROID_OS + sp mSurfaceComposerClient; +#endif + + struct Locked { + Vector > invalidatedSprites; +#ifdef HAVE_ANDROID_OS + Vector > disposedSurfaces; +#endif + uint32_t transactionNestingCount; + bool deferredSpriteUpdate; + } mLocked; // guarded by mLock + + void invalidateSpriteLocked(const sp& sprite); +#ifdef HAVE_ANDROID_OS + void disposeSurfaceLocked(const sp& surfaceControl); +#endif + + void handleMessage(const Message& message); + void doUpdateSprites(); + void doDisposeSurfaces(); + + void ensureSurfaceComposerClient(); +#ifdef HAVE_ANDROID_OS + sp obtainSurface(int32_t width, int32_t height); +#endif +}; + +} // namespace android + +#endif // _UI_SPRITES_H diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp index 80cb46702ba..b4de38b75b2 100644 --- a/widget/gonk/nsAppShell.cpp +++ b/widget/gonk/nsAppShell.cpp @@ -279,7 +279,8 @@ public: GeckoInputReaderPolicy() {} virtual void getReaderConfiguration(InputReaderConfiguration* outConfig); - + virtual sp obtainPointerController(int32_t +deviceId) { return NULL; }; void setDisplayInfo(); protected: