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/edge_effect.h"
12 #include "content/browser/android/edge_effect_l.h"
13 #include "content/browser/web_contents/web_contents_impl.h"
14 #include "content/common/input/did_overscroll_params.h"
15 #include "content/public/browser/navigation_controller.h"
16 #include "content/public/browser/user_metrics.h"
17 #include "content/public/common/content_switches.h"
18 #include "third_party/WebKit/public/web/WebInputEvent.h"
19 #include "ui/android/resources/resource_manager.h"
20 #include "ui/base/android/window_android_compositor.h"
21 #include "ui/base/l10n/l10n_util_android.h"
26 // Used for conditional creation of EdgeEffect types for the overscroll glow.
27 const int kAndroidLSDKVersion
= 21;
29 // Default offset in dips from the top of the view beyond which the refresh
30 // action will be activated.
31 const int kDefaultRefreshDragTargetDips
= 64;
33 scoped_ptr
<EdgeEffectBase
> CreateGlowEdgeEffect(
34 ui::ResourceManager
* resource_manager
,
36 DCHECK(resource_manager
);
37 static bool use_l_flavoured_effect
=
38 base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion
;
39 if (use_l_flavoured_effect
)
40 return scoped_ptr
<EdgeEffectBase
>(new EdgeEffectL(resource_manager
));
42 return scoped_ptr
<EdgeEffectBase
>(
43 new EdgeEffect(resource_manager
, dpi_scale
));
46 scoped_ptr
<OverscrollGlow
> CreateGlowEffect(OverscrollGlowClient
* client
,
48 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
49 switches::kDisableOverscrollEdgeEffect
)) {
53 return make_scoped_ptr(new OverscrollGlow(client
));
56 scoped_ptr
<OverscrollRefresh
> CreateRefreshEffect(
57 ui::WindowAndroidCompositor
* compositor
,
58 OverscrollRefreshClient
* client
,
60 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
61 switches::kDisablePullToRefreshEffect
)) {
65 return make_scoped_ptr(new OverscrollRefresh(
66 &compositor
->GetResourceManager(), client
,
67 kDefaultRefreshDragTargetDips
* dpi_scale
, l10n_util::IsLayoutRtl()));
72 OverscrollControllerAndroid::OverscrollControllerAndroid(
73 WebContents
* web_contents
,
74 ui::WindowAndroidCompositor
* compositor
,
76 : compositor_(compositor
),
77 dpi_scale_(dpi_scale
),
79 glow_effect_(CreateGlowEffect(this, dpi_scale
)),
80 refresh_effect_(CreateRefreshEffect(compositor
, this, dpi_scale
)),
81 triggered_refresh_active_(false),
82 is_fullscreen_(static_cast<WebContentsImpl
*>(web_contents
)
83 ->IsFullscreenForCurrentTab()) {
87 Observe(web_contents
);
90 OverscrollControllerAndroid::~OverscrollControllerAndroid() {
93 bool OverscrollControllerAndroid::WillHandleGestureEvent(
94 const blink::WebGestureEvent
& event
) {
98 // Suppress refresh detection for fullscreen web apps.
99 if (!refresh_effect_
|| is_fullscreen_
)
102 bool handled
= false;
103 bool maybe_needs_animate
= false;
104 switch (event
.type
) {
105 case blink::WebInputEvent::GestureScrollBegin
:
106 refresh_effect_
->OnScrollBegin();
109 case blink::WebInputEvent::GestureScrollUpdate
: {
110 gfx::Vector2dF
scroll_delta(event
.data
.scrollUpdate
.deltaX
,
111 event
.data
.scrollUpdate
.deltaY
);
112 scroll_delta
.Scale(dpi_scale_
);
113 maybe_needs_animate
= true;
114 handled
= refresh_effect_
->WillHandleScrollUpdate(scroll_delta
);
117 case blink::WebInputEvent::GestureScrollEnd
:
118 refresh_effect_
->OnScrollEnd(gfx::Vector2dF());
119 maybe_needs_animate
= true;
122 case blink::WebInputEvent::GestureFlingStart
: {
123 gfx::Vector2dF
scroll_velocity(event
.data
.flingStart
.velocityX
,
124 event
.data
.flingStart
.velocityY
);
125 scroll_velocity
.Scale(dpi_scale_
);
126 refresh_effect_
->OnScrollEnd(scroll_velocity
);
127 if (refresh_effect_
->IsActive()) {
128 // TODO(jdduke): Figure out a cleaner way of suppressing a fling.
129 // It's important that the any downstream code sees a scroll-ending
130 // event (in this case GestureFlingStart) if it has seen a scroll begin.
131 // Thus, we cannot simply consume the fling. Changing the event type to
132 // a GestureScrollEnd might work in practice, but could lead to
133 // unexpected results. For now, simply truncate the fling velocity, but
134 // not to zero as downstream code may not expect a zero-velocity fling.
135 blink::WebGestureEvent
& modified_event
=
136 const_cast<blink::WebGestureEvent
&>(event
);
137 modified_event
.data
.flingStart
.velocityX
= .01f
;
138 modified_event
.data
.flingStart
.velocityY
= .01f
;
140 maybe_needs_animate
= true;
143 case blink::WebInputEvent::GesturePinchBegin
:
144 refresh_effect_
->ReleaseWithoutActivation();
151 if (maybe_needs_animate
&& refresh_effect_
->IsActive())
157 void OverscrollControllerAndroid::OnGestureEventAck(
158 const blink::WebGestureEvent
& event
,
159 InputEventAckState ack_result
) {
163 // The overscroll effect requires an explicit release signal that may not be
164 // sent from the renderer compositor.
165 if (event
.type
== blink::WebInputEvent::GestureScrollEnd
||
166 event
.type
== blink::WebInputEvent::GestureFlingStart
) {
167 OnOverscrolled(DidOverscrollParams());
170 if (event
.type
== blink::WebInputEvent::GestureScrollUpdate
&&
172 // The effect should only be allowed if both the causal touch events go
173 // unconsumed and the generated scroll events go unconsumed.
174 bool consumed
= ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
||
175 event
.data
.scrollUpdate
.previousUpdateInSequencePrevented
;
176 refresh_effect_
->OnScrollUpdateAck(consumed
);
180 void OverscrollControllerAndroid::OnOverscrolled(
181 const DidOverscrollParams
& params
) {
185 if (refresh_effect_
&& (refresh_effect_
->IsActive() ||
186 refresh_effect_
->IsAwaitingScrollUpdateAck())) {
187 // An active (or potentially active) refresh effect should always pre-empt
188 // the passive glow effect.
193 glow_effect_
->OnOverscrolled(
194 base::TimeTicks::Now(),
195 gfx::ScaleVector2d(params
.accumulated_overscroll
, dpi_scale_
),
196 gfx::ScaleVector2d(params
.latest_overscroll_delta
, dpi_scale_
),
197 gfx::ScaleVector2d(params
.current_fling_velocity
, dpi_scale_
),
199 params
.causal_event_viewport_point
.OffsetFromOrigin(),
205 bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time
,
206 cc::Layer
* parent_layer
) {
207 DCHECK(parent_layer
);
211 bool needs_animate
= false;
213 needs_animate
|= refresh_effect_
->Animate(current_time
, parent_layer
);
215 needs_animate
|= glow_effect_
->Animate(current_time
, parent_layer
);
216 return needs_animate
;
219 void OverscrollControllerAndroid::OnFrameMetadataUpdated(
220 const cc::CompositorFrameMetadata
& frame_metadata
) {
221 if (!refresh_effect_
&& !glow_effect_
)
224 const float scale_factor
=
225 frame_metadata
.page_scale_factor
* frame_metadata
.device_scale_factor
;
226 gfx::SizeF viewport_size
=
227 gfx::ScaleSize(frame_metadata
.scrollable_viewport_size
, scale_factor
);
228 gfx::SizeF content_size
=
229 gfx::ScaleSize(frame_metadata
.root_layer_size
, scale_factor
);
230 gfx::Vector2dF content_scroll_offset
=
231 gfx::ScaleVector2d(frame_metadata
.root_scroll_offset
, scale_factor
);
234 refresh_effect_
->UpdateDisplay(viewport_size
, content_scroll_offset
);
237 glow_effect_
->UpdateDisplay(viewport_size
, content_size
,
238 content_scroll_offset
);
242 void OverscrollControllerAndroid::Enable() {
246 void OverscrollControllerAndroid::Disable() {
252 refresh_effect_
->Reset();
254 glow_effect_
->Reset();
258 void OverscrollControllerAndroid::DidNavigateMainFrame(
259 const LoadCommittedDetails
& details
,
260 const FrameNavigateParams
& params
) {
261 // Once the main frame has navigated, there's little need to further animate
262 // the reload effect. Note that the effect will naturally time out should the
263 // reload be interruped for any reason.
264 triggered_refresh_active_
= false;
267 void OverscrollControllerAndroid::DidToggleFullscreenModeForTab(
268 bool entered_fullscreen
) {
269 if (is_fullscreen_
== entered_fullscreen
)
271 is_fullscreen_
= entered_fullscreen
;
273 refresh_effect_
->ReleaseWithoutActivation();
276 void OverscrollControllerAndroid::TriggerRefresh() {
277 triggered_refresh_active_
= false;
281 triggered_refresh_active_
= true;
282 RecordAction(base::UserMetricsAction("MobilePullGestureReload"));
283 web_contents()->GetController().Reload(true);
286 bool OverscrollControllerAndroid::IsStillRefreshing() const {
287 return triggered_refresh_active_
;
290 scoped_ptr
<EdgeEffectBase
> OverscrollControllerAndroid::CreateEdgeEffect() {
291 return CreateGlowEdgeEffect(&compositor_
->GetResourceManager(), dpi_scale_
);
294 void OverscrollControllerAndroid::SetNeedsAnimate() {
295 compositor_
->SetNeedsAnimate();
298 } // namespace content