1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/android/overscroll_controller_android.h"
7 #include "base/android/build_info.h"
8 #include "base/command_line.h"
9 #include "cc/layers/layer.h"
10 #include "cc/output/compositor_frame_metadata.h"
11 #include "content/browser/android/content_view_core_impl.h"
12 #include "content/browser/android/edge_effect.h"
13 #include "content/browser/android/edge_effect_l.h"
14 #include "content/browser/web_contents/web_contents_impl.h"
15 #include "content/common/input/did_overscroll_params.h"
16 #include "content/public/browser/navigation_controller.h"
17 #include "content/public/browser/user_metrics.h"
18 #include "content/public/common/content_switches.h"
19 #include "third_party/WebKit/public/web/WebInputEvent.h"
20 #include "ui/android/resources/resource_manager.h"
21 #include "ui/android/window_android.h"
22 #include "ui/android/window_android_compositor.h"
23 #include "ui/base/l10n/l10n_util_android.h"
28 // Used for conditional creation of EdgeEffect types for the overscroll glow.
29 const int kAndroidLSDKVersion
= 21;
31 // If the glow effect alpha is greater than this value, the refresh effect will
32 // be suppressed. This value was experimentally determined to provide a
33 // reasonable balance between avoiding accidental refresh activation and
34 // minimizing the wait required to refresh after the glow has been triggered.
35 const float kMinGlowAlphaToDisableRefreshOnL
= 0.085f
;
37 bool IsAndroidLOrNewer() {
38 static bool android_l_or_newer
=
39 base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion
;
40 return android_l_or_newer
;
43 // Suppressing refresh detection when the glow is still animating prevents
44 // visual confusion and accidental activation after repeated scrolls.
45 float MinGlowAlphaToDisableRefresh() {
46 // Only the L effect is guaranteed to be both sufficiently brief and prominent
47 // to provide a meaningful "wait" signal. The refresh effect on previous
48 // Android releases can be quite faint, depending on the OEM-supplied
49 // overscroll resources, and lasts nearly twice as long.
50 if (IsAndroidLOrNewer())
51 return kMinGlowAlphaToDisableRefreshOnL
;
53 // Any value greater than 1 effectively prevents the glow effect from ever
54 // suppressing the refresh effect.
58 scoped_ptr
<EdgeEffectBase
> CreateGlowEdgeEffect(
59 ui::ResourceManager
* resource_manager
,
61 DCHECK(resource_manager
);
62 if (IsAndroidLOrNewer())
63 return scoped_ptr
<EdgeEffectBase
>(new EdgeEffectL(resource_manager
));
65 return scoped_ptr
<EdgeEffectBase
>(
66 new EdgeEffect(resource_manager
, dpi_scale
));
69 scoped_ptr
<OverscrollGlow
> CreateGlowEffect(OverscrollGlowClient
* client
,
71 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
72 switches::kDisableOverscrollEdgeEffect
)) {
76 return make_scoped_ptr(new OverscrollGlow(client
));
79 scoped_ptr
<OverscrollRefresh
> CreateRefreshEffect(
80 OverscrollRefreshHandler
* handler
) {
81 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
82 switches::kDisablePullToRefreshEffect
)) {
86 return make_scoped_ptr(new OverscrollRefresh(handler
));
91 OverscrollControllerAndroid::OverscrollControllerAndroid(
92 ContentViewCoreImpl
* content_view_core
)
93 : compositor_(content_view_core
->GetWindowAndroid()->GetCompositor()),
94 dpi_scale_(content_view_core
->GetDpiScale()),
96 glow_effect_(CreateGlowEffect(this, dpi_scale_
)),
97 refresh_effect_(CreateRefreshEffect(content_view_core
)),
98 is_fullscreen_(false) {
102 OverscrollControllerAndroid::~OverscrollControllerAndroid() {
105 bool OverscrollControllerAndroid::WillHandleGestureEvent(
106 const blink::WebGestureEvent
& event
) {
110 if (!refresh_effect_
)
113 // Suppress refresh detection for fullscreen HTML5 scenarios, e.g., video.
117 // Suppress refresh detection if the glow effect is still prominent.
118 if (glow_effect_
&& glow_effect_
->IsActive()) {
119 if (glow_effect_
->GetVisibleAlpha() > MinGlowAlphaToDisableRefresh())
123 bool handled
= false;
124 switch (event
.type
) {
125 case blink::WebInputEvent::GestureScrollBegin
:
126 refresh_effect_
->OnScrollBegin();
129 case blink::WebInputEvent::GestureScrollUpdate
: {
130 gfx::Vector2dF
scroll_delta(event
.data
.scrollUpdate
.deltaX
,
131 event
.data
.scrollUpdate
.deltaY
);
132 scroll_delta
.Scale(dpi_scale_
);
133 handled
= refresh_effect_
->WillHandleScrollUpdate(scroll_delta
);
136 case blink::WebInputEvent::GestureScrollEnd
:
137 refresh_effect_
->OnScrollEnd(gfx::Vector2dF());
140 case blink::WebInputEvent::GestureFlingStart
: {
141 if (refresh_effect_
->IsActive()) {
142 gfx::Vector2dF
scroll_velocity(event
.data
.flingStart
.velocityX
,
143 event
.data
.flingStart
.velocityY
);
144 scroll_velocity
.Scale(dpi_scale_
);
145 refresh_effect_
->OnScrollEnd(scroll_velocity
);
146 // TODO(jdduke): Figure out a cleaner way of suppressing a fling.
147 // It's important that the any downstream code sees a scroll-ending
148 // event (in this case GestureFlingStart) if it has seen a scroll begin.
149 // Thus, we cannot simply consume the fling. Changing the event type to
150 // a GestureScrollEnd might work in practice, but could lead to
151 // unexpected results. For now, simply truncate the fling velocity, but
152 // not to zero as downstream code may not expect a zero-velocity fling.
153 blink::WebGestureEvent
& modified_event
=
154 const_cast<blink::WebGestureEvent
&>(event
);
155 modified_event
.data
.flingStart
.velocityX
= .01f
;
156 modified_event
.data
.flingStart
.velocityY
= .01f
;
160 case blink::WebInputEvent::GesturePinchBegin
:
161 refresh_effect_
->ReleaseWithoutActivation();
171 void OverscrollControllerAndroid::OnGestureEventAck(
172 const blink::WebGestureEvent
& event
,
173 InputEventAckState ack_result
) {
177 // The overscroll effect requires an explicit release signal that may not be
178 // sent from the renderer compositor.
179 if (event
.type
== blink::WebInputEvent::GestureScrollEnd
||
180 event
.type
== blink::WebInputEvent::GestureFlingStart
) {
181 OnOverscrolled(DidOverscrollParams());
184 if (event
.type
== blink::WebInputEvent::GestureScrollUpdate
&&
186 // The effect should only be allowed if both the causal touch events go
187 // unconsumed and the generated scroll events go unconsumed.
188 bool consumed
= ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
||
189 event
.data
.scrollUpdate
.previousUpdateInSequencePrevented
;
190 refresh_effect_
->OnScrollUpdateAck(consumed
);
194 void OverscrollControllerAndroid::OnOverscrolled(
195 const DidOverscrollParams
& params
) {
199 if (refresh_effect_
&& (refresh_effect_
->IsActive() ||
200 refresh_effect_
->IsAwaitingScrollUpdateAck())) {
201 // An active (or potentially active) refresh effect should always pre-empt
202 // the passive glow effect.
207 glow_effect_
->OnOverscrolled(
208 base::TimeTicks::Now(),
209 gfx::ScaleVector2d(params
.accumulated_overscroll
, dpi_scale_
),
210 gfx::ScaleVector2d(params
.latest_overscroll_delta
, dpi_scale_
),
211 gfx::ScaleVector2d(params
.current_fling_velocity
, dpi_scale_
),
213 params
.causal_event_viewport_point
.OffsetFromOrigin(),
219 bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time
,
220 cc::Layer
* parent_layer
) {
221 DCHECK(parent_layer
);
225 return glow_effect_
->Animate(current_time
, parent_layer
);
228 void OverscrollControllerAndroid::OnFrameMetadataUpdated(
229 const cc::CompositorFrameMetadata
& frame_metadata
) {
230 if (!refresh_effect_
&& !glow_effect_
)
233 const float scale_factor
=
234 frame_metadata
.page_scale_factor
* frame_metadata
.device_scale_factor
;
235 gfx::SizeF viewport_size
=
236 gfx::ScaleSize(frame_metadata
.scrollable_viewport_size
, scale_factor
);
237 gfx::SizeF content_size
=
238 gfx::ScaleSize(frame_metadata
.root_layer_size
, scale_factor
);
239 gfx::Vector2dF content_scroll_offset
=
240 gfx::ScaleVector2d(frame_metadata
.root_scroll_offset
, scale_factor
);
242 if (refresh_effect_
) {
243 refresh_effect_
->OnFrameUpdated(content_scroll_offset
,
244 frame_metadata
.root_overflow_y_hidden
);
248 glow_effect_
->OnFrameUpdated(viewport_size
, content_size
,
249 content_scroll_offset
);
253 void OverscrollControllerAndroid::Enable() {
257 void OverscrollControllerAndroid::Disable() {
263 refresh_effect_
->Reset();
265 glow_effect_
->Reset();
269 void OverscrollControllerAndroid::DidToggleFullscreenModeForTab(
270 bool entered_fullscreen
) {
271 if (is_fullscreen_
== entered_fullscreen
)
273 is_fullscreen_
= entered_fullscreen
;
275 refresh_effect_
->ReleaseWithoutActivation();
278 scoped_ptr
<EdgeEffectBase
> OverscrollControllerAndroid::CreateEdgeEffect() {
279 return CreateGlowEdgeEffect(&compositor_
->GetResourceManager(), dpi_scale_
);
282 void OverscrollControllerAndroid::SetNeedsAnimate() {
283 compositor_
->SetNeedsAnimate();
286 } // namespace content