summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/AndroidAPZ.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz/src/AndroidAPZ.cpp')
-rw-r--r--gfx/layers/apz/src/AndroidAPZ.cpp274
1 files changed, 274 insertions, 0 deletions
diff --git a/gfx/layers/apz/src/AndroidAPZ.cpp b/gfx/layers/apz/src/AndroidAPZ.cpp
new file mode 100644
index 000000000..70042a870
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidAPZ.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AndroidAPZ.h"
+
+#include "AsyncPanZoomController.h"
+#include "GeneratedJNIWrappers.h"
+#include "gfxPrefs.h"
+#include "OverscrollHandoffState.h"
+#include "ViewConfiguration.h"
+
+#define ANDROID_APZ_LOG(...)
+// #define ANDROID_APZ_LOG(...) printf_stderr("ANDROID_APZ: " __VA_ARGS__)
+
+static float sMaxFlingSpeed = 0.0f;
+
+namespace mozilla {
+namespace layers {
+
+AndroidSpecificState::AndroidSpecificState() {
+ using namespace mozilla::java;
+
+ sdk::ViewConfiguration::LocalRef config;
+ if (sdk::ViewConfiguration::Get(GeckoAppShell::GetApplicationContext(), &config) == NS_OK) {
+ int32_t speed = 0;
+ if (config->GetScaledMaximumFlingVelocity(&speed) == NS_OK) {
+ sMaxFlingSpeed = (float)speed * 0.001f;
+ } else {
+ ANDROID_APZ_LOG("%p Failed to query ViewConfiguration for scaled maximum fling velocity\n", this);
+ }
+ } else {
+ ANDROID_APZ_LOG("%p Failed to get ViewConfiguration\n", this);
+ }
+
+ StackScroller::LocalRef scroller;
+ if (StackScroller::New(GeckoAppShell::GetApplicationContext(), &scroller) != NS_OK) {
+ ANDROID_APZ_LOG("%p Failed to create Android StackScroller\n", this);
+ return;
+ }
+ mOverScroller = scroller;
+}
+
+const float BOUNDS_EPSILON = 1.0f;
+
+// This function is used to convert the scroll offset from a float to an integer
+// suitable for using with the Android OverScroller Class.
+// The Android OverScroller class (unfortunately) operates in integers instead of floats.
+// When casting a float value such as 1.5 to an integer, the value is converted to 1.
+// If this value represents the max scroll offset, the OverScroller class will never scroll
+// to the end of the page as it will always be 0.5 pixels short. To work around this issue,
+// the min and max scroll extents are floor/ceil to convert them to the nearest integer
+// just outside of the actual scroll extents. This means, the starting
+// scroll offset must be converted the same way so that if the frame has already been
+// scrolled 1.5 pixels, it won't be snapped back when converted to an integer. This integer
+// rounding error was one of several causes of Bug 1276463.
+static int32_t
+ClampStart(float aOrigin, float aMin, float aMax)
+{
+ if (aOrigin <= aMin) {
+ return (int32_t)floor(aMin);
+ } else if (aOrigin >= aMax) {
+ return (int32_t)ceil(aMax);
+ }
+ return (int32_t)aOrigin;
+}
+
+AndroidFlingAnimation::AndroidFlingAnimation(AsyncPanZoomController& aApzc,
+ PlatformSpecificStateBase* aPlatformSpecificState,
+ const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
+ bool aFlingIsHandoff,
+ const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
+ : mApzc(aApzc)
+ , mOverscrollHandoffChain(aOverscrollHandoffChain)
+ , mScrolledApzc(aScrolledApzc)
+ , mSentBounceX(false)
+ , mSentBounceY(false)
+ , mFlingDuration(0)
+{
+ MOZ_ASSERT(mOverscrollHandoffChain);
+ AndroidSpecificState* state = aPlatformSpecificState->AsAndroidSpecificState();
+ MOZ_ASSERT(state);
+ mOverScroller = state->mOverScroller;
+ MOZ_ASSERT(mOverScroller);
+
+ // Drop any velocity on axes where we don't have room to scroll anyways
+ // (in this APZC, or an APZC further in the handoff chain).
+ // This ensures that we don't take the 'overscroll' path in Sample()
+ // on account of one axis which can't scroll having a velocity.
+ if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::HORIZONTAL)) {
+ ReentrantMonitorAutoEnter lock(mApzc.mMonitor);
+ mApzc.mX.SetVelocity(0);
+ }
+ if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::VERTICAL)) {
+ ReentrantMonitorAutoEnter lock(mApzc.mMonitor);
+ mApzc.mY.SetVelocity(0);
+ }
+
+ ParentLayerPoint velocity = mApzc.GetVelocityVector();
+
+ float scrollRangeStartX = mApzc.mX.GetPageStart().value;
+ float scrollRangeEndX = mApzc.mX.GetScrollRangeEnd().value;
+ float scrollRangeStartY = mApzc.mY.GetPageStart().value;
+ float scrollRangeEndY = mApzc.mY.GetScrollRangeEnd().value;
+ mStartOffset.x = mPreviousOffset.x = mApzc.mX.GetOrigin().value;
+ mStartOffset.y = mPreviousOffset.y = mApzc.mY.GetOrigin().value;
+ float length = velocity.Length();
+ if (length > 0.0f) {
+ mFlingDirection = velocity / length;
+
+ if ((sMaxFlingSpeed > 0.0f) && (length > sMaxFlingSpeed)) {
+ velocity = mFlingDirection * sMaxFlingSpeed;
+ }
+ }
+
+ mPreviousVelocity = velocity;
+
+ int32_t originX = ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX);
+ int32_t originY = ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY);
+ if (!state->mLastFling.IsNull()) {
+ // If it's been too long since the previous fling, or if the new fling's
+ // velocity is too low, don't allow flywheel to kick in. If we do allow
+ // flywheel to kick in, then we need to update the timestamp on the
+ // StackScroller because otherwise it might use a stale velocity.
+ TimeDuration flingDuration = TimeStamp::Now() - state->mLastFling;
+ if (flingDuration.ToMilliseconds() < gfxPrefs::APZFlingAccelInterval()
+ && velocity.Length() >= gfxPrefs::APZFlingAccelMinVelocity()) {
+ bool unused = false;
+ mOverScroller->ComputeScrollOffset(flingDuration.ToMilliseconds(), &unused);
+ } else {
+ mOverScroller->ForceFinished(true);
+ }
+ }
+ mOverScroller->Fling(originX, originY,
+ // Android needs the velocity in pixels per second and it is in pixels per ms.
+ (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f),
+ (int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX),
+ (int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY),
+ 0, 0, 0);
+ state->mLastFling = TimeStamp::Now();
+}
+
+/**
+ * Advances a fling by an interpolated amount based on the Android OverScroller.
+ * This should be called whenever sampling the content transform for this
+ * frame. Returns true if the fling animation should be advanced by one frame,
+ * or false if there is no fling or the fling has ended.
+ */
+bool
+AndroidFlingAnimation::DoSample(FrameMetrics& aFrameMetrics,
+ const TimeDuration& aDelta)
+{
+ bool shouldContinueFling = true;
+
+ mFlingDuration += aDelta.ToMilliseconds();
+ mOverScroller->ComputeScrollOffset(mFlingDuration, &shouldContinueFling);
+
+ int32_t currentX = 0;
+ int32_t currentY = 0;
+ mOverScroller->GetCurrX(&currentX);
+ mOverScroller->GetCurrY(&currentY);
+ ParentLayerPoint offset((float)currentX, (float)currentY);
+ ParentLayerPoint preCheckedOffset(offset);
+
+ bool hitBoundX = CheckBounds(mApzc.mX, offset.x, mFlingDirection.x, &(offset.x));
+ bool hitBoundY = CheckBounds(mApzc.mY, offset.y, mFlingDirection.y, &(offset.y));
+
+ ParentLayerPoint velocity = mPreviousVelocity;
+
+ // Sometimes the OverScroller fails to update the offset for a frame.
+ // If the frame can still scroll we just use the velocity from the previous
+ // frame. However, if the frame can no longer scroll in the direction
+ // of the fling, then end the animation.
+ if (offset != mPreviousOffset) {
+ if (aDelta.ToMilliseconds() > 0) {
+ mOverScroller->GetCurrSpeedX(&velocity.x);
+ mOverScroller->GetCurrSpeedY(&velocity.y);
+
+ velocity.x /= 1000;
+ velocity.y /= 1000;
+
+ mPreviousVelocity = velocity;
+ }
+ } else if ((fabsf(offset.x - preCheckedOffset.x) > BOUNDS_EPSILON) || (fabsf(offset.y - preCheckedOffset.y) > BOUNDS_EPSILON)) {
+ // The page is no longer scrolling but the fling animation is still animating beyond the page bounds. If it goes
+ // beyond the BOUNDS_EPSILON then it has overflowed and will never stop. In that case, stop the fling animation.
+ shouldContinueFling = false;
+ } else if (hitBoundX && hitBoundY) {
+ // We can't scroll any farther along either axis.
+ shouldContinueFling = false;
+ }
+
+ float speed = velocity.Length();
+
+ // gfxPrefs::APZFlingStoppedThreshold is only used in tests.
+ if (!shouldContinueFling || (speed < gfxPrefs::APZFlingStoppedThreshold())) {
+ if (shouldContinueFling) {
+ // The OverScroller thinks it should continue but the speed is below
+ // the stopping threshold so abort the animation.
+ mOverScroller->AbortAnimation();
+ }
+ // This animation is going to end. If DeferHandleFlingOverscroll
+ // has not been called and there is still some velocity left,
+ // call it so that fling hand off may occur if applicable.
+ if (!mSentBounceX && !mSentBounceY && (speed > 0.0f)) {
+ DeferHandleFlingOverscroll(velocity);
+ }
+ return false;
+ }
+
+ mPreviousOffset = offset;
+
+ mApzc.SetVelocityVector(velocity);
+ aFrameMetrics.SetScrollOffset(offset / aFrameMetrics.GetZoom());
+
+ // If we hit a bounds while flinging, send the velocity so that the bounce
+ // animation can play.
+ if (hitBoundX || hitBoundY) {
+ ParentLayerPoint bounceVelocity = velocity;
+
+ if (!mSentBounceX && hitBoundX && fabsf(offset.x - mStartOffset.x) > BOUNDS_EPSILON) {
+ mSentBounceX = true;
+ } else {
+ bounceVelocity.x = 0.0f;
+ }
+
+ if (!mSentBounceY && hitBoundY && fabsf(offset.y - mStartOffset.y) > BOUNDS_EPSILON) {
+ mSentBounceY = true;
+ } else {
+ bounceVelocity.y = 0.0f;
+ }
+ if (!IsZero(bounceVelocity)) {
+ DeferHandleFlingOverscroll(bounceVelocity);
+ }
+ }
+
+ return true;
+}
+
+void
+AndroidFlingAnimation::DeferHandleFlingOverscroll(ParentLayerPoint& aVelocity)
+{
+ mDeferredTasks.AppendElement(
+ NewRunnableMethod<ParentLayerPoint,
+ RefPtr<const OverscrollHandoffChain>,
+ RefPtr<const AsyncPanZoomController>>(&mApzc,
+ &AsyncPanZoomController::HandleFlingOverscroll,
+ aVelocity,
+ mOverscrollHandoffChain,
+ mScrolledApzc));
+
+}
+
+bool
+AndroidFlingAnimation::CheckBounds(Axis& aAxis, float aValue, float aDirection, float* aClamped)
+{
+ if ((aDirection < 0.0f) && (aValue <= aAxis.GetPageStart().value)) {
+ if (aClamped) {
+ *aClamped = aAxis.GetPageStart().value;
+ }
+ return true;
+ } else if ((aDirection > 0.0f) && (aValue >= aAxis.GetScrollRangeEnd().value)) {
+ if (aClamped) {
+ *aClamped = aAxis.GetScrollRangeEnd().value;
+ }
+ return true;
+ }
+ return false;
+}
+
+} // namespace layers
+} // namespace mozilla