Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "AndroidAPZ.h"
8
9
#include "AndroidFlingPhysics.h"
10
#include "AndroidVelocityTracker.h"
11
#include "AsyncPanZoomController.h"
12
#include "GeneratedJNIWrappers.h"
13
#include "GenericFlingAnimation.h"
14
#include "OverscrollHandoffState.h"
15
#include "SimpleVelocityTracker.h"
16
#include "ViewConfiguration.h"
17
#include "mozilla/StaticPrefs_apz.h"
18
19
static mozilla::LazyLogModule sApzAndLog("apz.android");
20
#define ANDROID_APZ_LOG(...) MOZ_LOG(sApzAndLog, LogLevel::Debug, (__VA_ARGS__))
21
22
static float sMaxFlingSpeed = 0.0f;
23
24
namespace mozilla {
25
namespace layers {
26
27
AndroidSpecificState::AndroidSpecificState() {
28
java::sdk::ViewConfiguration::LocalRef config;
29
if (java::sdk::ViewConfiguration::Get(
30
java::GeckoAppShell::GetApplicationContext(), &config) == NS_OK) {
31
int32_t speed = 0;
32
if (config->GetScaledMaximumFlingVelocity(&speed) == NS_OK) {
33
sMaxFlingSpeed = (float)speed * 0.001f;
34
} else {
35
ANDROID_APZ_LOG(
36
"%p Failed to query ViewConfiguration for scaled maximum fling "
37
"velocity\n",
38
this);
39
}
40
} else {
41
ANDROID_APZ_LOG("%p Failed to get ViewConfiguration\n", this);
42
}
43
44
java::StackScroller::LocalRef scroller;
45
if (java::StackScroller::New(java::GeckoAppShell::GetApplicationContext(),
46
&scroller) != NS_OK) {
47
ANDROID_APZ_LOG("%p Failed to create Android StackScroller\n", this);
48
return;
49
}
50
mOverScroller = scroller;
51
}
52
53
AsyncPanZoomAnimation* AndroidSpecificState::CreateFlingAnimation(
54
AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState,
55
float aPLPPI) {
56
if (StaticPrefs::apz_android_chrome_fling_physics_enabled()) {
57
return new GenericFlingAnimation<AndroidFlingPhysics>(
58
aApzc, aHandoffState.mChain, aHandoffState.mIsHandoff,
59
aHandoffState.mScrolledApzc, aPLPPI);
60
} else {
61
return new StackScrollerFlingAnimation(aApzc, this, aHandoffState.mChain,
62
aHandoffState.mIsHandoff,
63
aHandoffState.mScrolledApzc);
64
}
65
}
66
67
UniquePtr<VelocityTracker> AndroidSpecificState::CreateVelocityTracker(
68
Axis* aAxis) {
69
if (StaticPrefs::apz_android_chrome_fling_physics_enabled()) {
70
return MakeUnique<AndroidVelocityTracker>();
71
}
72
return MakeUnique<SimpleVelocityTracker>(aAxis);
73
}
74
75
/* static */
76
void AndroidSpecificState::InitializeGlobalState() {
77
// Not conditioned on
78
// StaticPrefs::apz_android_chrome_fling_physics_enabled() because the pref
79
// is live.
80
AndroidFlingPhysics::InitializeGlobalState();
81
}
82
83
const float BOUNDS_EPSILON = 1.0f;
84
85
// This function is used to convert the scroll offset from a float to an integer
86
// suitable for using with the Android OverScroller Class.
87
// The Android OverScroller class (unfortunately) operates in integers instead
88
// of floats. When casting a float value such as 1.5 to an integer, the value is
89
// converted to 1. If this value represents the max scroll offset, the
90
// OverScroller class will never scroll to the end of the page as it will always
91
// be 0.5 pixels short. To work around this issue, the min and max scroll
92
// extents are floor/ceil to convert them to the nearest integer just outside of
93
// the actual scroll extents. This means, the starting scroll offset must be
94
// converted the same way so that if the frame has already been scrolled 1.5
95
// pixels, it won't be snapped back when converted to an integer. This integer
96
// rounding error was one of several causes of Bug 1276463.
97
static int32_t ClampStart(float aOrigin, float aMin, float aMax) {
98
if (aOrigin <= aMin) {
99
return (int32_t)floor(aMin);
100
} else if (aOrigin >= aMax) {
101
return (int32_t)ceil(aMax);
102
}
103
return (int32_t)aOrigin;
104
}
105
106
StackScrollerFlingAnimation::StackScrollerFlingAnimation(
107
AsyncPanZoomController& aApzc,
108
PlatformSpecificStateBase* aPlatformSpecificState,
109
const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
110
bool aFlingIsHandoff,
111
const RefPtr<const AsyncPanZoomController>& aScrolledApzc)
112
: mApzc(aApzc),
113
mOverscrollHandoffChain(aOverscrollHandoffChain),
114
mScrolledApzc(aScrolledApzc),
115
mSentBounceX(false),
116
mSentBounceY(false),
117
mFlingDuration(0) {
118
MOZ_ASSERT(mOverscrollHandoffChain);
119
AndroidSpecificState* state =
120
aPlatformSpecificState->AsAndroidSpecificState();
121
MOZ_ASSERT(state);
122
mOverScroller = state->mOverScroller;
123
MOZ_ASSERT(mOverScroller);
124
125
// Drop any velocity on axes where we don't have room to scroll anyways
126
// (in this APZC, or an APZC further in the handoff chain).
127
// This ensures that we don't take the 'overscroll' path in Sample()
128
// on account of one axis which can't scroll having a velocity.
129
if (!mOverscrollHandoffChain->CanScrollInDirection(
130
&mApzc, ScrollDirection::eHorizontal)) {
131
RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
132
mApzc.mX.SetVelocity(0);
133
}
134
if (!mOverscrollHandoffChain->CanScrollInDirection(
135
&mApzc, ScrollDirection::eVertical)) {
136
RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex);
137
mApzc.mY.SetVelocity(0);
138
}
139
140
ParentLayerPoint velocity = mApzc.GetVelocityVector();
141
142
float scrollRangeStartX = mApzc.mX.GetPageStart().value;
143
float scrollRangeEndX = mApzc.mX.GetScrollRangeEnd().value;
144
float scrollRangeStartY = mApzc.mY.GetPageStart().value;
145
float scrollRangeEndY = mApzc.mY.GetScrollRangeEnd().value;
146
mStartOffset.x = mPreviousOffset.x = mApzc.mX.GetOrigin().value;
147
mStartOffset.y = mPreviousOffset.y = mApzc.mY.GetOrigin().value;
148
float length = velocity.Length();
149
if (length > 0.0f) {
150
mFlingDirection = velocity / length;
151
152
if ((sMaxFlingSpeed > 0.0f) && (length > sMaxFlingSpeed)) {
153
velocity = mFlingDirection * sMaxFlingSpeed;
154
}
155
}
156
157
mPreviousVelocity = velocity;
158
159
int32_t originX =
160
ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX);
161
int32_t originY =
162
ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY);
163
if (!state->mLastFling.IsNull()) {
164
// If it's been too long since the previous fling, or if the new fling's
165
// velocity is too low, don't allow flywheel to kick in. If we do allow
166
// flywheel to kick in, then we need to update the timestamp on the
167
// StackScroller because otherwise it might use a stale velocity.
168
TimeDuration flingDuration = TimeStamp::Now() - state->mLastFling;
169
if (flingDuration.ToMilliseconds() <
170
StaticPrefs::apz_fling_accel_interval_ms() &&
171
velocity.Length() >= StaticPrefs::apz_fling_accel_interval_ms()) {
172
bool unused = false;
173
mOverScroller->ComputeScrollOffset(flingDuration.ToMilliseconds(),
174
&unused);
175
} else {
176
mOverScroller->ForceFinished(true);
177
}
178
}
179
mOverScroller->Fling(
180
originX, originY,
181
// Android needs the velocity in pixels per second and it is in pixels per
182
// ms.
183
(int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f),
184
(int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX),
185
(int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY), 0, 0,
186
0);
187
state->mLastFling = TimeStamp::Now();
188
}
189
190
/**
191
* Advances a fling by an interpolated amount based on the Android OverScroller.
192
* This should be called whenever sampling the content transform for this
193
* frame. Returns true if the fling animation should be advanced by one frame,
194
* or false if there is no fling or the fling has ended.
195
*/
196
bool StackScrollerFlingAnimation::DoSample(FrameMetrics& aFrameMetrics,
197
const TimeDuration& aDelta) {
198
bool shouldContinueFling = true;
199
200
mFlingDuration += aDelta.ToMilliseconds();
201
mOverScroller->ComputeScrollOffset(mFlingDuration, &shouldContinueFling);
202
203
int32_t currentX = 0;
204
int32_t currentY = 0;
205
mOverScroller->GetCurrX(&currentX);
206
mOverScroller->GetCurrY(&currentY);
207
ParentLayerPoint offset((float)currentX, (float)currentY);
208
ParentLayerPoint preCheckedOffset(offset);
209
210
bool hitBoundX =
211
CheckBounds(mApzc.mX, offset.x, mFlingDirection.x, &(offset.x));
212
bool hitBoundY =
213
CheckBounds(mApzc.mY, offset.y, mFlingDirection.y, &(offset.y));
214
215
ParentLayerPoint velocity = mPreviousVelocity;
216
217
// Sometimes the OverScroller fails to update the offset for a frame.
218
// If the frame can still scroll we just use the velocity from the previous
219
// frame. However, if the frame can no longer scroll in the direction
220
// of the fling, then end the animation.
221
if (offset != mPreviousOffset) {
222
if (aDelta.ToMilliseconds() > 0) {
223
mOverScroller->GetCurrSpeedX(&velocity.x);
224
mOverScroller->GetCurrSpeedY(&velocity.y);
225
226
velocity.x /= 1000;
227
velocity.y /= 1000;
228
229
mPreviousVelocity = velocity;
230
}
231
} else if ((fabsf(offset.x - preCheckedOffset.x) > BOUNDS_EPSILON) ||
232
(fabsf(offset.y - preCheckedOffset.y) > BOUNDS_EPSILON)) {
233
// The page is no longer scrolling but the fling animation is still
234
// animating beyond the page bounds. If it goes beyond the BOUNDS_EPSILON
235
// then it has overflowed and will never stop. In that case, stop the fling
236
// animation.
237
shouldContinueFling = false;
238
} else if (hitBoundX && hitBoundY) {
239
// We can't scroll any farther along either axis.
240
shouldContinueFling = false;
241
}
242
243
float speed = velocity.Length();
244
245
// StaticPrefs::apz_fling_stopped_threshold is only used in tests.
246
if (!shouldContinueFling ||
247
(speed < StaticPrefs::apz_fling_stopped_threshold())) {
248
if (shouldContinueFling) {
249
// The OverScroller thinks it should continue but the speed is below
250
// the stopping threshold so abort the animation.
251
mOverScroller->AbortAnimation();
252
}
253
// This animation is going to end. If DeferHandleFlingOverscroll
254
// has not been called and there is still some velocity left,
255
// call it so that fling hand off may occur if applicable.
256
if (!mSentBounceX && !mSentBounceY && (speed > 0.0f)) {
257
DeferHandleFlingOverscroll(velocity);
258
}
259
return false;
260
}
261
262
mPreviousOffset = offset;
263
264
mApzc.SetVelocityVector(velocity);
265
mApzc.SetScrollOffset(offset / aFrameMetrics.GetZoom());
266
267
// If we hit a bounds while flinging, send the velocity so that the bounce
268
// animation can play.
269
if (hitBoundX || hitBoundY) {
270
ParentLayerPoint bounceVelocity = velocity;
271
272
if (!mSentBounceX && hitBoundX &&
273
fabsf(offset.x - mStartOffset.x) > BOUNDS_EPSILON) {
274
mSentBounceX = true;
275
} else {
276
bounceVelocity.x = 0.0f;
277
}
278
279
if (!mSentBounceY && hitBoundY &&
280
fabsf(offset.y - mStartOffset.y) > BOUNDS_EPSILON) {
281
mSentBounceY = true;
282
} else {
283
bounceVelocity.y = 0.0f;
284
}
285
if (!IsZero(bounceVelocity)) {
286
DeferHandleFlingOverscroll(bounceVelocity);
287
}
288
}
289
290
return true;
291
}
292
293
void StackScrollerFlingAnimation::DeferHandleFlingOverscroll(
294
ParentLayerPoint& aVelocity) {
295
mDeferredTasks.AppendElement(
296
NewRunnableMethod<ParentLayerPoint, RefPtr<const OverscrollHandoffChain>,
297
RefPtr<const AsyncPanZoomController>>(
298
"layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc,
299
&AsyncPanZoomController::HandleFlingOverscroll, aVelocity,
300
mOverscrollHandoffChain, mScrolledApzc));
301
}
302
303
bool StackScrollerFlingAnimation::CheckBounds(Axis& aAxis, float aValue,
304
float aDirection,
305
float* aClamped) {
306
if ((aDirection < 0.0f) && (aValue <= aAxis.GetPageStart().value)) {
307
if (aClamped) {
308
*aClamped = aAxis.GetPageStart().value;
309
}
310
return true;
311
} else if ((aDirection > 0.0f) &&
312
(aValue >= aAxis.GetScrollRangeEnd().value)) {
313
if (aClamped) {
314
*aClamped = aAxis.GetScrollRangeEnd().value;
315
}
316
return true;
317
}
318
return false;
319
}
320
321
} // namespace layers
322
} // namespace mozilla