|  | /* | 
|  | * Copyright (C) 2014 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. | 
|  | */ | 
|  |  | 
|  | #include "Animator.h" | 
|  |  | 
|  | #include <inttypes.h> | 
|  | #include <set> | 
|  |  | 
|  | #include "AnimationContext.h" | 
|  | #include "Interpolator.h" | 
|  | #include "RenderNode.h" | 
|  | #include "RenderProperties.h" | 
|  |  | 
|  | namespace android { | 
|  | namespace uirenderer { | 
|  |  | 
|  | /************************************************************ | 
|  | *  BaseRenderNodeAnimator | 
|  | ************************************************************/ | 
|  |  | 
|  | BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue) | 
|  | : mTarget(nullptr) | 
|  | , mStagingTarget(nullptr) | 
|  | , mFinalValue(finalValue) | 
|  | , mDeltaValue(0) | 
|  | , mFromValue(0) | 
|  | , mStagingPlayState(PlayState::NotStarted) | 
|  | , mPlayState(PlayState::NotStarted) | 
|  | , mHasStartValue(false) | 
|  | , mStartTime(0) | 
|  | , mDuration(300) | 
|  | , mStartDelay(0) | 
|  | , mMayRunAsync(true) | 
|  | , mPlayTime(0) {} | 
|  |  | 
|  | BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {} | 
|  |  | 
|  | void BaseRenderNodeAnimator::checkMutable() { | 
|  | // Should be impossible to hit as the Java-side also has guards for this | 
|  | LOG_ALWAYS_FATAL_IF(mStagingPlayState != PlayState::NotStarted, | 
|  | "Animator has already been started!"); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::setInterpolator(Interpolator* interpolator) { | 
|  | checkMutable(); | 
|  | mInterpolator.reset(interpolator); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::setStartValue(float value) { | 
|  | checkMutable(); | 
|  | doSetStartValue(value); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::doSetStartValue(float value) { | 
|  | mFromValue = value; | 
|  | mDeltaValue = (mFinalValue - mFromValue); | 
|  | mHasStartValue = true; | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::setDuration(nsecs_t duration) { | 
|  | checkMutable(); | 
|  | mDuration = duration; | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) { | 
|  | checkMutable(); | 
|  | mStartDelay = startDelay; | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::attach(RenderNode* target) { | 
|  | mStagingTarget = target; | 
|  | onAttached(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::start() { | 
|  | mStagingPlayState = PlayState::Running; | 
|  | mStagingRequests.push_back(Request::Start); | 
|  | onStagingPlayStateChanged(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::cancel() { | 
|  | mStagingPlayState = PlayState::Finished; | 
|  | mStagingRequests.push_back(Request::Cancel); | 
|  | onStagingPlayStateChanged(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::reset() { | 
|  | mStagingPlayState = PlayState::Finished; | 
|  | mStagingRequests.push_back(Request::Reset); | 
|  | onStagingPlayStateChanged(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::reverse() { | 
|  | mStagingPlayState = PlayState::Reversing; | 
|  | mStagingRequests.push_back(Request::Reverse); | 
|  | onStagingPlayStateChanged(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::end() { | 
|  | mStagingPlayState = PlayState::Finished; | 
|  | mStagingRequests.push_back(Request::End); | 
|  | onStagingPlayStateChanged(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::resolveStagingRequest(Request request) { | 
|  | switch (request) { | 
|  | case Request::Start: | 
|  | mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) | 
|  | ? mPlayTime | 
|  | : 0; | 
|  | mPlayState = PlayState::Running; | 
|  | mPendingActionUponFinish = Action::None; | 
|  | break; | 
|  | case Request::Reverse: | 
|  | mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) | 
|  | ? mPlayTime | 
|  | : mDuration; | 
|  | mPlayState = PlayState::Reversing; | 
|  | mPendingActionUponFinish = Action::None; | 
|  | break; | 
|  | case Request::Reset: | 
|  | mPlayTime = 0; | 
|  | mPlayState = PlayState::Finished; | 
|  | mPendingActionUponFinish = Action::Reset; | 
|  | break; | 
|  | case Request::Cancel: | 
|  | mPlayState = PlayState::Finished; | 
|  | mPendingActionUponFinish = Action::None; | 
|  | break; | 
|  | case Request::End: | 
|  | mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration; | 
|  | mPlayState = PlayState::Finished; | 
|  | mPendingActionUponFinish = Action::End; | 
|  | break; | 
|  | default: | 
|  | LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request)); | 
|  | }; | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { | 
|  | if (mStagingTarget) { | 
|  | RenderNode* oldTarget = mTarget; | 
|  | mTarget = mStagingTarget; | 
|  | mStagingTarget = nullptr; | 
|  | if (oldTarget && oldTarget != mTarget) { | 
|  | oldTarget->onAnimatorTargetChanged(this); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!mStagingRequests.empty()) { | 
|  | // No interpolator was set, use the default | 
|  | if (mPlayState == PlayState::NotStarted && !mInterpolator) { | 
|  | mInterpolator.reset(Interpolator::createDefaultInterpolator()); | 
|  | } | 
|  | // Keep track of the play state and play time before they are changed when | 
|  | // staging requests are resolved. | 
|  | nsecs_t currentPlayTime = mPlayTime; | 
|  | PlayState prevFramePlayState = mPlayState; | 
|  |  | 
|  | // Resolve staging requests one by one. | 
|  | for (Request request : mStagingRequests) { | 
|  | resolveStagingRequest(request); | 
|  | } | 
|  | mStagingRequests.clear(); | 
|  |  | 
|  | if (mStagingPlayState == PlayState::Finished) { | 
|  | callOnFinishedListener(context); | 
|  | } else if (mStagingPlayState == PlayState::Running || | 
|  | mStagingPlayState == PlayState::Reversing) { | 
|  | bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState; | 
|  | if (prevFramePlayState != mStagingPlayState) { | 
|  | transitionToRunning(context); | 
|  | } | 
|  | if (changed) { | 
|  | // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was | 
|  | // requested from UI thread). It is achieved by modifying mStartTime, such that | 
|  | // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the | 
|  | // case of reversing) | 
|  | nsecs_t currentFrameTime = context.frameTimeMs(); | 
|  | if (mPlayState == PlayState::Reversing) { | 
|  | // Reverse is not supported for animations with a start delay, so here we | 
|  | // assume no start delay. | 
|  | mStartTime = currentFrameTime - (mDuration - mPlayTime); | 
|  | } else { | 
|  | // Animation should play forward | 
|  | if (mPlayTime == 0) { | 
|  | // If the request is to start from the beginning, include start delay. | 
|  | mStartTime = currentFrameTime + mStartDelay; | 
|  | } else { | 
|  | // If the request is to seek to a non-zero play time, then we skip start | 
|  | // delay. | 
|  | mStartTime = currentFrameTime - mPlayTime; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | onPushStaging(); | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { | 
|  | nsecs_t frameTimeMs = context.frameTimeMs(); | 
|  | LOG_ALWAYS_FATAL_IF(frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", frameTimeMs); | 
|  | if (mStartDelay < 0 || mStartDelay > 50000) { | 
|  | ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay); | 
|  | } | 
|  | mStartTime = frameTimeMs + mStartDelay; | 
|  | if (mStartTime < 0) { | 
|  | ALOGW("Ended up with a really weird start time of %" PRId64 " with frame time %" PRId64 | 
|  | " and start delay %" PRId64, | 
|  | mStartTime, frameTimeMs, mStartDelay); | 
|  | // Set to 0 so that the animate() basically instantly finishes | 
|  | mStartTime = 0; | 
|  | } | 
|  | if (mDuration < 0) { | 
|  | ALOGW("Your duration is strange and confusing: %" PRId64, mDuration); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool BaseRenderNodeAnimator::animate(AnimationContext& context) { | 
|  | if (mPlayState < PlayState::Running) { | 
|  | return false; | 
|  | } | 
|  | if (mPlayState == PlayState::Finished) { | 
|  | if (mPendingActionUponFinish == Action::Reset) { | 
|  | // Skip to start. | 
|  | updatePlayTime(0); | 
|  | } else if (mPendingActionUponFinish == Action::End) { | 
|  | // Skip to end. | 
|  | updatePlayTime(mDuration); | 
|  | } | 
|  | // Reset pending action. | 
|  | mPendingActionUponFinish = Action::None; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // This should be set before setValue() so animators can query this time when setValue | 
|  | // is called. | 
|  | nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime; | 
|  | bool finished = updatePlayTime(currentPlayTime); | 
|  | if (finished && mPlayState != PlayState::Finished) { | 
|  | mPlayState = PlayState::Finished; | 
|  | callOnFinishedListener(context); | 
|  | } | 
|  | return finished; | 
|  | } | 
|  |  | 
|  | bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { | 
|  | mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime; | 
|  | onPlayTimeChanged(mPlayTime); | 
|  | // If BaseRenderNodeAnimator is handling the delay (not typical), then | 
|  | // because the staging properties reflect the final value, we always need | 
|  | // to call setValue even if the animation isn't yet running or is still | 
|  | // being delayed as we need to override the staging value | 
|  | if (playTime < 0) { | 
|  | return false; | 
|  | } | 
|  | if (!this->mHasStartValue) { | 
|  | doSetStartValue(getValue(mTarget)); | 
|  | } | 
|  |  | 
|  | float fraction = 1.0f; | 
|  | if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) { | 
|  | fraction = mPlayTime / (float)mDuration; | 
|  | } | 
|  | fraction = MathUtils::clamp(fraction, 0.0f, 1.0f); | 
|  |  | 
|  | fraction = mInterpolator->interpolate(fraction); | 
|  | setValue(mTarget, mFromValue + (mDeltaValue * fraction)); | 
|  |  | 
|  | return playTime >= mDuration; | 
|  | } | 
|  |  | 
|  | nsecs_t BaseRenderNodeAnimator::getRemainingPlayTime() { | 
|  | return mPlayState == PlayState::Reversing ? mPlayTime : mDuration - mPlayTime; | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) { | 
|  | if (mPlayState < PlayState::Finished) { | 
|  | mPlayState = PlayState::Finished; | 
|  | callOnFinishedListener(context); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BaseRenderNodeAnimator::callOnFinishedListener(AnimationContext& context) { | 
|  | if (mListener.get()) { | 
|  | context.callOnFinished(this, mListener.get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /************************************************************ | 
|  | *  RenderPropertyAnimator | 
|  | ************************************************************/ | 
|  |  | 
|  | struct RenderPropertyAnimator::PropertyAccessors { | 
|  | RenderNode::DirtyPropertyMask dirtyMask; | 
|  | GetFloatProperty getter; | 
|  | SetFloatProperty setter; | 
|  | }; | 
|  |  | 
|  | // Maps RenderProperty enum to accessors | 
|  | const RenderPropertyAnimator::PropertyAccessors RenderPropertyAnimator::PROPERTY_ACCESSOR_LUT[] = { | 
|  | {RenderNode::TRANSLATION_X, &RenderProperties::getTranslationX, | 
|  | &RenderProperties::setTranslationX}, | 
|  | {RenderNode::TRANSLATION_Y, &RenderProperties::getTranslationY, | 
|  | &RenderProperties::setTranslationY}, | 
|  | {RenderNode::TRANSLATION_Z, &RenderProperties::getTranslationZ, | 
|  | &RenderProperties::setTranslationZ}, | 
|  | {RenderNode::SCALE_X, &RenderProperties::getScaleX, &RenderProperties::setScaleX}, | 
|  | {RenderNode::SCALE_Y, &RenderProperties::getScaleY, &RenderProperties::setScaleY}, | 
|  | {RenderNode::ROTATION, &RenderProperties::getRotation, &RenderProperties::setRotation}, | 
|  | {RenderNode::ROTATION_X, &RenderProperties::getRotationX, &RenderProperties::setRotationX}, | 
|  | {RenderNode::ROTATION_Y, &RenderProperties::getRotationY, &RenderProperties::setRotationY}, | 
|  | {RenderNode::X, &RenderProperties::getX, &RenderProperties::setX}, | 
|  | {RenderNode::Y, &RenderProperties::getY, &RenderProperties::setY}, | 
|  | {RenderNode::Z, &RenderProperties::getZ, &RenderProperties::setZ}, | 
|  | {RenderNode::ALPHA, &RenderProperties::getAlpha, &RenderProperties::setAlpha}, | 
|  | }; | 
|  |  | 
|  | RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float finalValue) | 
|  | : BaseRenderNodeAnimator(finalValue), mPropertyAccess(&(PROPERTY_ACCESSOR_LUT[property])) {} | 
|  |  | 
|  | void RenderPropertyAnimator::onAttached() { | 
|  | if (!mHasStartValue && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { | 
|  | setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void RenderPropertyAnimator::onStagingPlayStateChanged() { | 
|  | if (mStagingPlayState == PlayState::Running) { | 
|  | if (mStagingTarget) { | 
|  | (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); | 
|  | } else { | 
|  | // In the case of start delay where stagingTarget has been sync'ed over and null'ed | 
|  | // we delay the properties update to push staging. | 
|  | mShouldUpdateStagingProperties = true; | 
|  | } | 
|  | } else if (mStagingPlayState == PlayState::Finished) { | 
|  | // We're being canceled, so make sure that whatever values the UI thread | 
|  | // is observing for us is pushed over | 
|  | mShouldSyncPropertyFields = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | void RenderPropertyAnimator::onPushStaging() { | 
|  | if (mShouldUpdateStagingProperties) { | 
|  | (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); | 
|  | mShouldUpdateStagingProperties = false; | 
|  | } | 
|  |  | 
|  | if (mShouldSyncPropertyFields) { | 
|  | mTarget->setPropertyFieldsDirty(dirtyMask()); | 
|  | mShouldSyncPropertyFields = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | uint32_t RenderPropertyAnimator::dirtyMask() { | 
|  | return mPropertyAccess->dirtyMask; | 
|  | } | 
|  |  | 
|  | float RenderPropertyAnimator::getValue(RenderNode* target) const { | 
|  | return (target->properties().*mPropertyAccess->getter)(); | 
|  | } | 
|  |  | 
|  | void RenderPropertyAnimator::setValue(RenderNode* target, float value) { | 
|  | (target->animatorProperties().*mPropertyAccess->setter)(value); | 
|  | } | 
|  |  | 
|  | /************************************************************ | 
|  | *  CanvasPropertyPrimitiveAnimator | 
|  | ************************************************************/ | 
|  |  | 
|  | CanvasPropertyPrimitiveAnimator::CanvasPropertyPrimitiveAnimator(CanvasPropertyPrimitive* property, | 
|  | float finalValue) | 
|  | : BaseRenderNodeAnimator(finalValue), mProperty(property) {} | 
|  |  | 
|  | float CanvasPropertyPrimitiveAnimator::getValue(RenderNode* target) const { | 
|  | return mProperty->value; | 
|  | } | 
|  |  | 
|  | void CanvasPropertyPrimitiveAnimator::setValue(RenderNode* target, float value) { | 
|  | mProperty->value = value; | 
|  | } | 
|  |  | 
|  | uint32_t CanvasPropertyPrimitiveAnimator::dirtyMask() { | 
|  | return RenderNode::DISPLAY_LIST; | 
|  | } | 
|  |  | 
|  | /************************************************************ | 
|  | *  CanvasPropertySkPaintAnimator | 
|  | ************************************************************/ | 
|  |  | 
|  | CanvasPropertyPaintAnimator::CanvasPropertyPaintAnimator(CanvasPropertyPaint* property, | 
|  | PaintField field, float finalValue) | 
|  | : BaseRenderNodeAnimator(finalValue), mProperty(property), mField(field) {} | 
|  |  | 
|  | float CanvasPropertyPaintAnimator::getValue(RenderNode* target) const { | 
|  | switch (mField) { | 
|  | case STROKE_WIDTH: | 
|  | return mProperty->value.getStrokeWidth(); | 
|  | case ALPHA: | 
|  | return mProperty->value.getAlpha(); | 
|  | } | 
|  | LOG_ALWAYS_FATAL("Unknown field %d", (int)mField); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static uint8_t to_uint8(float value) { | 
|  | int c = (int)(value + .5f); | 
|  | return static_cast<uint8_t>(c < 0 ? 0 : c > 255 ? 255 : c); | 
|  | } | 
|  |  | 
|  | void CanvasPropertyPaintAnimator::setValue(RenderNode* target, float value) { | 
|  | switch (mField) { | 
|  | case STROKE_WIDTH: | 
|  | mProperty->value.setStrokeWidth(value); | 
|  | return; | 
|  | case ALPHA: | 
|  | mProperty->value.setAlpha(to_uint8(value)); | 
|  | return; | 
|  | } | 
|  | LOG_ALWAYS_FATAL("Unknown field %d", (int)mField); | 
|  | } | 
|  |  | 
|  | uint32_t CanvasPropertyPaintAnimator::dirtyMask() { | 
|  | return RenderNode::DISPLAY_LIST; | 
|  | } | 
|  |  | 
|  | RevealAnimator::RevealAnimator(int centerX, int centerY, float startValue, float finalValue) | 
|  | : BaseRenderNodeAnimator(finalValue), mCenterX(centerX), mCenterY(centerY) { | 
|  | setStartValue(startValue); | 
|  | } | 
|  |  | 
|  | float RevealAnimator::getValue(RenderNode* target) const { | 
|  | return target->properties().getRevealClip().getRadius(); | 
|  | } | 
|  |  | 
|  | void RevealAnimator::setValue(RenderNode* target, float value) { | 
|  | target->animatorProperties().mutableRevealClip().set(true, mCenterX, mCenterY, value); | 
|  | } | 
|  |  | 
|  | uint32_t RevealAnimator::dirtyMask() { | 
|  | return RenderNode::GENERIC; | 
|  | } | 
|  |  | 
|  | } /* namespace uirenderer */ | 
|  | } /* namespace android */ |