Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / android / overscroll_controller_android.cc
blob4548cf33ce20fba36a0dd46aecc0d50726730678
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/android/window_android_compositor.h"
21 #include "ui/base/l10n/l10n_util_android.h"
23 namespace content {
24 namespace {
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 // If the glow effect alpha is greater than this value, the refresh effect will
34 // be suppressed. This value was experimentally determined to provide a
35 // reasonable balance between avoiding accidental refresh activation and
36 // minimizing the wait required to refresh after the glow has been triggered.
37 const float kMinGlowAlphaToDisableRefreshOnL = 0.085f;
39 bool IsAndroidLOrNewer() {
40 static bool android_l_or_newer =
41 base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion;
42 return android_l_or_newer;
45 // Suppressing refresh detection when the glow is still animating prevents
46 // visual confusion and accidental activation after repeated scrolls.
47 float MinGlowAlphaToDisableRefresh() {
48 // Only the L effect is guaranteed to be both sufficiently brief and prominent
49 // to provide a meaningful "wait" signal. The refresh effect on previous
50 // Android releases can be quite faint, depending on the OEM-supplied
51 // overscroll resources, and lasts nearly twice as long.
52 if (IsAndroidLOrNewer())
53 return kMinGlowAlphaToDisableRefreshOnL;
55 // Any value greater than 1 effectively prevents the glow effect from ever
56 // suppressing the refresh effect.
57 return 1.01f;
60 scoped_ptr<EdgeEffectBase> CreateGlowEdgeEffect(
61 ui::ResourceManager* resource_manager,
62 float dpi_scale) {
63 DCHECK(resource_manager);
64 if (IsAndroidLOrNewer())
65 return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager));
67 return scoped_ptr<EdgeEffectBase>(
68 new EdgeEffect(resource_manager, dpi_scale));
71 scoped_ptr<OverscrollGlow> CreateGlowEffect(OverscrollGlowClient* client,
72 float dpi_scale) {
73 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
74 switches::kDisableOverscrollEdgeEffect)) {
75 return nullptr;
78 return make_scoped_ptr(new OverscrollGlow(client));
81 scoped_ptr<OverscrollRefresh> CreateRefreshEffect(
82 ui::WindowAndroidCompositor* compositor,
83 OverscrollRefreshClient* client,
84 float dpi_scale) {
85 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
86 switches::kDisablePullToRefreshEffect)) {
87 return nullptr;
90 return make_scoped_ptr(new OverscrollRefresh(
91 &compositor->GetResourceManager(), client,
92 kDefaultRefreshDragTargetDips * dpi_scale, l10n_util::IsLayoutRtl()));
95 } // namespace
97 OverscrollControllerAndroid::OverscrollControllerAndroid(
98 WebContents* web_contents,
99 ui::WindowAndroidCompositor* compositor,
100 float dpi_scale)
101 : compositor_(compositor),
102 dpi_scale_(dpi_scale),
103 enabled_(true),
104 glow_effect_(CreateGlowEffect(this, dpi_scale)),
105 refresh_effect_(CreateRefreshEffect(compositor, this, dpi_scale)),
106 triggered_refresh_active_(false),
107 is_fullscreen_(static_cast<WebContentsImpl*>(web_contents)
108 ->IsFullscreenForCurrentTab()) {
109 DCHECK(web_contents);
110 DCHECK(compositor);
111 if (refresh_effect_)
112 Observe(web_contents);
115 OverscrollControllerAndroid::~OverscrollControllerAndroid() {
118 bool OverscrollControllerAndroid::WillHandleGestureEvent(
119 const blink::WebGestureEvent& event) {
120 if (!enabled_)
121 return false;
123 if (!refresh_effect_)
124 return false;
126 // Suppress refresh detection for fullscreen HTML5 scenarios, e.g., video.
127 if (is_fullscreen_)
128 return false;
130 // Suppress refresh detection if the glow effect is still prominent.
131 if (glow_effect_ && glow_effect_->IsActive()) {
132 if (glow_effect_->GetVisibleAlpha() > MinGlowAlphaToDisableRefresh())
133 return false;
136 bool handled = false;
137 bool maybe_needs_animate = false;
138 switch (event.type) {
139 case blink::WebInputEvent::GestureScrollBegin:
140 refresh_effect_->OnScrollBegin();
141 break;
143 case blink::WebInputEvent::GestureScrollUpdate: {
144 gfx::Vector2dF scroll_delta(event.data.scrollUpdate.deltaX,
145 event.data.scrollUpdate.deltaY);
146 scroll_delta.Scale(dpi_scale_);
147 maybe_needs_animate = true;
148 handled = refresh_effect_->WillHandleScrollUpdate(scroll_delta);
149 } break;
151 case blink::WebInputEvent::GestureScrollEnd:
152 refresh_effect_->OnScrollEnd(gfx::Vector2dF());
153 maybe_needs_animate = true;
154 break;
156 case blink::WebInputEvent::GestureFlingStart: {
157 gfx::Vector2dF scroll_velocity(event.data.flingStart.velocityX,
158 event.data.flingStart.velocityY);
159 scroll_velocity.Scale(dpi_scale_);
160 refresh_effect_->OnScrollEnd(scroll_velocity);
161 if (refresh_effect_->IsActive()) {
162 // TODO(jdduke): Figure out a cleaner way of suppressing a fling.
163 // It's important that the any downstream code sees a scroll-ending
164 // event (in this case GestureFlingStart) if it has seen a scroll begin.
165 // Thus, we cannot simply consume the fling. Changing the event type to
166 // a GestureScrollEnd might work in practice, but could lead to
167 // unexpected results. For now, simply truncate the fling velocity, but
168 // not to zero as downstream code may not expect a zero-velocity fling.
169 blink::WebGestureEvent& modified_event =
170 const_cast<blink::WebGestureEvent&>(event);
171 modified_event.data.flingStart.velocityX = .01f;
172 modified_event.data.flingStart.velocityY = .01f;
174 maybe_needs_animate = true;
175 } break;
177 case blink::WebInputEvent::GesturePinchBegin:
178 refresh_effect_->ReleaseWithoutActivation();
179 break;
181 default:
182 break;
185 if (maybe_needs_animate && refresh_effect_->IsActive())
186 SetNeedsAnimate();
188 return handled;
191 void OverscrollControllerAndroid::OnGestureEventAck(
192 const blink::WebGestureEvent& event,
193 InputEventAckState ack_result) {
194 if (!enabled_)
195 return;
197 // The overscroll effect requires an explicit release signal that may not be
198 // sent from the renderer compositor.
199 if (event.type == blink::WebInputEvent::GestureScrollEnd ||
200 event.type == blink::WebInputEvent::GestureFlingStart) {
201 OnOverscrolled(DidOverscrollParams());
204 if (event.type == blink::WebInputEvent::GestureScrollUpdate &&
205 refresh_effect_) {
206 // The effect should only be allowed if both the causal touch events go
207 // unconsumed and the generated scroll events go unconsumed.
208 bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED ||
209 event.data.scrollUpdate.previousUpdateInSequencePrevented;
210 refresh_effect_->OnScrollUpdateAck(consumed);
214 void OverscrollControllerAndroid::OnOverscrolled(
215 const DidOverscrollParams& params) {
216 if (!enabled_)
217 return;
219 if (refresh_effect_ && (refresh_effect_->IsActive() ||
220 refresh_effect_->IsAwaitingScrollUpdateAck())) {
221 // An active (or potentially active) refresh effect should always pre-empt
222 // the passive glow effect.
223 return;
226 if (glow_effect_ &&
227 glow_effect_->OnOverscrolled(
228 base::TimeTicks::Now(),
229 gfx::ScaleVector2d(params.accumulated_overscroll, dpi_scale_),
230 gfx::ScaleVector2d(params.latest_overscroll_delta, dpi_scale_),
231 gfx::ScaleVector2d(params.current_fling_velocity, dpi_scale_),
232 gfx::ScaleVector2d(
233 params.causal_event_viewport_point.OffsetFromOrigin(),
234 dpi_scale_))) {
235 SetNeedsAnimate();
239 bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time,
240 cc::Layer* parent_layer) {
241 DCHECK(parent_layer);
242 if (!enabled_)
243 return false;
245 bool needs_animate = false;
246 if (refresh_effect_)
247 needs_animate |= refresh_effect_->Animate(current_time, parent_layer);
248 if (glow_effect_)
249 needs_animate |= glow_effect_->Animate(current_time, parent_layer);
250 return needs_animate;
253 void OverscrollControllerAndroid::OnFrameMetadataUpdated(
254 const cc::CompositorFrameMetadata& frame_metadata) {
255 if (!refresh_effect_ && !glow_effect_)
256 return;
258 const float scale_factor =
259 frame_metadata.page_scale_factor * frame_metadata.device_scale_factor;
260 gfx::SizeF viewport_size =
261 gfx::ScaleSize(frame_metadata.scrollable_viewport_size, scale_factor);
262 gfx::SizeF content_size =
263 gfx::ScaleSize(frame_metadata.root_layer_size, scale_factor);
264 gfx::Vector2dF content_scroll_offset =
265 gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor);
267 if (refresh_effect_) {
268 refresh_effect_->UpdateDisplay(viewport_size, content_scroll_offset,
269 frame_metadata.root_overflow_y_hidden);
272 if (glow_effect_) {
273 glow_effect_->UpdateDisplay(viewport_size, content_size,
274 content_scroll_offset);
278 void OverscrollControllerAndroid::Enable() {
279 enabled_ = true;
282 void OverscrollControllerAndroid::Disable() {
283 if (!enabled_)
284 return;
285 enabled_ = false;
286 if (!enabled_) {
287 if (refresh_effect_)
288 refresh_effect_->Reset();
289 if (glow_effect_)
290 glow_effect_->Reset();
294 void OverscrollControllerAndroid::DidNavigateMainFrame(
295 const LoadCommittedDetails& details,
296 const FrameNavigateParams& params) {
297 // Once the main frame has navigated, there's little need to further animate
298 // the reload effect. Note that the effect will naturally time out should the
299 // reload be interruped for any reason.
300 triggered_refresh_active_ = false;
303 void OverscrollControllerAndroid::DidToggleFullscreenModeForTab(
304 bool entered_fullscreen) {
305 if (is_fullscreen_ == entered_fullscreen)
306 return;
307 is_fullscreen_ = entered_fullscreen;
308 if (is_fullscreen_)
309 refresh_effect_->ReleaseWithoutActivation();
312 void OverscrollControllerAndroid::TriggerRefresh() {
313 triggered_refresh_active_ = false;
314 if (!web_contents())
315 return;
317 triggered_refresh_active_ = true;
318 RecordAction(base::UserMetricsAction("MobilePullGestureReload"));
319 web_contents()->GetController().Reload(true);
322 bool OverscrollControllerAndroid::IsStillRefreshing() const {
323 return triggered_refresh_active_;
326 scoped_ptr<EdgeEffectBase> OverscrollControllerAndroid::CreateEdgeEffect() {
327 return CreateGlowEdgeEffect(&compositor_->GetResourceManager(), dpi_scale_);
330 void OverscrollControllerAndroid::SetNeedsAnimate() {
331 compositor_->SetNeedsAnimate();
334 } // namespace content