|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/android/overscroll_controller_android.h" | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/metrics/field_trial_params.h" | 
|  | #include "cc/layers/layer.h" | 
|  | #include "components/viz/common/quads/compositor_frame_metadata.h" | 
|  | #include "content/public/browser/navigation_controller.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "content/public/common/use_zoom_for_dsf_policy.h" | 
|  | #include "third_party/blink/public/common/input/web_gesture_event.h" | 
|  | #include "third_party/blink/public/common/input/web_input_event.h" | 
|  | #include "ui/android/edge_effect.h" | 
|  | #include "ui/android/resources/resource_manager.h" | 
|  | #include "ui/android/window_android.h" | 
|  | #include "ui/android/window_android_compositor.h" | 
|  | #include "ui/base/l10n/l10n_util_android.h" | 
|  | #include "ui/events/blink/did_overscroll_params.h" | 
|  |  | 
|  | using ui::DidOverscrollParams; | 
|  | using ui::EdgeEffect; | 
|  | using ui::OverscrollGlow; | 
|  | using ui::OverscrollGlowClient; | 
|  | using ui::OverscrollRefresh; | 
|  |  | 
|  | namespace content { | 
|  | namespace { | 
|  |  | 
|  | // If the glow effect alpha is greater than this value, the refresh effect will | 
|  | // be suppressed. This value was experimentally determined to provide a | 
|  | // reasonable balance between avoiding accidental refresh activation and | 
|  | // minimizing the wait required to refresh after the glow has been triggered. | 
|  | const float kMinGlowAlphaToDisableRefresh = 0.085f; | 
|  |  | 
|  | std::unique_ptr<EdgeEffect> CreateGlowEdgeEffect( | 
|  | ui::ResourceManager* resource_manager, | 
|  | float dpi_scale) { | 
|  | DCHECK(resource_manager); | 
|  | return std::make_unique<EdgeEffect>(resource_manager); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<OverscrollGlow> CreateGlowEffect(OverscrollGlowClient* client) { | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kDisableOverscrollEdgeEffect)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return std::make_unique<OverscrollGlow>(client); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<OverscrollRefresh> CreateRefreshEffect( | 
|  | ui::OverscrollRefreshHandler* overscroll_refresh_handler, | 
|  | float dpi_scale) { | 
|  | if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kDisablePullToRefreshEffect)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | float edge_width = OverscrollRefresh::kDefaultNavigationEdgeWidth * dpi_scale; | 
|  | return std::make_unique<OverscrollRefresh>(overscroll_refresh_handler, | 
|  | edge_width); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<OverscrollControllerAndroid> | 
|  | OverscrollControllerAndroid::CreateForTests( | 
|  | ui::WindowAndroidCompositor* compositor, | 
|  | float dpi_scale, | 
|  | std::unique_ptr<ui::OverscrollGlow> glow_effect, | 
|  | std::unique_ptr<ui::OverscrollRefresh> refresh_effect) { | 
|  | return std::unique_ptr<OverscrollControllerAndroid>( | 
|  | new OverscrollControllerAndroid(compositor, dpi_scale, | 
|  | std::move(glow_effect), | 
|  | std::move(refresh_effect))); | 
|  | } | 
|  |  | 
|  | OverscrollControllerAndroid::OverscrollControllerAndroid( | 
|  | ui::WindowAndroidCompositor* compositor, | 
|  | float dpi_scale, | 
|  | std::unique_ptr<ui::OverscrollGlow> glow_effect, | 
|  | std::unique_ptr<ui::OverscrollRefresh> refresh_effect) | 
|  | : compositor_(compositor), | 
|  | dpi_scale_(dpi_scale), | 
|  | enabled_(true), | 
|  | glow_effect_(std::move(glow_effect)), | 
|  | refresh_effect_(std::move(refresh_effect)) {} | 
|  |  | 
|  | OverscrollControllerAndroid::OverscrollControllerAndroid( | 
|  | ui::OverscrollRefreshHandler* overscroll_refresh_handler, | 
|  | ui::WindowAndroidCompositor* compositor, | 
|  | float dpi_scale) | 
|  | : compositor_(compositor), | 
|  | dpi_scale_(dpi_scale), | 
|  | enabled_(true), | 
|  | glow_effect_(CreateGlowEffect(this)), | 
|  | refresh_effect_( | 
|  | CreateRefreshEffect(overscroll_refresh_handler, dpi_scale_)) { | 
|  | DCHECK(compositor_); | 
|  | } | 
|  |  | 
|  | OverscrollControllerAndroid::~OverscrollControllerAndroid() { | 
|  | } | 
|  |  | 
|  | bool OverscrollControllerAndroid::WillHandleGestureEvent( | 
|  | const blink::WebGestureEvent& event) { | 
|  | if (!enabled_) | 
|  | return false; | 
|  |  | 
|  | if (!refresh_effect_) | 
|  | return false; | 
|  |  | 
|  | // Suppress refresh detection if the glow effect is still prominent. | 
|  | if (glow_effect_ && glow_effect_->IsActive()) { | 
|  | if (glow_effect_->GetVisibleAlpha() > kMinGlowAlphaToDisableRefresh) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool handled = false; | 
|  | switch (event.GetType()) { | 
|  | case blink::WebInputEvent::Type::kGestureScrollBegin: | 
|  | refresh_effect_->OnScrollBegin( | 
|  | gfx::ScalePoint(event.PositionInWidget(), dpi_scale_)); | 
|  | break; | 
|  |  | 
|  | case blink::WebInputEvent::Type::kGestureScrollUpdate: { | 
|  | gfx::Vector2dF scroll_delta(event.data.scroll_update.delta_x, | 
|  | event.data.scroll_update.delta_y); | 
|  | scroll_delta.Scale(dpi_scale_); | 
|  | handled = refresh_effect_->WillHandleScrollUpdate(scroll_delta); | 
|  | } break; | 
|  |  | 
|  | case blink::WebInputEvent::Type::kGestureScrollEnd: | 
|  | refresh_effect_->OnScrollEnd(gfx::Vector2dF()); | 
|  | break; | 
|  |  | 
|  | case blink::WebInputEvent::Type::kGestureFlingStart: { | 
|  | if (refresh_effect_->IsActive()) { | 
|  | gfx::Vector2dF scroll_velocity(event.data.fling_start.velocity_x, | 
|  | event.data.fling_start.velocity_y); | 
|  | scroll_velocity.Scale(dpi_scale_); | 
|  | refresh_effect_->OnScrollEnd(scroll_velocity); | 
|  | // TODO(jdduke): Figure out a cleaner way of suppressing a fling. | 
|  | // It's important that the any downstream code sees a scroll-ending | 
|  | // event (in this case GestureFlingStart) if it has seen a scroll begin. | 
|  | // Thus, we cannot simply consume the fling. Changing the event type to | 
|  | // a GestureScrollEnd might work in practice, but could lead to | 
|  | // unexpected results. For now, simply truncate the fling velocity, but | 
|  | // not to zero as downstream code may not expect a zero-velocity fling. | 
|  | blink::WebGestureEvent& modified_event = | 
|  | const_cast<blink::WebGestureEvent&>(event); | 
|  | modified_event.data.fling_start.velocity_x = .01f; | 
|  | modified_event.data.fling_start.velocity_y = .01f; | 
|  | } | 
|  | } break; | 
|  |  | 
|  | case blink::WebInputEvent::Type::kGesturePinchBegin: | 
|  | refresh_effect_->ReleaseWithoutActivation(); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return handled; | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::OnGestureEventAck( | 
|  | const blink::WebGestureEvent& event, | 
|  | blink::mojom::InputEventResultState ack_result) { | 
|  | if (!enabled_) | 
|  | return; | 
|  |  | 
|  | // The overscroll effect requires an explicit release signal that may not be | 
|  | // sent from the renderer compositor. | 
|  | if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollEnd || | 
|  | event.GetType() == blink::WebInputEvent::Type::kGestureFlingStart) { | 
|  | OnOverscrolled(DidOverscrollParams()); | 
|  | } | 
|  |  | 
|  | if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollUpdate && | 
|  | refresh_effect_) { | 
|  | // The effect should only be allowed if the scroll events go unconsumed. | 
|  | if (refresh_effect_->IsAwaitingScrollUpdateAck() && | 
|  | ack_result == blink::mojom::InputEventResultState::kConsumed) { | 
|  | refresh_effect_->Reset(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::OnOverscrolled( | 
|  | const DidOverscrollParams& params) { | 
|  | if (!enabled_) | 
|  | return; | 
|  |  | 
|  | if (refresh_effect_) { | 
|  | refresh_effect_->OnOverscrolled(params.overscroll_behavior); | 
|  |  | 
|  | if (refresh_effect_->IsActive() || | 
|  | refresh_effect_->IsAwaitingScrollUpdateAck()) { | 
|  | // An active (or potentially active) refresh effect should always pre-empt | 
|  | // the passive glow effect. | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // When use-zoom-for-dsf is enabled, each value of params was already scaled | 
|  | // by the device scale factor. | 
|  | float scale_factor = IsUseZoomForDSFEnabled() ? 1.f : dpi_scale_; | 
|  | gfx::Vector2dF accumulated_overscroll = | 
|  | gfx::ScaleVector2d(params.accumulated_overscroll, scale_factor); | 
|  | gfx::Vector2dF latest_overscroll_delta = | 
|  | gfx::ScaleVector2d(params.latest_overscroll_delta, scale_factor); | 
|  | gfx::Vector2dF current_fling_velocity = | 
|  | gfx::ScaleVector2d(params.current_fling_velocity, scale_factor); | 
|  | gfx::Vector2dF overscroll_location = gfx::ScaleVector2d( | 
|  | params.causal_event_viewport_point.OffsetFromOrigin(), scale_factor); | 
|  |  | 
|  | if (params.overscroll_behavior.x == cc::OverscrollBehavior::Type::kNone) { | 
|  | accumulated_overscroll.set_x(0); | 
|  | latest_overscroll_delta.set_x(0); | 
|  | current_fling_velocity.set_x(0); | 
|  | } | 
|  |  | 
|  | if (params.overscroll_behavior.y == cc::OverscrollBehavior::Type::kNone) { | 
|  | accumulated_overscroll.set_y(0); | 
|  | latest_overscroll_delta.set_y(0); | 
|  | current_fling_velocity.set_y(0); | 
|  | } | 
|  |  | 
|  | if (glow_effect_ && glow_effect_->OnOverscrolled( | 
|  | base::TimeTicks::Now(), accumulated_overscroll, | 
|  | latest_overscroll_delta, current_fling_velocity, | 
|  | overscroll_location)) { | 
|  | SetNeedsAnimate(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time, | 
|  | cc::Layer* parent_layer) { | 
|  | DCHECK(parent_layer); | 
|  | if (!enabled_ || !glow_effect_) | 
|  | return false; | 
|  |  | 
|  | return glow_effect_->Animate(current_time, parent_layer); | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::OnFrameMetadataUpdated( | 
|  | float page_scale_factor, | 
|  | float device_scale_factor, | 
|  | const gfx::SizeF& scrollable_viewport_size, | 
|  | const gfx::SizeF& root_layer_size, | 
|  | const gfx::Vector2dF& root_scroll_offset, | 
|  | bool root_overflow_y_hidden) { | 
|  | if (!refresh_effect_ && !glow_effect_) | 
|  | return; | 
|  |  | 
|  | // When use-zoom-for-dsf is enabled, frame_metadata.page_scale_factor was | 
|  | // already scaled by the device scale factor. | 
|  | float scale_factor = page_scale_factor; | 
|  | if (!IsUseZoomForDSFEnabled()) { | 
|  | scale_factor *= device_scale_factor; | 
|  | } | 
|  | gfx::SizeF viewport_size = | 
|  | gfx::ScaleSize(scrollable_viewport_size, scale_factor); | 
|  | gfx::SizeF content_size = gfx::ScaleSize(root_layer_size, scale_factor); | 
|  | gfx::Vector2dF content_scroll_offset = | 
|  | gfx::ScaleVector2d(root_scroll_offset, scale_factor); | 
|  |  | 
|  | if (refresh_effect_) { | 
|  | refresh_effect_->OnFrameUpdated(viewport_size, content_scroll_offset, | 
|  | root_overflow_y_hidden); | 
|  | } | 
|  |  | 
|  | if (glow_effect_) { | 
|  | glow_effect_->OnFrameUpdated(viewport_size, content_size, | 
|  | content_scroll_offset); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::Enable() { | 
|  | enabled_ = true; | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::Disable() { | 
|  | if (!enabled_) | 
|  | return; | 
|  | enabled_ = false; | 
|  | if (!enabled_) { | 
|  | if (refresh_effect_) | 
|  | refresh_effect_->Reset(); | 
|  | if (glow_effect_) | 
|  | glow_effect_->Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::unique_ptr<EdgeEffect> OverscrollControllerAndroid::CreateEdgeEffect() { | 
|  | return CreateGlowEdgeEffect(&compositor_->GetResourceManager(), dpi_scale_); | 
|  | } | 
|  |  | 
|  | void OverscrollControllerAndroid::SetNeedsAnimate() { | 
|  | compositor_->SetNeedsAnimate(); | 
|  | } | 
|  |  | 
|  | }  // namespace content |