0

[Android] Add a native pull-to-refresh overscroll effect

Add a restricted pull-to-refresh effect to Android. The effect is
triggered only when 1) the scroll starts when the page has no vertical
offset, 2) the scroll direction is upward and 3) the initial scroll
is not consumed by the page. Page reloads are triggered only when
the user pulls and releases beyond a certain threshold.

BUG=428429

Review URL: https://codereview.chromium.org/679493002

Cr-Commit-Position: refs/heads/master@{#304320}
This commit is contained in:
jdduke
2014-11-14 16:49:52 -08:00
committed by Commit bot
parent 21404088e7
commit 9db1b9d8f0
44 changed files with 1496 additions and 416 deletions

@ -42,7 +42,7 @@ LOCAL_SRC_FILES += \
$(call all-java-files-under, java/generated_src)
# Java files generated from .template rules. This list should match list of java dependencies in
# android_webview/android_webview.gyp
# android_webview/libwebviewchromium.gyp
LOCAL_GENERATED_SOURCES := \
$(call intermediates-dir-for,GYP,shared)/enums/bitmap_format_java/org/chromium/ui/gfx/BitmapFormat.java \
$(call intermediates-dir-for,GYP,shared)/enums/cert_verify_status_android_java/org/chromium/net/CertVerifyStatusAndroid.java \
@ -63,6 +63,7 @@ $(call intermediates-dir-for,GYP,shared)/enums/base_java_library_load_from_apk_s
$(call intermediates-dir-for,GYP,shared)/enums/base_java_memory_pressure_level/org/chromium/base/MemoryPressureLevel.java \
$(call intermediates-dir-for,GYP,shared)/enums/media_android_imageformat/org/chromium/media/AndroidImageFormat.java \
$(call intermediates-dir-for,GYP,shared)/enums/page_transition_types_java/org/chromium/ui/base/PageTransition.java \
$(call intermediates-dir-for,GYP,shared)/enums/system_ui_resource_type_java/org/chromium/ui/base/SystemUIResourceType.java \
$(call intermediates-dir-for,GYP,shared)/templates/net_errors_java/org/chromium/net/NetError.java \
# content dependencies on java components that are provided by the system on

@ -31,6 +31,7 @@
'../net/net.gyp:private_key_types_java',
'../ui/android/ui_android.gyp:bitmap_format_java',
'../ui/android/ui_android.gyp:page_transition_types_java',
'../ui/android/ui_android.gyp:system_ui_resource_type_java',
'../ui/android/ui_android.gyp:window_open_disposition_java',
],
# Enable feedback-directed optimisation for the library when building in

@ -0,0 +1,33 @@
// 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.
#ifndef CONTENT_BROWSER_ANDROID_ANIMATION_UTILS_H_
#define CONTENT_BROWSER_ANDROID_ANIMATION_UTILS_H_
namespace content {
template <typename T>
T Lerp(T a, T b, T t) {
return a + (b - a) * t;
}
template <typename T>
T Clamp(T value, T low, T high) {
return value < low ? low : (value > high ? high : value);
}
template <typename T>
T Damp(T input, T factor) {
T result;
if (factor == 1) {
result = 1 - (1 - input) * (1 - input);
} else {
result = 1 - std::pow(1 - input, 2 * factor);
}
return result;
}
} // namespace content
#endif // CONTENT_BROWSER_ANDROID_ANIMATION_UTILS_H_

@ -22,6 +22,7 @@
#include "content/browser/android/interstitial_page_delegate_android.h"
#include "content/browser/android/load_url_params.h"
#include "content/browser/android/popup_touch_handle_drawable.h"
#include "content/browser/android/system_ui_resource_manager_impl.h"
#include "content/browser/android/tracing_controller_android.h"
#include "content/browser/android/web_contents_observer_android.h"
#include "content/browser/device_sensors/sensor_manager_android.h"
@ -61,8 +62,9 @@ base::android::RegistrationMethod kContentRegisteredMethods[] = {
{"DateTimePickerAndroid", content::RegisterDateTimeChooserAndroid},
{"DownloadControllerAndroidImpl",
content::DownloadControllerAndroidImpl::RegisterDownloadController},
{"GamepadList", content::GamepadPlatformDataFetcherAndroid::
RegisterGamepadPlatformDataFetcherAndroid},
{"GamepadList",
content::GamepadPlatformDataFetcherAndroid::
RegisterGamepadPlatformDataFetcherAndroid},
{"HandleViewResources",
content::CompositedTouchHandleDrawable::RegisterHandleViewResources},
{"InterstitialPageDelegateAndroid",
@ -87,6 +89,8 @@ base::android::RegistrationMethod kContentRegisteredMethods[] = {
{"ServiceRegistryAndroid", content::ServiceRegistryAndroid::Register},
{"SpeechRecognizerImplAndroid",
content::SpeechRecognizerImplAndroid::RegisterSpeechRecognizer},
{"SystemUIResourceManagerImpl",
content::SystemUIResourceManagerImpl::RegisterUIResources},
{"TimeZoneMonitorAndroid", content::TimeZoneMonitorAndroid::Register},
{"TouchEventSynthesizer",
content::SyntheticGestureTargetAndroid::RegisterTouchEventSynthesizer},

@ -337,7 +337,7 @@ void ContentViewCoreImpl::RenderViewHostChanged(RenderViewHost* old_host,
}
RenderWidgetHostViewAndroid*
ContentViewCoreImpl::GetRenderWidgetHostViewAndroid() {
ContentViewCoreImpl::GetRenderWidgetHostViewAndroid() const {
RenderWidgetHostView* rwhv = NULL;
if (web_contents_) {
rwhv = web_contents_->GetRenderWidgetHostView();
@ -798,8 +798,8 @@ ui::WindowAndroid* ContentViewCoreImpl::GetWindowAndroid() const {
return window_android_;
}
scoped_refptr<cc::Layer> ContentViewCoreImpl::GetLayer() const {
return root_layer_.get();
const scoped_refptr<cc::Layer>& ContentViewCoreImpl::GetLayer() const {
return root_layer_;
}
// ----------------------------------------------------------------------------

@ -51,7 +51,7 @@ class ContentViewCoreImpl : public ContentViewCore,
virtual WebContents* GetWebContents() const override;
virtual ui::ViewAndroid* GetViewAndroid() const override;
virtual ui::WindowAndroid* GetWindowAndroid() const override;
virtual scoped_refptr<cc::Layer> GetLayer() const override;
virtual const scoped_refptr<cc::Layer>& GetLayer() const override;
virtual void ShowPastePopup(int x, int y) override;
virtual void GetScaledContentBitmap(
float scale,
@ -295,7 +295,7 @@ class ContentViewCoreImpl : public ContentViewCore,
void InitWebContents();
RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid();
RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid() const;
blink::WebGestureEvent MakeGestureEvent(
blink::WebInputEvent::Type type, int64 time_ms, float x, float y) const;

@ -6,6 +6,7 @@
#include "cc/layers/layer.h"
#include "cc/layers/ui_resource_layer.h"
#include "content/browser/android/animation_utils.h"
#include "ui/base/android/system_ui_resource_manager.h"
namespace content {
@ -52,32 +53,11 @@ const int kVelocityGlowFactor = 12;
const float kEdgeHeightAtMdpi = 12.f;
const float kGlowHeightAtMdpi = 128.f;
template <typename T>
T Lerp(T a, T b, T t) {
return a + (b - a) * t;
}
template <typename T>
T Clamp(T value, T low, T high) {
return value < low ? low : (value > high ? high : value);
}
template <typename T>
T Damp(T input, T factor) {
T result;
if (factor == 1) {
result = 1 - (1 - input) * (1 - input);
} else {
result = 1 - std::pow(1 - input, 2 * factor);
}
return result;
}
} // namespace
class EdgeEffect::EffectLayer {
public:
EffectLayer(ui::SystemUIResourceManager::ResourceType resource_type,
EffectLayer(ui::SystemUIResourceType resource_type,
ui::SystemUIResourceManager* resource_manager)
: ui_resource_layer_(cc::UIResourceLayer::Create()),
resource_type_(resource_type),
@ -108,7 +88,7 @@ class EdgeEffect::EffectLayer {
}
scoped_refptr<cc::UIResourceLayer> ui_resource_layer_;
ui::SystemUIResourceManager::ResourceType resource_type_;
ui::SystemUIResourceType resource_type_;
ui::SystemUIResourceManager* resource_manager_;
DISALLOW_COPY_AND_ASSIGN(EffectLayer);
@ -116,10 +96,8 @@ class EdgeEffect::EffectLayer {
EdgeEffect::EdgeEffect(ui::SystemUIResourceManager* resource_manager,
float device_scale_factor)
: edge_(new EffectLayer(ui::SystemUIResourceManager::OVERSCROLL_EDGE,
resource_manager)),
glow_(new EffectLayer(ui::SystemUIResourceManager::OVERSCROLL_GLOW,
resource_manager)),
: edge_(new EffectLayer(ui::OVERSCROLL_EDGE, resource_manager)),
glow_(new EffectLayer(ui::OVERSCROLL_GLOW, resource_manager)),
base_edge_height_(kEdgeHeightAtMdpi * device_scale_factor),
base_glow_height_(kGlowHeightAtMdpi * device_scale_factor),
edge_alpha_(0),
@ -362,10 +340,8 @@ void EdgeEffect::SetParent(cc::Layer* parent) {
void EdgeEffect::PreloadResources(
ui::SystemUIResourceManager* resource_manager) {
DCHECK(resource_manager);
resource_manager->PreloadResource(
ui::SystemUIResourceManager::OVERSCROLL_EDGE);
resource_manager->PreloadResource(
ui::SystemUIResourceManager::OVERSCROLL_GLOW);
resource_manager->PreloadResource(ui::OVERSCROLL_EDGE);
resource_manager->PreloadResource(ui::OVERSCROLL_GLOW);
}
} // namespace content

@ -5,6 +5,7 @@
#include "content/browser/android/edge_effect_l.h"
#include "cc/layers/ui_resource_layer.h"
#include "content/browser/android/animation_utils.h"
#include "ui/base/android/system_ui_resource_manager.h"
namespace content {
@ -40,29 +41,7 @@ const float kPullDistanceAlphaGlowFactor = 0.8f;
const int kVelocityGlowFactor = 6;
const ui::SystemUIResourceManager::ResourceType kResourceType =
ui::SystemUIResourceManager::OVERSCROLL_GLOW_L;
template <typename T>
T Lerp(T a, T b, T t) {
return a + (b - a) * t;
}
template <typename T>
T Clamp(T value, T low, T high) {
return value < low ? low : (value > high ? high : value);
}
template <typename T>
T Damp(T input, T factor) {
T result;
if (factor == 1) {
result = 1 - (1 - input) * (1 - input);
} else {
result = 1 - std::pow(1 - input, 2 * factor);
}
return result;
}
const ui::SystemUIResourceType kResourceType = ui::OVERSCROLL_GLOW_L;
} // namespace

@ -0,0 +1,227 @@
// 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/android/build_info.h"
#include "base/bind.h"
#include "cc/layers/layer.h"
#include "cc/output/compositor_frame_metadata.h"
#include "content/browser/android/edge_effect.h"
#include "content/browser/android/edge_effect_l.h"
#include "content/common/input/did_overscroll_params.h"
#include "content/public/browser/web_contents.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "ui/base/android/window_android_compositor.h"
namespace content {
namespace {
// Used for conditional creation of EdgeEffect types for the overscroll glow.
const int kAndroidLSDKVersion = 21;
scoped_ptr<EdgeEffectBase> CreateGlowEdgeEffect(
ui::SystemUIResourceManager* resource_manager,
float dpi_scale) {
DCHECK(resource_manager);
static bool use_l_flavoured_effect =
base::android::BuildInfo::GetInstance()->sdk_int() >= kAndroidLSDKVersion;
if (use_l_flavoured_effect)
return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager));
return scoped_ptr<EdgeEffectBase>(
new EdgeEffect(resource_manager, dpi_scale));
}
} // namespace
OverscrollControllerAndroid::OverscrollControllerAndroid(
WebContents* web_contents,
ui::WindowAndroidCompositor* compositor,
float dpi_scale)
: WebContentsObserver(web_contents),
compositor_(compositor),
dpi_scale_(dpi_scale),
enabled_(true),
glow_effect_(base::Bind(&CreateGlowEdgeEffect,
&compositor->GetSystemUIResourceManager(),
dpi_scale_)),
refresh_effect_(&compositor->GetSystemUIResourceManager(), this),
triggered_refresh_active_(false) {
DCHECK(web_contents);
}
OverscrollControllerAndroid::~OverscrollControllerAndroid() {
}
bool OverscrollControllerAndroid::WillHandleGestureEvent(
const blink::WebGestureEvent& event) {
if (!enabled_)
return false;
bool handled = false;
bool maybe_needs_animate = false;
switch (event.type) {
case blink::WebInputEvent::GestureScrollBegin:
refresh_effect_.OnScrollBegin();
break;
case blink::WebInputEvent::GestureScrollUpdate: {
gfx::Vector2dF scroll_delta(event.data.scrollUpdate.deltaX,
event.data.scrollUpdate.deltaY);
scroll_delta.Scale(dpi_scale_);
maybe_needs_animate = true;
handled = refresh_effect_.WillHandleScrollUpdate(scroll_delta);
} break;
case blink::WebInputEvent::GestureScrollEnd:
refresh_effect_.OnScrollEnd(gfx::Vector2dF());
maybe_needs_animate = true;
break;
case blink::WebInputEvent::GestureFlingStart: {
gfx::Vector2dF scroll_velocity(event.data.flingStart.velocityX,
event.data.flingStart.velocityY);
scroll_velocity.Scale(dpi_scale_);
refresh_effect_.OnScrollEnd(scroll_velocity);
if (refresh_effect_.IsActive()) {
// 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.flingStart.velocityX = .01f;
modified_event.data.flingStart.velocityY = .01f;
}
maybe_needs_animate = true;
} break;
default:
break;
}
if (maybe_needs_animate && refresh_effect_.IsActive())
SetNeedsAnimate();
return handled;
}
void OverscrollControllerAndroid::OnGestureEventAck(
const blink::WebGestureEvent& event,
InputEventAckState ack_result) {
if (!enabled_)
return;
// The overscroll effect requires an explicit release signal that may not be
// sent from the renderer compositor.
if (event.type == blink::WebInputEvent::GestureScrollEnd ||
event.type == blink::WebInputEvent::GestureFlingStart) {
OnOverscrolled(DidOverscrollParams());
}
if (event.type == blink::WebInputEvent::GestureScrollUpdate) {
// The effect should only be allowed if both the causal touch events go
// unconsumed and the generated scroll events go unconsumed.
// TODO(jdduke): Prevent activation if the first touchmove was consumed,
// i.e., the first GSU was prevented.
bool consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
refresh_effect_.OnScrollUpdateAck(consumed);
}
}
void OverscrollControllerAndroid::OnOverscrolled(
const DidOverscrollParams& params) {
if (!enabled_)
return;
if (refresh_effect_.IsActive() ||
refresh_effect_.IsAwaitingScrollUpdateAck()) {
// An active (or potentially active) refresh effect should always pre-empt
// the passive glow effect.
return;
}
if (glow_effect_.OnOverscrolled(
base::TimeTicks::Now(),
gfx::ScaleVector2d(params.accumulated_overscroll, dpi_scale_),
gfx::ScaleVector2d(params.latest_overscroll_delta, dpi_scale_),
gfx::ScaleVector2d(params.current_fling_velocity, dpi_scale_),
gfx::ScaleVector2d(params.causal_event_viewport_point.OffsetFromOrigin(),
dpi_scale_))) {
SetNeedsAnimate();
}
}
bool OverscrollControllerAndroid::Animate(base::TimeTicks current_time,
cc::Layer* parent_layer) {
DCHECK(parent_layer);
if (!enabled_)
return false;
bool needs_animate = refresh_effect_.Animate(current_time, parent_layer);
needs_animate |= glow_effect_.Animate(current_time, parent_layer);
return needs_animate;
}
void OverscrollControllerAndroid::OnFrameMetadataUpdated(
const cc::CompositorFrameMetadata& frame_metadata) {
const float scale_factor =
frame_metadata.page_scale_factor * frame_metadata.device_scale_factor;
gfx::SizeF viewport_size =
gfx::ScaleSize(frame_metadata.scrollable_viewport_size, scale_factor);
gfx::SizeF content_size =
gfx::ScaleSize(frame_metadata.root_layer_size, scale_factor);
gfx::Vector2dF content_scroll_offset =
gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor);
refresh_effect_.UpdateDisplay(viewport_size, content_scroll_offset);
glow_effect_.UpdateDisplay(viewport_size, content_size,
content_scroll_offset);
}
void OverscrollControllerAndroid::Enable() {
enabled_ = true;
}
void OverscrollControllerAndroid::Disable() {
if (!enabled_)
return;
enabled_ = false;
if (!enabled_) {
refresh_effect_.Reset();
glow_effect_.Reset();
}
}
void OverscrollControllerAndroid::DidNavigateMainFrame(
const LoadCommittedDetails& details,
const FrameNavigateParams& params) {
// Once the main frame has navigated, there's little need to further animate
// the reload effect. Note that the effect will naturally time out should the
// reload be interruped for any reason.
triggered_refresh_active_ = false;
}
void OverscrollControllerAndroid::TriggerRefresh() {
triggered_refresh_active_ = false;
if (!web_contents())
return;
triggered_refresh_active_ = true;
web_contents()->ReloadFocusedFrame(false);
}
bool OverscrollControllerAndroid::IsStillRefreshing() const {
return triggered_refresh_active_;
}
void OverscrollControllerAndroid::SetNeedsAnimate() {
compositor_->SetNeedsAnimate();
}
} // namespace content

@ -0,0 +1,92 @@
// 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.
#ifndef CONTENT_BROWSER_ANDROID_OVERSCROLL_CONTROLLER_ANDROID_H_
#define CONTENT_BROWSER_ANDROID_OVERSCROLL_CONTROLLER_ANDROID_H_
#include "base/time/time.h"
#include "content/browser/android/overscroll_glow.h"
#include "content/browser/android/overscroll_refresh.h"
#include "content/common/input/input_event_ack_state.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace blink {
class WebGestureEvent;
}
namespace cc {
class CompositorFrameMetadata;
class Layer;
}
namespace ui {
class WindowAndroidCompositor;
}
namespace content {
struct DidOverscrollParams;
// Glue class for handling all inputs into Android-specific overscroll effects,
// both the passive overscroll glow and the active overscroll pull-to-refresh.
// Note that all input coordinates (both for events and overscroll) are in DIPs.
class OverscrollControllerAndroid : public OverscrollRefreshClient,
public WebContentsObserver {
public:
OverscrollControllerAndroid(WebContents* web_contents,
ui::WindowAndroidCompositor* compositor,
float dpi_scale);
virtual ~OverscrollControllerAndroid();
// Returns true if |event| is consumed by an overscroll effect, in which
// case it should cease propagation.
bool WillHandleGestureEvent(const blink::WebGestureEvent& event);
// To be called upon receipt of a gesture event ack.
void OnGestureEventAck(const blink::WebGestureEvent& event,
InputEventAckState ack_result);
// To be called upon receipt of an overscroll event.
void OnOverscrolled(const DidOverscrollParams& overscroll_params);
// Returns true if the effect still needs animation ticks.
// Note: The effect will detach itself when no further animation is required.
bool Animate(base::TimeTicks current_time, cc::Layer* parent_layer);
// To be called whenever the content frame has been updated.
void OnFrameMetadataUpdated(const cc::CompositorFrameMetadata& metadata);
// Toggle activity of any overscroll effects. When disabled, events will be
// ignored until the controller is re-enabled.
void Enable();
void Disable();
private:
// WebContentsObserver implementation.
void DidNavigateMainFrame(const LoadCommittedDetails& details,
const FrameNavigateParams& params) override;
// OverscrollRefreshClient implementation.
void TriggerRefresh() override;
bool IsStillRefreshing() const override;
void SetNeedsAnimate();
ui::WindowAndroidCompositor* compositor_;
const float dpi_scale_;
bool enabled_;
// TODO(jdduke): Factor out a common API from the two overscroll effects.
OverscrollGlow glow_effect_;
OverscrollRefresh refresh_effect_;
bool triggered_refresh_active_;
DISALLOW_COPY_AND_ASSIGN(OverscrollControllerAndroid);
};
} // namespace content
#endif // CONTENT_BROWSER_ANDROID_OVERSCROLL_CONTROLLER_ANDROID_H_

@ -77,7 +77,7 @@ gfx::SizeF ComputeSize(OverscrollGlow::Edge edge,
OverscrollGlow::OverscrollGlow(const EdgeEffectProvider& edge_effect_provider)
: edge_effect_provider_(edge_effect_provider),
enabled_(true),
edge_offsets_(),
initialized_(false) {
DCHECK(!edge_effect_provider_.is_null());
}
@ -86,45 +86,30 @@ OverscrollGlow::~OverscrollGlow() {
Detach();
}
void OverscrollGlow::Enable() {
enabled_ = true;
}
void OverscrollGlow::Disable() {
if (!enabled_)
void OverscrollGlow::Reset() {
if (!initialized_)
return;
enabled_ = false;
if (!enabled_ && initialized_) {
Detach();
for (size_t i = 0; i < EDGE_COUNT; ++i)
edge_effects_[i]->Finish();
}
Detach();
for (size_t i = 0; i < EDGE_COUNT; ++i)
edge_effects_[i]->Finish();
}
bool OverscrollGlow::OnOverscrolled(cc::Layer* overscrolling_layer,
base::TimeTicks current_time,
bool OverscrollGlow::OnOverscrolled(base::TimeTicks current_time,
gfx::Vector2dF accumulated_overscroll,
gfx::Vector2dF overscroll_delta,
gfx::Vector2dF velocity,
gfx::Vector2dF displacement) {
DCHECK(overscrolling_layer);
if (!enabled_)
return false;
// The size of the glow determines the relative effect of the inputs; an
// empty-sized effect is effectively disabled.
if (display_params_.size.IsEmpty())
if (viewport_size_.IsEmpty())
return false;
// Ignore sufficiently small values that won't meaningfuly affect animation.
overscroll_delta = ZeroSmallComponents(overscroll_delta);
if (overscroll_delta.IsZero()) {
if (initialized_) {
if (initialized_)
Release(current_time);
UpdateLayerAttachment(overscrolling_layer);
}
return NeedsAnimate();
return CheckNeedsAnimate();
}
if (!InitializeIfNecessary())
@ -142,45 +127,55 @@ bool OverscrollGlow::OnOverscrolled(cc::Layer* overscrolling_layer,
else
Pull(current_time, overscroll_delta, displacement);
UpdateLayerAttachment(overscrolling_layer);
return NeedsAnimate();
return CheckNeedsAnimate();
}
bool OverscrollGlow::Animate(base::TimeTicks current_time) {
if (!NeedsAnimate()) {
Detach();
bool OverscrollGlow::Animate(base::TimeTicks current_time,
cc::Layer* parent_layer) {
DCHECK(parent_layer);
if (!CheckNeedsAnimate())
return false;
}
UpdateLayerAttachment(parent_layer);
for (size_t i = 0; i < EDGE_COUNT; ++i) {
if (edge_effects_[i]->Update(current_time)) {
Edge edge = static_cast<Edge>(i);
edge_effects_[i]->ApplyToLayers(
ComputeSize(edge, display_params_.size),
ComputeTransform(
edge, display_params_.size, display_params_.edge_offsets[i]));
ComputeSize(edge, viewport_size_),
ComputeTransform(edge, viewport_size_, edge_offsets_[i]));
}
}
if (!NeedsAnimate()) {
return CheckNeedsAnimate();
}
void OverscrollGlow::UpdateDisplay(
const gfx::SizeF& viewport_size,
const gfx::SizeF& content_size,
const gfx::Vector2dF& content_scroll_offset) {
viewport_size_ = viewport_size;
edge_offsets_[OverscrollGlow::EDGE_TOP] = -content_scroll_offset.y();
edge_offsets_[OverscrollGlow::EDGE_LEFT] = -content_scroll_offset.x();
edge_offsets_[OverscrollGlow::EDGE_BOTTOM] = content_size.height() -
content_scroll_offset.y() -
viewport_size.height();
edge_offsets_[OverscrollGlow::EDGE_RIGHT] =
content_size.width() - content_scroll_offset.x() - viewport_size.width();
}
bool OverscrollGlow::CheckNeedsAnimate() {
if (!initialized_) {
Detach();
return false;
}
return true;
}
void OverscrollGlow::UpdateDisplayParameters(const DisplayParameters& params) {
display_params_ = params;
}
bool OverscrollGlow::NeedsAnimate() const {
if (!enabled_ || !initialized_)
return false;
for (size_t i = 0; i < EDGE_COUNT; ++i) {
if (!edge_effects_[i]->IsFinished())
return true;
}
Detach();
return false;
}
@ -189,10 +184,8 @@ void OverscrollGlow::UpdateLayerAttachment(cc::Layer* parent) {
if (!root_layer_.get())
return;
if (!NeedsAnimate()) {
Detach();
if (!CheckNeedsAnimate())
return;
}
if (root_layer_->parent() != parent)
parent->AddChild(root_layer_);
@ -207,7 +200,6 @@ void OverscrollGlow::Detach() {
}
bool OverscrollGlow::InitializeIfNecessary() {
DCHECK(enabled_);
if (initialized_)
return true;
@ -225,10 +217,11 @@ bool OverscrollGlow::InitializeIfNecessary() {
void OverscrollGlow::Pull(base::TimeTicks current_time,
const gfx::Vector2dF& overscroll_delta,
const gfx::Vector2dF& overscroll_location) {
DCHECK(enabled_ && initialized_);
DCHECK(initialized_);
DCHECK(!overscroll_delta.IsZero());
const float inv_width = 1.f / display_params_.size.width();
const float inv_height = 1.f / display_params_.size.height();
DCHECK(!viewport_size_.IsEmpty());
const float inv_width = 1.f / viewport_size_.width();
const float inv_height = 1.f / viewport_size_.height();
gfx::Vector2dF overscroll_pull =
gfx::ScaleVector2d(overscroll_delta, inv_width, inv_height);
@ -264,7 +257,7 @@ void OverscrollGlow::Absorb(base::TimeTicks current_time,
const gfx::Vector2dF& velocity,
bool x_overscroll_started,
bool y_overscroll_started) {
DCHECK(enabled_ && initialized_);
DCHECK(initialized_);
DCHECK(!velocity.IsZero());
// Only trigger on initial overscroll at a non-zero velocity
@ -295,8 +288,4 @@ EdgeEffectBase* OverscrollGlow::GetOppositeEdge(int edge_index) {
return edge_effects_[(edge_index + 2) % EDGE_COUNT].get();
}
OverscrollGlow::DisplayParameters::DisplayParameters() {
edge_offsets[0] = edge_offsets[1] = edge_offsets[2] = edge_offsets[3] = 0.f;
}
} // namespace content

@ -38,44 +38,36 @@ class OverscrollGlow {
~OverscrollGlow();
// Enable the effect. If the effect was previously disabled, it will remain
// dormant until subsequent calls to |OnOverscrolled()|.
void Enable();
// Deactivate and detach the effect. Subsequent calls to |OnOverscrolled()| or
// |Animate()| will have no effect.
void Disable();
// Effect layers will be attached to |overscrolling_layer| if necessary.
// Called when the root content layer overscrolls.
// |accumulated_overscroll| and |overscroll_delta| are in device pixels, while
// |velocity| is in device pixels / second.
// Returns true if the effect still needs animation ticks.
bool OnOverscrolled(cc::Layer* overscrolling_layer,
base::TimeTicks current_time,
bool OnOverscrolled(base::TimeTicks current_time,
gfx::Vector2dF accumulated_overscroll,
gfx::Vector2dF overscroll_delta,
gfx::Vector2dF velocity,
gfx::Vector2dF overscroll_location);
// Returns true if the effect still needs animation ticks.
// Returns true if the effect still needs animation ticks, with effect layers
// attached to |parent_layer| if necessary.
// Note: The effect will detach itself when no further animation is required.
bool Animate(base::TimeTicks current_time);
bool Animate(base::TimeTicks current_time, cc::Layer* parent_layer);
// Update the effect according to the most recent display parameters,
// Note: All dimensions are in device pixels.
struct DisplayParameters {
DisplayParameters();
gfx::SizeF size;
float edge_offsets[EDGE_COUNT];
};
void UpdateDisplayParameters(const DisplayParameters& params);
void UpdateDisplay(const gfx::SizeF& viewport_size,
const gfx::SizeF& content_size,
const gfx::Vector2dF& content_scroll_offset);
// Reset the effect to its inactive state, clearing any active effects.
void Reset();
private:
enum Axis { AXIS_X, AXIS_Y };
// Returns whether the effect is initialized.
// Returns whether the effect has been properly initialized.
bool InitializeIfNecessary();
bool NeedsAnimate() const;
bool CheckNeedsAnimate();
void UpdateLayerAttachment(cc::Layer* parent);
void Detach();
void Pull(base::TimeTicks current_time,
@ -92,8 +84,8 @@ class OverscrollGlow {
EdgeEffectProvider edge_effect_provider_;
scoped_ptr<EdgeEffectBase> edge_effects_[EDGE_COUNT];
DisplayParameters display_params_;
bool enabled_;
gfx::SizeF viewport_size_;
float edge_offsets_[EDGE_COUNT];
bool initialized_;
scoped_refptr<cc::Layer> root_layer_;

@ -0,0 +1,418 @@
// 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_refresh.h"
#include "cc/layers/ui_resource_layer.h"
#include "cc/trees/layer_tree_host.h"
#include "content/browser/android/animation_utils.h"
#include "ui/base/android/system_ui_resource_manager.h"
using std::max;
using std::min;
namespace content {
namespace {
const ui::SystemUIResourceType kIdleResourceType = ui::OVERSCROLL_REFRESH_IDLE;
const ui::SystemUIResourceType kActiveResourceType =
ui::OVERSCROLL_REFRESH_ACTIVE;
// Animation duration after the effect is released without triggering a refresh.
const int kRecedeTimeMs = 300;
// Animation duration after the effect is released and triggers a refresh.
const int kActivationTimeMs = 1000;
// Max animation duration after the effect is released and triggers a refresh.
const int kMaxActivationTimeMs = kActivationTimeMs * 3;
// Animation duration after the refresh activated phase has completed.
const int kActivationRecedeTimeMs = 300;
// Input threshold required to activate the refresh.
const float kPullActivationThreshold = .35f;
// Input threshold required to start glowing.
const float kGlowActivationThreshold = kPullActivationThreshold * 0.85f;
// Useful for avoiding accidental triggering when a scroll janks (is delayed),
// capping the impulse per event.
const float kMaxNormalizedDeltaPerPull = kPullActivationThreshold / 4.f;
// Maximum offset of the effect relative to the content size.
const float kMaxRelativeOffset = .3f;
// Minimum alpha for the effect layer.
const float kMinAlpha = 0.25f;
// Controls spin velocity.
const float kPullRotationMultiplier = 180.f * (1.f / kPullActivationThreshold);
// Experimentally determined constant used to allow activation even if touch
// release results in a small upward fling (quite common during a slow scroll).
const float kMinFlingVelocityForActivation = -500.f;
const float kEpsilon = 0.005f;
void UpdateLayer(cc::UIResourceLayer* layer,
cc::Layer* parent,
cc::UIResourceId res_id,
const gfx::SizeF& viewport_size,
float relative_offset,
float opacity,
float rotation) {
if (layer->parent() != parent)
parent->AddChild(layer);
if (!layer->layer_tree_host())
return;
// An empty window size, while meaningless, is also relatively harmless, and
// will simply prevent any drawing of the layers.
if (viewport_size.IsEmpty()) {
layer->SetIsDrawable(false);
return;
}
if (!res_id) {
layer->SetIsDrawable(false);
return;
}
if (opacity == 0) {
layer->SetIsDrawable(false);
layer->SetOpacity(0);
return;
}
gfx::Size image_size = layer->layer_tree_host()->GetUIResourceSize(res_id);
layer->SetUIResourceId(res_id);
layer->SetIsDrawable(true);
layer->SetTransformOrigin(
gfx::Point3F(image_size.width() * 0.5f, image_size.height() * 0.5f, 0));
layer->SetBounds(image_size);
layer->SetContentsOpaque(false);
layer->SetOpacity(Clamp(opacity, 0.f, 1.f));
float min_viewport_size = min(viewport_size.width(), viewport_size.height());
float offset_x = (viewport_size.width() - image_size.width()) * 0.5f;
float offset_y =
Damp(relative_offset, 1.2f) * min_viewport_size * kMaxRelativeOffset -
image_size.height();
gfx::Transform transform;
transform.Translate(offset_x, offset_y);
transform.Rotate(rotation);
layer->SetTransform(transform);
}
} // namespace
class OverscrollRefresh::Effect {
public:
Effect(ui::SystemUIResourceManager* resource_manager)
: resource_manager_(resource_manager),
idle_layer_(cc::UIResourceLayer::Create()),
active_layer_(cc::UIResourceLayer::Create()),
idle_alpha_(0),
active_alpha_(0),
offset_(0),
rotation_(0),
idle_alpha_start_(0),
idle_alpha_finish_(0),
active_alpha_start_(0),
active_alpha_finish_(0),
offset_start_(0),
offset_finish_(0),
rotation_start_(0),
rotation_finish_(0),
state_(STATE_IDLE) {
idle_layer_->SetIsDrawable(false);
active_layer_->SetIsDrawable(false);
}
~Effect() { Detach(); }
void Pull(float normalized_delta) {
if (state_ != STATE_PULL)
offset_ = 0;
state_ = STATE_PULL;
normalized_delta = Clamp(normalized_delta, -kMaxNormalizedDeltaPerPull,
kMaxNormalizedDeltaPerPull);
offset_ += normalized_delta;
offset_ = Clamp(offset_, 0.f, 1.f);
idle_alpha_ =
kMinAlpha + (1.f - kMinAlpha) * offset_ / kGlowActivationThreshold;
active_alpha_ = (offset_ - kGlowActivationThreshold) /
(kPullActivationThreshold - kGlowActivationThreshold);
idle_alpha_ = Clamp(idle_alpha_, 0.f, 1.f);
active_alpha_ = Clamp(active_alpha_, 0.f, 1.f);
rotation_ = kPullRotationMultiplier * Damp(offset_, 1.f);
}
bool Animate(base::TimeTicks current_time, bool still_refreshing) {
if (IsFinished())
return false;
if (state_ == STATE_PULL)
return true;
const double dt = (current_time - start_time_).InMilliseconds();
const double t = min(dt / duration_.InMilliseconds(), 1.);
const float interp = static_cast<float>(Damp(t, 1.));
idle_alpha_ = Lerp(idle_alpha_start_, idle_alpha_finish_, interp);
active_alpha_ = Lerp(active_alpha_start_, active_alpha_finish_, interp);
offset_ = Lerp(offset_start_, offset_finish_, interp);
rotation_ = Lerp(rotation_start_, rotation_finish_, interp);
if (t < 1.f - kEpsilon)
return true;
switch (state_) {
case STATE_IDLE:
case STATE_PULL:
NOTREACHED() << "Invalidate state for animation.";
break;
case STATE_ACTIVATED:
start_time_ = current_time;
if (still_refreshing &&
(current_time - activated_start_time_ <
base::TimeDelta::FromMilliseconds(kMaxActivationTimeMs))) {
offset_start_ = offset_finish_ = offset_;
rotation_start_ = rotation_;
rotation_finish_ = rotation_start_ + 360.f;
break;
}
state_ = STATE_ACTIVATED_RECEDE;
duration_ = base::TimeDelta::FromMilliseconds(kActivationRecedeTimeMs);
idle_alpha_start_ = idle_alpha_;
active_alpha_start_ = active_alpha_;
idle_alpha_finish_ = 0;
active_alpha_finish_ = 0;
rotation_start_ = rotation_finish_ = rotation_;
offset_start_ = offset_finish_ = offset_;
break;
case STATE_ACTIVATED_RECEDE:
Finish();
break;
case STATE_RECEDE:
Finish();
break;
};
return !IsFinished();
}
bool Release(base::TimeTicks current_time, bool allow_activation) {
if (state_ != STATE_ACTIVATED && state_ != STATE_PULL)
return false;
if (state_ == STATE_ACTIVATED && allow_activation)
return false;
start_time_ = current_time;
idle_alpha_start_ = idle_alpha_;
active_alpha_start_ = active_alpha_;
offset_start_ = offset_;
rotation_start_ = rotation_;
if (offset_ < kPullActivationThreshold || !allow_activation) {
state_ = STATE_RECEDE;
duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs);
idle_alpha_finish_ = 0;
active_alpha_finish_ = 0;
offset_finish_ = 0;
rotation_finish_ = rotation_start_ - 180.f;
return false;
}
state_ = STATE_ACTIVATED;
duration_ = base::TimeDelta::FromMilliseconds(kActivationTimeMs);
activated_start_time_ = current_time;
idle_alpha_finish_ = idle_alpha_start_;
active_alpha_finish_ = active_alpha_start_;
offset_finish_ = kPullActivationThreshold;
rotation_finish_ = rotation_start_ + 360.f;
return true;
}
void Finish() {
Detach();
idle_layer_->SetIsDrawable(false);
active_layer_->SetIsDrawable(false);
offset_ = 0;
idle_alpha_ = 0;
active_alpha_ = 0;
rotation_ = 0;
state_ = STATE_IDLE;
}
void ApplyToLayers(const gfx::SizeF& size, cc::Layer* parent) {
if (IsFinished())
return;
UpdateLayer(idle_layer_.get(),
parent,
resource_manager_->GetUIResourceId(kIdleResourceType),
size,
offset_,
idle_alpha_,
rotation_);
UpdateLayer(active_layer_.get(),
parent,
resource_manager_->GetUIResourceId(kActiveResourceType),
size,
offset_,
active_alpha_,
rotation_);
}
bool IsFinished() const { return state_ == STATE_IDLE; }
private:
enum State {
STATE_IDLE = 0,
STATE_PULL,
STATE_ACTIVATED,
STATE_ACTIVATED_RECEDE,
STATE_RECEDE
};
void Detach() {
idle_layer_->RemoveFromParent();
active_layer_->RemoveFromParent();
}
ui::SystemUIResourceManager* const resource_manager_;
scoped_refptr<cc::UIResourceLayer> idle_layer_;
scoped_refptr<cc::UIResourceLayer> active_layer_;
float idle_alpha_;
float active_alpha_;
float offset_;
float rotation_;
float idle_alpha_start_;
float idle_alpha_finish_;
float active_alpha_start_;
float active_alpha_finish_;
float offset_start_;
float offset_finish_;
float rotation_start_;
float rotation_finish_;
base::TimeTicks start_time_;
base::TimeTicks activated_start_time_;
base::TimeDelta duration_;
State state_;
};
OverscrollRefresh::OverscrollRefresh(
ui::SystemUIResourceManager* resource_manager,
OverscrollRefreshClient* client)
: client_(client),
scrolled_to_top_(true),
scroll_consumption_state_(DISABLED),
effect_(new Effect(resource_manager)) {
DCHECK(client);
}
OverscrollRefresh::~OverscrollRefresh() {
}
void OverscrollRefresh::Reset() {
scroll_consumption_state_ = DISABLED;
effect_->Finish();
}
void OverscrollRefresh::OnScrollBegin() {
bool allow_activation = false;
Release(allow_activation);
if (scrolled_to_top_)
scroll_consumption_state_ = AWAITING_SCROLL_UPDATE_ACK;
}
void OverscrollRefresh::OnScrollEnd(const gfx::Vector2dF& scroll_velocity) {
bool allow_activation = scroll_velocity.y() > kMinFlingVelocityForActivation;
Release(allow_activation);
}
void OverscrollRefresh::OnScrollUpdateAck(bool was_consumed) {
if (scroll_consumption_state_ != AWAITING_SCROLL_UPDATE_ACK)
return;
scroll_consumption_state_ = was_consumed ? DISABLED : ENABLED;
}
bool OverscrollRefresh::WillHandleScrollUpdate(
const gfx::Vector2dF& scroll_delta) {
if (viewport_size_.IsEmpty())
return false;
switch (scroll_consumption_state_) {
case DISABLED:
return false;
case AWAITING_SCROLL_UPDATE_ACK:
// If the initial scroll motion is downward, never allow activation.
if (scroll_delta.y() <= 0)
scroll_consumption_state_ = DISABLED;
return false;
case ENABLED: {
float normalized_delta = scroll_delta.y() / min(viewport_size_.height(),
viewport_size_.width());
effect_->Pull(normalized_delta);
return true;
}
}
NOTREACHED() << "Invalid overscroll state: " << scroll_consumption_state_;
return false;
}
bool OverscrollRefresh::Animate(base::TimeTicks current_time,
cc::Layer* parent_layer) {
DCHECK(parent_layer);
if (effect_->IsFinished())
return false;
if (effect_->Animate(current_time, client_->IsStillRefreshing()))
effect_->ApplyToLayers(viewport_size_, parent_layer);
return !effect_->IsFinished();
}
bool OverscrollRefresh::IsActive() const {
return scroll_consumption_state_ == ENABLED || !effect_->IsFinished();
}
bool OverscrollRefresh::IsAwaitingScrollUpdateAck() const {
return scroll_consumption_state_ == AWAITING_SCROLL_UPDATE_ACK;
}
void OverscrollRefresh::UpdateDisplay(
const gfx::SizeF& viewport_size,
const gfx::Vector2dF& content_scroll_offset) {
viewport_size_ = viewport_size;
scrolled_to_top_ = content_scroll_offset.y() == 0;
}
void OverscrollRefresh::Release(bool allow_activation) {
if (scroll_consumption_state_ == ENABLED) {
if (effect_->Release(base::TimeTicks::Now(), allow_activation))
client_->TriggerRefresh();
}
scroll_consumption_state_ = DISABLED;
}
} // namespace content

@ -0,0 +1,103 @@
// 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.
#ifndef CONTENT_BROWSER_ANDROID_OVERSCROLL_REFRESH_H_
#define CONTENT_BROWSER_ANDROID_OVERSCROLL_REFRESH_H_
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "content/common/content_export.h"
#include "ui/gfx/size_f.h"
#include "ui/gfx/vector2d_f.h"
namespace cc {
class Layer;
}
namespace ui {
class SystemUIResourceManager;
}
namespace content {
// Allows both page reload activation and page reloading state queries.
class CONTENT_EXPORT OverscrollRefreshClient {
public:
virtual ~OverscrollRefreshClient() {}
// Called when the effect is released beyond the activation threshold. This
// should cause a refresh of some kind, e.g., page reload.
virtual void TriggerRefresh() = 0;
// Whether the triggered refresh has yet to complete. The effect will continue
// animating until the refresh completes (or it reaches a reasonable timeout).
virtual bool IsStillRefreshing() const = 0;
};
// Simple pull-to-refresh styled effect. Listens to scroll events, conditionally
// activating when:
// 1) The scroll begins when the page has no vertical scroll offset.
// 2) The page doesn't consume the initial scroll events.
// 3) The initial scroll direction is upward.
// The actual page reload action is triggered only when the effect is active
// and beyond a particular threshold when released.
class CONTENT_EXPORT OverscrollRefresh {
public:
OverscrollRefresh(ui::SystemUIResourceManager* resource_manager,
OverscrollRefreshClient* client);
~OverscrollRefresh();
// Scroll event stream listening methods.
void OnScrollBegin();
void OnScrollEnd(const gfx::Vector2dF& velocity);
// Scroll ack listener. The effect will only be activated if the initial
// updates go unconsumed.
void OnScrollUpdateAck(bool was_consumed);
// Returns true if the effect has consumed the |scroll_delta|.
bool WillHandleScrollUpdate(const gfx::Vector2dF& scroll_delta);
// Returns true if the effect still needs animation ticks, with effect layers
// attached to |parent| if necessary.
// Note: The effect will detach itself when no further animation is required.
bool Animate(base::TimeTicks current_time, cc::Layer* parent_layer);
// Update the effect according to the most recent display parameters,
// Note: All dimensions are in device pixels.
void UpdateDisplay(const gfx::SizeF& viewport_size,
const gfx::Vector2dF& content_scroll_offset);
// Reset the effect to its inactive state, detaching any active effects.
void Reset();
// Returns true if the refresh effect is either being manipulated or animated.
bool IsActive() const;
// Returns true if the effect is waiting for an unconsumed scroll to start.
bool IsAwaitingScrollUpdateAck() const;
private:
void Release(bool allow_activation);
OverscrollRefreshClient* const client_;
gfx::SizeF viewport_size_;
bool scrolled_to_top_;
enum ScrollConsumptionState {
DISABLED,
AWAITING_SCROLL_UPDATE_ACK,
ENABLED,
} scroll_consumption_state_;
class Effect;
scoped_ptr<Effect> effect_;
DISALLOW_COPY_AND_ASSIGN(OverscrollRefresh);
};
} // namespace content
#endif // CONTENT_BROWSER_ANDROID_OVERSCROLL_REFRESH_H_

@ -0,0 +1,261 @@
// 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 "cc/layers/layer.h"
#include "content/browser/android/overscroll_refresh.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/android/system_ui_resource_manager.h"
namespace content {
gfx::SizeF DefaultViewportSize() {
return gfx::SizeF(512, 512);
}
class OverscrollRefreshTest : public OverscrollRefreshClient,
public ui::SystemUIResourceManager,
public testing::Test {
public:
OverscrollRefreshTest() : refresh_triggered_(false) {}
// OverscrollRefreshClient implementation.
void TriggerRefresh() override {
refresh_triggered_ = true;
still_refreshing_ = true;
}
bool IsStillRefreshing() const override { return still_refreshing_; }
// SystemUIResoruceManager implementation.
void PreloadResource(ui::SystemUIResourceType) override {}
bool GetAndResetRefreshTriggered() {
bool triggered = refresh_triggered_;
refresh_triggered_ = false;
return triggered;
}
protected:
void SignalRefreshCompleted() { still_refreshing_ = false; }
cc::UIResourceId GetUIResourceId(ui::SystemUIResourceType) override {
return 0;
}
private:
bool refresh_triggered_;
bool still_refreshing_;
};
TEST_F(OverscrollRefreshTest, Basic) {
OverscrollRefresh effect(this, this);
gfx::Vector2dF origin_scroll_offset;
effect.UpdateDisplay(DefaultViewportSize(), origin_scroll_offset);
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollBegin();
EXPECT_FALSE(effect.IsActive());
EXPECT_TRUE(effect.IsAwaitingScrollUpdateAck());
// The initial scroll should not be consumed, as it should first be offered
// to content.
gfx::Vector2dF scroll_up(0, 10);
EXPECT_FALSE(effect.WillHandleScrollUpdate(scroll_up));
EXPECT_FALSE(effect.IsActive());
EXPECT_TRUE(effect.IsAwaitingScrollUpdateAck());
// The unconsumed, overscrolling scroll will trigger the effect.
effect.OnScrollUpdateAck(false);
EXPECT_TRUE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
// Further scrolls will be consumed.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.IsActive());
// Even scrolls in the down direction should be consumed.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, -50)));
EXPECT_TRUE(effect.IsActive());
// Feed enough scrolls to the effect to exceeds tht threshold.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 100)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 100)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 100)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 100)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 100)));
EXPECT_TRUE(effect.IsActive());
// Ending the scroll while beyond the threshold should trigger a refresh.
gfx::Vector2dF zero_velocity;
EXPECT_FALSE(GetAndResetRefreshTriggered());
effect.OnScrollEnd(zero_velocity);
EXPECT_TRUE(effect.IsActive());
EXPECT_TRUE(GetAndResetRefreshTriggered());
SignalRefreshCompleted();
// Ensure animation doesn't explode.
base::TimeTicks initial_time = base::TimeTicks::Now();
base::TimeTicks current_time = initial_time;
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
while (effect.Animate(current_time, layer.get()))
current_time += base::TimeDelta::FromMilliseconds(16);
// The effect should terminate in a timely fashion.
EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue());
EXPECT_LE(
current_time.ToInternalValue(),
(initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue());
EXPECT_FALSE(effect.IsActive());
}
TEST_F(OverscrollRefreshTest, AnimationTerminatesEvenIfRefreshNeverTerminates) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
ASSERT_TRUE(effect.IsActive());
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
effect.OnScrollEnd(gfx::Vector2dF(0, 0));
ASSERT_TRUE(GetAndResetRefreshTriggered());
// Verify that the animation terminates even if the triggered refresh
// action never terminates (i.e., |still_refreshing_| is always true).
base::TimeTicks initial_time = base::TimeTicks::Now();
base::TimeTicks current_time = initial_time;
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
while (effect.Animate(current_time, layer.get()))
current_time += base::TimeDelta::FromMilliseconds(16);
EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue());
EXPECT_LE(
current_time.ToInternalValue(),
(initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue());
EXPECT_FALSE(effect.IsActive());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfBelowThreshold) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
ASSERT_TRUE(effect.IsActive());
// Terminating the pull before it exceeds the threshold will prevent refresh.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
effect.OnScrollEnd(gfx::Vector2dF());
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialYOffsetIsNotZero) {
OverscrollRefresh effect(this, this);
// A positive y scroll offset at the start of scroll will prevent activation,
// even if the subsequent scroll overscrolls upward.
gfx::Vector2dF nonzero_offset(0, 10);
effect.UpdateDisplay(DefaultViewportSize(), nonzero_offset);
effect.OnScrollBegin();
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
effect.OnScrollEnd(gfx::Vector2dF());
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollDownward) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
// A downward initial scroll will prevent activation, even if the subsequent
// scroll overscrolls upward.
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, -10)));
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
effect.OnScrollEnd(gfx::Vector2dF());
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollOrTouchConsumed) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
// Consumption of the initial touchmove or scroll should prevent future
// activation.
effect.OnScrollUpdateAck(true);
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
effect.OnScrollUpdateAck(false);
EXPECT_FALSE(effect.IsActive());
EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
effect.OnScrollEnd(gfx::Vector2dF());
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollsJanked) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
ASSERT_TRUE(effect.IsActive());
// It should take more than just one or two large scrolls to trigger,
// mitigating likelihood of jank triggering the effect.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
effect.OnScrollEnd(gfx::Vector2dF());
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
TEST_F(OverscrollRefreshTest, NotTriggeredIfFlungDownward) {
OverscrollRefresh effect(this, this);
effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
effect.OnScrollBegin();
ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
effect.OnScrollUpdateAck(false);
ASSERT_TRUE(effect.IsActive());
// Ensure the pull exceeds the necessary threshold.
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
// Terminating the pull with a down-directed fling should prevent triggering.
effect.OnScrollEnd(gfx::Vector2dF(0, -1000));
EXPECT_FALSE(GetAndResetRefreshTriggered());
}
} // namespace content

@ -13,6 +13,8 @@
#include "cc/resources/ui_resource_bitmap.h"
#include "content/public/browser/android/ui_resource_client_android.h"
#include "content/public/browser/android/ui_resource_provider.h"
#include "jni/UIResources_jni.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
@ -48,24 +50,45 @@ SkBitmap CreateOverscrollGlowLBitmap(const gfx::Size& screen_size) {
return glow_bitmap;
}
void LoadBitmap(ui::SystemUIResourceManager::ResourceType type,
SkBitmap LoadJavaBitmap(ui::SystemUIResourceType type, const gfx::Size& size) {
ScopedJavaLocalRef<jobject> jobj =
Java_UIResources_getBitmap(base::android::AttachCurrentThread(),
base::android::GetApplicationContext(), type,
size.width(), size.height());
if (jobj.is_null())
return SkBitmap();
SkBitmap bitmap = CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(jobj.obj()));
if (bitmap.isNull())
return bitmap;
if (size.IsEmpty())
return bitmap;
return skia::ImageOperations::Resize(
bitmap, skia::ImageOperations::RESIZE_BOX, size.width(), size.height());
}
void LoadBitmap(ui::SystemUIResourceType type,
SkBitmap* bitmap_holder,
const gfx::Size& screen_size) {
TRACE_EVENT1(
"browser", "SystemUIResourceManagerImpl::LoadBitmap", "type", type);
SkBitmap bitmap;
switch (type) {
case ui::SystemUIResourceManager::OVERSCROLL_EDGE:
bitmap = gfx::CreateSkBitmapFromAndroidResource(
"android:drawable/overscroll_edge", gfx::Size(128, 12));
case ui::OVERSCROLL_EDGE:
bitmap = LoadJavaBitmap(type, gfx::Size(128, 12));
break;
case ui::SystemUIResourceManager::OVERSCROLL_GLOW:
bitmap = gfx::CreateSkBitmapFromAndroidResource(
"android:drawable/overscroll_glow", gfx::Size(128, 64));
case ui::OVERSCROLL_GLOW:
bitmap = LoadJavaBitmap(type, gfx::Size(128, 64));
break;
case ui::SystemUIResourceManager::OVERSCROLL_GLOW_L:
case ui::OVERSCROLL_GLOW_L:
bitmap = CreateOverscrollGlowLBitmap(screen_size);
break;
case ui::OVERSCROLL_REFRESH_IDLE:
case ui::OVERSCROLL_REFRESH_ACTIVE:
bitmap = LoadJavaBitmap(type, gfx::Size());
break;
}
bitmap.setImmutable();
*bitmap_holder = bitmap;
@ -80,7 +103,7 @@ class SystemUIResourceManagerImpl::Entry
DCHECK(provider);
}
virtual ~Entry() {
~Entry() override {
if (id_)
provider_->DeleteUIResource(id_);
id_ = 0;
@ -102,14 +125,14 @@ class SystemUIResourceManagerImpl::Entry
}
// content::UIResourceClient implementation.
virtual cc::UIResourceBitmap GetBitmap(cc::UIResourceId uid,
bool resource_lost) override {
cc::UIResourceBitmap GetBitmap(cc::UIResourceId uid,
bool resource_lost) override {
DCHECK(!bitmap_.empty());
return cc::UIResourceBitmap(bitmap_);
}
// content::UIResourceClientAndroid implementation.
virtual void UIResourceIsInvalid() override { id_ = 0; }
void UIResourceIsInvalid() override { id_ = 0; }
const SkBitmap& bitmap() const { return bitmap_; }
@ -129,19 +152,20 @@ SystemUIResourceManagerImpl::SystemUIResourceManagerImpl(
SystemUIResourceManagerImpl::~SystemUIResourceManagerImpl() {
}
void SystemUIResourceManagerImpl::PreloadResource(ResourceType type) {
void SystemUIResourceManagerImpl::PreloadResource(
ui::SystemUIResourceType type) {
GetEntry(type);
}
cc::UIResourceId SystemUIResourceManagerImpl::GetUIResourceId(
ResourceType type) {
ui::SystemUIResourceType type) {
return GetEntry(type)->GetUIResourceId();
}
SystemUIResourceManagerImpl::Entry* SystemUIResourceManagerImpl::GetEntry(
ResourceType type) {
DCHECK_GE(type, RESOURCE_TYPE_FIRST);
DCHECK_LE(type, RESOURCE_TYPE_LAST);
ui::SystemUIResourceType type) {
DCHECK_GE(type, ui::SYSTEM_UI_RESOURCE_TYPE_FIRST);
DCHECK_LE(type, ui::SYSTEM_UI_RESOURCE_TYPE_LAST);
if (!resource_map_[type]) {
resource_map_[type].reset(new Entry(ui_resource_provider_));
// Lazily build the resource.
@ -150,7 +174,7 @@ SystemUIResourceManagerImpl::Entry* SystemUIResourceManagerImpl::GetEntry(
return resource_map_[type].get();
}
void SystemUIResourceManagerImpl::BuildResource(ResourceType type) {
void SystemUIResourceManagerImpl::BuildResource(ui::SystemUIResourceType type) {
DCHECK(GetEntry(type)->bitmap().empty());
// Instead of blocking the main thread, we post a task to load the bitmap.
@ -169,10 +193,15 @@ void SystemUIResourceManagerImpl::BuildResource(ResourceType type) {
}
void SystemUIResourceManagerImpl::OnFinishedLoadBitmap(
ResourceType type,
ui::SystemUIResourceType type,
SkBitmap* bitmap_holder) {
DCHECK(bitmap_holder);
GetEntry(type)->SetBitmap(*bitmap_holder);
}
// static
bool SystemUIResourceManagerImpl::RegisterUIResources(JNIEnv* env) {
return RegisterNativesImpl(env);
}
} // namespace content

@ -5,6 +5,7 @@
#ifndef CONTENT_BROWSER_ANDROID_SYSTEM_UI_RESOURCE_MANAGER_IMPL_H_
#define CONTENT_BROWSER_ANDROID_SYSTEM_UI_RESOURCE_MANAGER_IMPL_H_
#include "base/android/jni_android.h"
#include "base/basictypes.h"
#include "base/containers/scoped_ptr_hash_map.h"
#include "base/memory/weak_ptr.h"
@ -26,22 +27,24 @@ class CONTENT_EXPORT SystemUIResourceManagerImpl
public:
explicit SystemUIResourceManagerImpl(
UIResourceProvider* ui_resource_provider);
virtual ~SystemUIResourceManagerImpl();
~SystemUIResourceManagerImpl() override;
virtual void PreloadResource(ResourceType type) override;
virtual cc::UIResourceId GetUIResourceId(ResourceType type) override;
void PreloadResource(ui::SystemUIResourceType type) override;
cc::UIResourceId GetUIResourceId(ui::SystemUIResourceType type) override;
static bool RegisterUIResources(JNIEnv* env);
private:
friend class TestSystemUIResourceManagerImpl;
class Entry;
// Start loading the resource bitmap. virtual for testing.
virtual void BuildResource(ResourceType type);
virtual void BuildResource(ui::SystemUIResourceType type);
Entry* GetEntry(ResourceType type);
void OnFinishedLoadBitmap(ResourceType, SkBitmap* bitmap_holder);
Entry* GetEntry(ui::SystemUIResourceType type);
void OnFinishedLoadBitmap(ui::SystemUIResourceType, SkBitmap* bitmap_holder);
scoped_ptr<Entry> resource_map_[RESOURCE_TYPE_LAST + 1];
scoped_ptr<Entry> resource_map_[ui::SYSTEM_UI_RESOURCE_TYPE_LAST + 1];
UIResourceProvider* ui_resource_provider_;
base::WeakPtrFactory<SystemUIResourceManagerImpl> weak_factory_;

@ -20,10 +20,9 @@ class TestSystemUIResourceManagerImpl
virtual ~TestSystemUIResourceManagerImpl() {}
virtual void BuildResource(
ui::SystemUIResourceManager::ResourceType type) override {}
virtual void BuildResource(ui::SystemUIResourceType type) override {}
void SetResourceAsLoaded(ui::SystemUIResourceManager::ResourceType type) {
void SetResourceAsLoaded(ui::SystemUIResourceType type) {
SkBitmap small_bitmap;
SkCanvas canvas(small_bitmap);
small_bitmap.allocPixels(
@ -36,8 +35,7 @@ class TestSystemUIResourceManagerImpl
namespace {
const ui::SystemUIResourceManager::ResourceType TEST_RESOURCE_TYPE =
ui::SystemUIResourceManager::OVERSCROLL_GLOW;
const ui::SystemUIResourceType kTestResourceType = ui::OVERSCROLL_GLOW;
class MockUIResourceProvider : public content::UIResourceProvider {
public:
@ -100,12 +98,11 @@ class MockUIResourceProvider : public content::UIResourceProvider {
class SystemUIResourceManagerImplTest : public testing::Test {
public:
void PreloadResource(ui::SystemUIResourceManager::ResourceType type) {
void PreloadResource(ui::SystemUIResourceType type) {
ui_resource_provider_.GetSystemUIResourceManager().PreloadResource(type);
}
cc::UIResourceId GetUIResourceId(
ui::SystemUIResourceManager::ResourceType type) {
cc::UIResourceId GetUIResourceId(ui::SystemUIResourceType type) {
return ui_resource_provider_.GetSystemUIResourceManager().GetUIResourceId(
type);
}
@ -116,7 +113,7 @@ class SystemUIResourceManagerImplTest : public testing::Test {
ui_resource_provider_.LayerTreeHostReturned();
}
void SetResourceAsLoaded(ui::SystemUIResourceManager::ResourceType type) {
void SetResourceAsLoaded(ui::SystemUIResourceType type) {
ui_resource_provider_.GetSystemUIResourceManager().SetResourceAsLoaded(
type);
}
@ -130,40 +127,40 @@ class SystemUIResourceManagerImplTest : public testing::Test {
};
TEST_F(SystemUIResourceManagerImplTest, GetResourceAfterBitmapLoaded) {
SetResourceAsLoaded(TEST_RESOURCE_TYPE);
EXPECT_NE(0, GetUIResourceId(TEST_RESOURCE_TYPE));
SetResourceAsLoaded(kTestResourceType);
EXPECT_NE(0, GetUIResourceId(kTestResourceType));
}
TEST_F(SystemUIResourceManagerImplTest, GetResourceBeforeLoadBitmap) {
EXPECT_EQ(0, GetUIResourceId(TEST_RESOURCE_TYPE));
SetResourceAsLoaded(TEST_RESOURCE_TYPE);
EXPECT_NE(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_EQ(0, GetUIResourceId(kTestResourceType));
SetResourceAsLoaded(kTestResourceType);
EXPECT_NE(0, GetUIResourceId(kTestResourceType));
}
TEST_F(SystemUIResourceManagerImplTest, PreloadEnsureResource) {
// Preloading the resource should trigger bitmap loading, but the actual
// resource id will not be generated until it is explicitly requested.
cc::UIResourceId first_resource_id = GetNextUIResourceId();
PreloadResource(TEST_RESOURCE_TYPE);
SetResourceAsLoaded(TEST_RESOURCE_TYPE);
PreloadResource(kTestResourceType);
SetResourceAsLoaded(kTestResourceType);
EXPECT_EQ(first_resource_id, GetNextUIResourceId());
EXPECT_NE(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_NE(0, GetUIResourceId(kTestResourceType));
EXPECT_NE(first_resource_id, GetNextUIResourceId());
}
TEST_F(SystemUIResourceManagerImplTest, ResetLayerTreeHost) {
EXPECT_EQ(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_EQ(0, GetUIResourceId(kTestResourceType));
LayerTreeHostCleared();
EXPECT_EQ(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_EQ(0, GetUIResourceId(kTestResourceType));
LayerTreeHostReturned();
EXPECT_EQ(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_EQ(0, GetUIResourceId(kTestResourceType));
SetResourceAsLoaded(TEST_RESOURCE_TYPE);
EXPECT_NE(0, GetUIResourceId(TEST_RESOURCE_TYPE));
SetResourceAsLoaded(kTestResourceType);
EXPECT_NE(0, GetUIResourceId(kTestResourceType));
LayerTreeHostCleared();
EXPECT_EQ(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_EQ(0, GetUIResourceId(kTestResourceType));
LayerTreeHostReturned();
EXPECT_NE(0, GetUIResourceId(TEST_RESOURCE_TYPE));
EXPECT_NE(0, GetUIResourceId(kTestResourceType));
}
} // namespace content

@ -34,7 +34,7 @@
#include "content/browser/android/edge_effect.h"
#include "content/browser/android/edge_effect_l.h"
#include "content/browser/android/in_process/synchronous_compositor_impl.h"
#include "content/browser/android/overscroll_glow.h"
#include "content/browser/android/overscroll_controller_android.h"
#include "content/browser/devtools/render_view_devtools_agent_host.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
@ -88,9 +88,6 @@ const int kUndefinedOutputSurfaceId = -1;
// V1 saw errors of ~0.065 between computed window and content widths.
const float kMobileViewportWidthEpsilon = 0.15f;
// Used for conditional creation of EdgeEffect types for overscroll.
const int kKitKatMR2SDKVersion = 19;
static const char kAsyncReadBackString[] = "Compositing.CopyFromSurfaceTime";
// Sends an acknowledgement to the renderer of a processed IME event.
@ -138,72 +135,6 @@ ui::LatencyInfo CreateLatencyInfo(const blink::WebInputEvent& event) {
return latency_info;
}
OverscrollGlow::DisplayParameters CreateOverscrollDisplayParameters(
const cc::CompositorFrameMetadata& frame_metadata) {
const float scale_factor =
frame_metadata.page_scale_factor * frame_metadata.device_scale_factor;
// Compute the size and offsets for each edge, where each effect is sized to
// the viewport and offset by the distance of each viewport edge to the
// respective content edge.
OverscrollGlow::DisplayParameters params;
params.size = gfx::ScaleSize(
frame_metadata.scrollable_viewport_size, scale_factor);
params.edge_offsets[OverscrollGlow::EDGE_TOP] =
-frame_metadata.root_scroll_offset.y() * scale_factor;
params.edge_offsets[OverscrollGlow::EDGE_LEFT] =
-frame_metadata.root_scroll_offset.x() * scale_factor;
params.edge_offsets[OverscrollGlow::EDGE_BOTTOM] =
(frame_metadata.root_layer_size.height() -
frame_metadata.root_scroll_offset.y() -
frame_metadata.scrollable_viewport_size.height()) *
scale_factor;
params.edge_offsets[OverscrollGlow::EDGE_RIGHT] =
(frame_metadata.root_layer_size.width() -
frame_metadata.root_scroll_offset.x() -
frame_metadata.scrollable_viewport_size.width()) *
scale_factor;
return params;
}
bool UseEdgeEffectL() {
static bool use_edge_effect_l =
base::android::BuildInfo::GetInstance()->sdk_int() > kKitKatMR2SDKVersion;
return use_edge_effect_l;
}
scoped_ptr<EdgeEffectBase> CreateEdgeEffect(
ui::SystemUIResourceManager* resource_manager,
float device_scale_factor) {
DCHECK(resource_manager);
if (UseEdgeEffectL())
return scoped_ptr<EdgeEffectBase>(new EdgeEffectL(resource_manager));
return scoped_ptr<EdgeEffectBase>(
new EdgeEffect(resource_manager, device_scale_factor));
}
scoped_ptr<OverscrollGlow> CreateOverscrollEffect(
ContentViewCore* content_view_core) {
DCHECK(content_view_core);
ui::WindowAndroidCompositor* compositor =
content_view_core->GetWindowAndroid()->GetCompositor();
DCHECK(compositor);
ui::SystemUIResourceManager* system_resource_manager =
&compositor->GetSystemUIResourceManager();
if (UseEdgeEffectL())
EdgeEffectL::PreloadResources(system_resource_manager);
else
EdgeEffect::PreloadResources(system_resource_manager);
return make_scoped_ptr(
new OverscrollGlow(base::Bind(&CreateEdgeEffect,
system_resource_manager,
content_view_core->GetDpiScale())));
}
scoped_ptr<TouchSelectionController> CreateSelectionController(
TouchSelectionControllerClient* client,
ContentViewCore* content_view_core) {
@ -217,6 +148,19 @@ scoped_ptr<TouchSelectionController> CreateSelectionController(
touch_slop_pixels / content_view_core->GetDpiScale()));
}
scoped_ptr<OverscrollControllerAndroid> CreateOverscrollController(
ContentViewCore* content_view_core) {
DCHECK(content_view_core);
ui::WindowAndroid* window = content_view_core->GetWindowAndroid();
DCHECK(window);
ui::WindowAndroidCompositor* compositor = window->GetCompositor();
DCHECK(compositor);
return make_scoped_ptr(new OverscrollControllerAndroid(
content_view_core->GetWebContents(),
compositor,
content_view_core->GetDpiScale()));
}
ui::GestureProvider::Config CreateGestureProviderConfig() {
ui::GestureProvider::Config config = ui::DefaultGestureProviderConfig();
config.disable_click_delay =
@ -273,7 +217,7 @@ RenderWidgetHostViewAndroid::RenderWidgetHostViewAndroid(
ime_adapter_android_(this),
cached_background_color_(SK_ColorWHITE),
last_output_surface_id_(kUndefinedOutputSurfaceId),
overscroll_effect_enabled_(
overscroll_controller_enabled_(
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableOverscrollEdgeEffect)),
gesture_provider_(CreateGestureProviderConfig(), this),
@ -475,15 +419,15 @@ void RenderWidgetHostViewAndroid::MovePluginWindows(
void RenderWidgetHostViewAndroid::Focus() {
host_->Focus();
host_->SetInputMethodActive(true);
if (overscroll_effect_)
overscroll_effect_->Enable();
if (overscroll_controller_)
overscroll_controller_->Enable();
}
void RenderWidgetHostViewAndroid::Blur() {
host_->SetInputMethodActive(false);
host_->Blur();
if (overscroll_effect_)
overscroll_effect_->Disable();
if (overscroll_controller_)
overscroll_controller_->Disable();
}
bool RenderWidgetHostViewAndroid::HasFocus() const {
@ -1029,10 +973,6 @@ void RenderWidgetHostViewAndroid::ComputeContentsSize(
frame_metadata.scrollable_viewport_size,
frame_metadata.device_scale_factor * frame_metadata.page_scale_factor));
if (overscroll_effect_) {
overscroll_effect_->UpdateDisplayParameters(
CreateOverscrollDisplayParameters(frame_metadata));
}
}
void RenderWidgetHostViewAndroid::InternalSwapCompositorFrame(
@ -1231,6 +1171,9 @@ void RenderWidgetHostViewAndroid::OnFrameMetadataUpdated(
if (!content_view_core_)
return;
if (overscroll_controller_)
overscroll_controller_->OnFrameMetadataUpdated(frame_metadata);
if (selection_controller_) {
selection_controller_->OnSelectionBoundsChanged(
frame_metadata.selection_start, frame_metadata.selection_end);
@ -1267,8 +1210,8 @@ void RenderWidgetHostViewAndroid::AttachLayers() {
return;
content_view_core_->AttachLayer(layer_);
if (overscroll_effect_)
overscroll_effect_->Enable();
if (overscroll_controller_)
overscroll_controller_->Enable();
layer_->SetHideLayerAndSubtree(!is_showing_);
}
@ -1280,8 +1223,8 @@ void RenderWidgetHostViewAndroid::RemoveLayers() {
return;
content_view_core_->RemoveLayer(layer_);
if (overscroll_effect_)
overscroll_effect_->Disable();
if (overscroll_controller_)
overscroll_controller_->Disable();
}
void RenderWidgetHostViewAndroid::RequestVSyncUpdate(uint32 requests) {
@ -1340,8 +1283,11 @@ void RenderWidgetHostViewAndroid::SendBeginFrame(base::TimeTicks frame_time,
}
bool RenderWidgetHostViewAndroid::Animate(base::TimeTicks frame_time) {
bool needs_animate =
overscroll_effect_ ? overscroll_effect_->Animate(frame_time) : false;
bool needs_animate = false;
if (overscroll_controller_) {
needs_animate |= overscroll_controller_->Animate(
frame_time, content_view_core_->GetLayer().get());
}
if (selection_controller_)
needs_animate |= selection_controller_->Animate(frame_time);
return needs_animate;
@ -1392,12 +1338,8 @@ void RenderWidgetHostViewAndroid::ProcessAckedTouchEvent(
void RenderWidgetHostViewAndroid::GestureEventAck(
const blink::WebGestureEvent& event,
InputEventAckState ack_result) {
// The overscroll effect requires an explicit release signal that may not be
// sent from the renderer compositor.
if (event.type == blink::WebInputEvent::GestureScrollEnd ||
event.type == blink::WebInputEvent::GestureFlingStart) {
DidOverscroll(DidOverscrollParams());
}
if (overscroll_controller_)
overscroll_controller_->OnGestureEventAck(event, ack_result);
if (content_view_core_)
content_view_core_->OnGestureEventAck(event, ack_result);
@ -1418,6 +1360,13 @@ InputEventAckState RenderWidgetHostViewAndroid::FilterInputEvent(
}
}
if (overscroll_controller_ &&
blink::WebInputEvent::isGestureEventType(input_event.type) &&
overscroll_controller_->WillHandleGestureEvent(
static_cast<const blink::WebGestureEvent&>(input_event))) {
return INPUT_EVENT_ACK_STATE_CONSUMED;
}
if (content_view_core_ &&
content_view_core_->FilterInputEvent(input_event))
return INPUT_EVENT_ACK_STATE_CONSUMED;
@ -1502,8 +1451,8 @@ void RenderWidgetHostViewAndroid::SendMouseWheelEvent(
void RenderWidgetHostViewAndroid::SendGestureEvent(
const blink::WebGestureEvent& event) {
// Sending a gesture that may trigger overscroll should resume the effect.
if (overscroll_effect_)
overscroll_effect_->Enable();
if (overscroll_controller_)
overscroll_controller_->Enable();
if (host_)
host_->ForwardGestureEventWithLatencyInfo(event, CreateLatencyInfo(event));
@ -1555,23 +1504,8 @@ void RenderWidgetHostViewAndroid::DidOverscroll(
if (!content_view_core_ || !layer_.get() || !is_showing_)
return;
const float device_scale_factor = content_view_core_->GetDpiScale();
if (overscroll_effect_ &&
overscroll_effect_->OnOverscrolled(
content_view_core_->GetLayer().get(),
base::TimeTicks::Now(),
gfx::ScaleVector2d(params.accumulated_overscroll,
device_scale_factor),
gfx::ScaleVector2d(params.latest_overscroll_delta,
device_scale_factor),
gfx::ScaleVector2d(params.current_fling_velocity,
device_scale_factor),
gfx::ScaleVector2d(
params.causal_event_viewport_point.OffsetFromOrigin(),
device_scale_factor))) {
SetNeedsAnimate();
}
if (overscroll_controller_)
overscroll_controller_->OnOverscrolled(params);
}
void RenderWidgetHostViewAndroid::DidStopFlinging() {
@ -1586,7 +1520,7 @@ void RenderWidgetHostViewAndroid::SetContentViewCore(
bool resize = false;
if (content_view_core != content_view_core_) {
overscroll_effect_.reset();
overscroll_controller_.reset();
selection_controller_.reset();
ReleaseLocksOnSurface();
resize = true;
@ -1617,9 +1551,10 @@ void RenderWidgetHostViewAndroid::SetContentViewCore(
if (!selection_controller_)
selection_controller_ = CreateSelectionController(this, content_view_core_);
if (overscroll_effect_enabled_ && !overscroll_effect_ &&
content_view_core_->GetWindowAndroid()->GetCompositor())
overscroll_effect_ = CreateOverscrollEffect(content_view_core_);
if (overscroll_controller_enabled_ && !overscroll_controller_ &&
content_view_core_->GetWindowAndroid()->GetCompositor()) {
overscroll_controller_ = CreateOverscrollController(content_view_core_);
}
}
void RenderWidgetHostViewAndroid::RunAckCallbacks() {
@ -1640,15 +1575,15 @@ void RenderWidgetHostViewAndroid::OnCompositingDidCommit() {
void RenderWidgetHostViewAndroid::OnAttachCompositor() {
DCHECK(content_view_core_);
if (overscroll_effect_enabled_ && !overscroll_effect_)
overscroll_effect_ = CreateOverscrollEffect(content_view_core_);
if (overscroll_controller_enabled_ && !overscroll_controller_)
overscroll_controller_ = CreateOverscrollController(content_view_core_);
}
void RenderWidgetHostViewAndroid::OnDetachCompositor() {
DCHECK(content_view_core_);
DCHECK(!using_synchronous_compositor_);
RunAckCallbacks();
overscroll_effect_.reset();
overscroll_controller_.reset();
}
void RenderWidgetHostViewAndroid::OnVSync(base::TimeTicks frame_time,

@ -50,7 +50,7 @@ class WebMouseEvent;
namespace content {
class ContentViewCoreImpl;
class OverscrollGlow;
class OverscrollControllerAndroid;
class RenderWidgetHost;
class RenderWidgetHostImpl;
struct DidOverscrollParams;
@ -210,6 +210,17 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
virtual void SelectRange(float x1, float y1, float x2, float y2) override;
virtual void LongPress(base::TimeTicks time, float x, float y) override;
// TouchSelectionControllerClient implementation.
virtual bool SupportsAnimation() const override;
virtual void SetNeedsAnimate() override;
virtual void MoveCaret(const gfx::PointF& position) override;
virtual void MoveRangeSelectionExtent(const gfx::PointF& extent) override;
virtual void SelectBetweenCoordinates(const gfx::PointF& base,
const gfx::PointF& extent) override;
virtual void OnSelectionEvent(SelectionEventType event,
const gfx::PointF& anchor_position) override;
virtual scoped_ptr<TouchHandleDrawable> CreateDrawable() override;
// Non-virtual methods
void SetContentViewCore(ContentViewCoreImpl* content_view_core);
SkColor GetCachedBackgroundColor() const;
@ -263,17 +274,6 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
const TextSurroundingSelectionCallback& callback);
private:
// TouchSelectionControllerClient implementation.
virtual bool SupportsAnimation() const override;
virtual void SetNeedsAnimate() override;
virtual void MoveCaret(const gfx::PointF& position) override;
virtual void MoveRangeSelectionExtent(const gfx::PointF& extent) override;
virtual void SelectBetweenCoordinates(const gfx::PointF& base,
const gfx::PointF& extent) override;
virtual void OnSelectionEvent(SelectionEventType event,
const gfx::PointF& anchor_position) override;
virtual scoped_ptr<TouchHandleDrawable> CreateDrawable() override;
void RunAckCallbacks();
void DestroyDelegatedContent();
@ -372,9 +372,9 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
std::queue<base::Closure> ack_callbacks_;
const bool overscroll_effect_enabled_;
// Used to render overscroll overlays.
scoped_ptr<OverscrollGlow> overscroll_effect_;
// Used to control and render overscroll-related effects.
const bool overscroll_controller_enabled_;
scoped_ptr<OverscrollControllerAndroid> overscroll_controller_;
// Provides gesture synthesis given a stream of touch events (derived from
// Android MotionEvent's) and touch event acks.

@ -276,6 +276,7 @@
'browser/accessibility/browser_accessibility_state_impl.h',
'browser/accessibility/browser_accessibility_win.cc',
'browser/accessibility/browser_accessibility_win.h',
'browser/android/animation_utils.h',
'browser/android/browser_jni_registrar.cc',
'browser/android/browser_jni_registrar.h',
'browser/android/browser_startup_controller.cc',
@ -326,6 +327,10 @@
'browser/android/load_url_params.h',
'browser/android/overscroll_glow.cc',
'browser/android/overscroll_glow.h',
'browser/android/overscroll_controller_android.cc',
'browser/android/overscroll_controller_android.h',
'browser/android/overscroll_refresh.cc',
'browser/android/overscroll_refresh.h',
'browser/android/popup_item_type_list.h',
'browser/android/popup_touch_handle_drawable.cc',
'browser/android/popup_touch_handle_drawable.h',

@ -38,6 +38,7 @@
'public/android/java/src/org/chromium/content/browser/TouchEventSynthesizer.java',
'public/android/java/src/org/chromium/content/browser/TracingControllerAndroid.java',
'public/android/java/src/org/chromium/content/browser/VibrationProvider.java',
'public/android/java/src/org/chromium/content/browser/UIResources.java',
'public/android/java/src/org/chromium/content/browser/WebContentsObserver.java',
'public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java',
'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java',

@ -932,6 +932,7 @@
'browser/android/java/gin_java_method_invocation_helper_unittest.cc',
'browser/android/java/java_type_unittest.cc',
'browser/android/java/jni_helper_unittest.cc',
'browser/android/overscroll_refresh_unittest.cc',
'browser/android/system_ui_resource_manager_impl_unittest.cc',
'browser/renderer_host/input/motion_event_android_unittest.cc',
'renderer/java/gin_java_bridge_value_converter_unittest.cc',

Binary file not shown.

After

(image error) Size: 5.6 KiB

Binary file not shown.

After

(image error) Size: 5.4 KiB

Binary file not shown.

After

(image error) Size: 4.4 KiB

Binary file not shown.

After

(image error) Size: 4.3 KiB

Binary file not shown.

After

(image error) Size: 6.8 KiB

Binary file not shown.

After

(image error) Size: 6.6 KiB

Binary file not shown.

After

(image error) Size: 10 KiB

Binary file not shown.

After

(image error) Size: 9.8 KiB

Binary file not shown.

After

(image error) Size: 14 KiB

Binary file not shown.

After

(image error) Size: 13 KiB

@ -0,0 +1,84 @@
// 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.
package org.chromium.content.browser;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.content.R;
import org.chromium.ui.base.SystemUIResourceType;
/**
* Helper class for retrieving both system and embedded UI-related resources.
*/
@JNINamespace("content")
class UIResources {
private static Bitmap getResourceBitmap(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
if (reqWidth > 0 && reqHeight > 0) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
}
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(res, resId, options);
}
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
@SuppressWarnings("unused")
@CalledByNative
private static Bitmap getBitmap(
Context context, int uiResourceId, int reqWidth, int reqHeight) {
Resources res = null;
int resId = 0;
if (uiResourceId == SystemUIResourceType.OVERSCROLL_EDGE) {
res = Resources.getSystem();
resId = res.getIdentifier("android:drawable/overscroll_edge", null, null);
} else if (uiResourceId == SystemUIResourceType.OVERSCROLL_GLOW) {
res = Resources.getSystem();
resId = res.getIdentifier("android:drawable/overscroll_glow", null, null);
} else if (uiResourceId == SystemUIResourceType.OVERSCROLL_REFRESH_IDLE) {
res = context.getResources();
resId = R.drawable.refresh_gray;
} else if (uiResourceId == SystemUIResourceType.OVERSCROLL_REFRESH_ACTIVE) {
res = context.getResources();
resId = R.drawable.refresh_blue;
} else {
assert false;
}
if (res == null) return null;
if (resId == 0) return null;
return getResourceBitmap(res, resId, reqWidth, reqHeight);
}
}

@ -48,7 +48,7 @@ class CONTENT_EXPORT ContentViewCore {
virtual base::android::ScopedJavaLocalRef<jobject> GetJavaObject() = 0;
virtual ui::ViewAndroid* GetViewAndroid() const = 0;
virtual ui::WindowAndroid* GetWindowAndroid() const = 0;
virtual scoped_refptr<cc::Layer> GetLayer() const = 0;
virtual const scoped_refptr<cc::Layer>& GetLayer() const = 0;
virtual void ShowPastePopup(int x, int y) = 0;
// Request a scaled content readback. The result is passed through the

@ -6,11 +6,13 @@ java_cpp_enum("java_enums_srcjar") {
sources = [
"../base/page_transition_types.h",
"../base/window_open_disposition.h",
"../base/android/system_ui_resource_type.h",
"../gfx/android/java_bitmap.h",
]
outputs = [
"org/chromium/ui/WindowOpenDisposition.java",
"org/chromium/ui/base/PageTransition.java",
"org/chromium/ui/base/SystemUIResourceType.java",
"org/chromium/ui/gfx/BitmapFormat.java",
]
}

@ -4,9 +4,7 @@
package org.chromium.ui.gfx;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
@ -24,57 +22,6 @@ public class BitmapHelper {
return Bitmap.createBitmap(width, height, bitmapConfig);
}
/**
* Decode and sample down a bitmap resource to the requested width and height.
*
* @param name The resource name of the bitmap to decode.
* @param reqWidth The requested width of the resulting bitmap.
* @param reqHeight The requested height of the resulting bitmap.
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions.
* that are equal to or greater than the requested width and height.
*/
@CalledByNative
private static Bitmap decodeDrawableResource(String name,
int reqWidth,
int reqHeight) {
Resources res = Resources.getSystem();
int resId = res.getIdentifier(name, null, null);
if (resId == 0) return null;
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(res, resId, options);
}
// http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
private static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth,
int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
/**
* Provides a matching integer constant for the Bitmap.Config value passed.
*

@ -23,6 +23,14 @@
},
'includes': [ '../../build/android/java_cpp_enum.gypi' ],
},
{
'target_name': 'system_ui_resource_type_java',
'type': 'none',
'variables': {
'source_file': '../base/android/system_ui_resource_type.h',
},
'includes': [ '../../build/android/java_cpp_enum.gypi' ],
},
{
'target_name': 'window_open_disposition_java',
'type': 'none',
@ -44,6 +52,7 @@
'../../base/base.gyp:base_java',
'bitmap_format_java',
'page_transition_types_java',
'system_ui_resource_type_java',
'ui_strings_grd',
'window_open_disposition_java',
],

@ -24,6 +24,7 @@ component("base") {
"accelerators/platform_accelerator_cocoa.h",
"accelerators/platform_accelerator_cocoa.mm",
"android/system_ui_resource_manager.h",
"android/system_ui_resource_type.h",
"android/ui_base_jni_registrar.cc",
"android/ui_base_jni_registrar.h",
"android/view_android.cc",

@ -6,6 +6,7 @@
#define UI_BASE_ANDROID_SYSTEM_UI_RESOURCE_MANAGER_H_
#include "cc/resources/ui_resource_client.h"
#include "ui/base/android/system_ui_resource_type.h"
#include "ui/base/ui_base_export.h"
namespace ui {
@ -13,14 +14,6 @@ namespace ui {
// Interface for loading and accessing shared system UI resources.
class UI_BASE_EXPORT SystemUIResourceManager {
public:
enum ResourceType {
OVERSCROLL_EDGE = 0,
OVERSCROLL_GLOW,
OVERSCROLL_GLOW_L,
RESOURCE_TYPE_FIRST = OVERSCROLL_EDGE,
RESOURCE_TYPE_LAST = OVERSCROLL_GLOW_L
};
virtual ~SystemUIResourceManager() {}
// Optionally trigger bitmap loading for a given |resource|, if necessary.
@ -28,13 +21,13 @@ class UI_BASE_EXPORT SystemUIResourceManager {
// |GetUIResourceId()| will yield a valid result only after loading finishes.
// This method is particularly useful for idly loading a resource before an
// explicit cc::UIResourceId is required. Repeated calls will be ignored.
virtual void PreloadResource(ResourceType resource) = 0;
virtual void PreloadResource(SystemUIResourceType resource) = 0;
// Return the resource id associated with |resource|. If loading hasn't yet
// begun for the given |resource|, it will be triggered immediately. If
// loading is asynchronous, 0 will be returned until loading has finished, and
// the caller is responsible for re-querying until a valid id is returned.
virtual cc::UIResourceId GetUIResourceId(ResourceType resource) = 0;
virtual cc::UIResourceId GetUIResourceId(SystemUIResourceType resource) = 0;
};
} // namespace ui

@ -0,0 +1,24 @@
// 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.
#ifndef UI_BASE_ANDROID_SYSTEM_UI_RESOURCE_TYPE_H_
#define UI_BASE_ANDROID_SYSTEM_UI_RESOURCE_TYPE_H_
namespace ui {
// A Java counterpart will be generated for this enum.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.ui.base
enum SystemUIResourceType {
OVERSCROLL_EDGE,
OVERSCROLL_GLOW,
OVERSCROLL_GLOW_L,
OVERSCROLL_REFRESH_IDLE,
OVERSCROLL_REFRESH_ACTIVE,
SYSTEM_UI_RESOURCE_TYPE_FIRST = OVERSCROLL_EDGE,
SYSTEM_UI_RESOURCE_TYPE_LAST = OVERSCROLL_REFRESH_ACTIVE
};
} // namespace ui
#endif // UI_BASE_ANDROID_SYSTEM_UI_RESOURCE_TYPE_H_

@ -48,6 +48,7 @@
'accelerators/platform_accelerator_cocoa.h',
'accelerators/platform_accelerator_cocoa.mm',
'android/system_ui_resource_manager.h',
'android/system_ui_resource_type.h',
'android/ui_base_jni_registrar.cc',
'android/ui_base_jni_registrar.h',
'android/view_android.cc',

@ -9,7 +9,6 @@
#include "base/android/jni_string.h"
#include "base/logging.h"
#include "jni/BitmapHelper_jni.h"
#include "skia/ext/image_operations.h"
#include "ui/gfx/size.h"
using base::android::AttachCurrentThread;
@ -86,26 +85,6 @@ ScopedJavaLocalRef<jobject> ConvertToJavaBitmap(const SkBitmap* skbitmap) {
return jbitmap;
}
SkBitmap CreateSkBitmapFromAndroidResource(const char* name, gfx::Size size) {
DCHECK(name);
DCHECK(!size.IsEmpty());
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> jname(ConvertUTF8ToJavaString(env, name));
base::android::ScopedJavaLocalRef<jobject> jobj =
Java_BitmapHelper_decodeDrawableResource(
env, jname.obj(), size.width(), size.height());
if (jobj.is_null())
return SkBitmap();
SkBitmap bitmap = CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(jobj.obj()));
if (bitmap.isNull())
return bitmap;
return skia::ImageOperations::Resize(
bitmap, skia::ImageOperations::RESIZE_BOX, size.width(), size.height());
}
SkBitmap CreateSkBitmapFromJavaBitmap(const JavaBitmap& jbitmap) {
// TODO(jdduke): Convert to DCHECK's when sufficient data has been capture for
// crbug.com/341406.

@ -58,13 +58,6 @@ GFX_EXPORT base::android::ScopedJavaLocalRef<jobject> CreateJavaBitmap(
int height,
SkColorType color_type);
// Loads an SkBitmap from the provided drawable resource identifier (e.g.,
// android:drawable/overscroll_glow). If the resource loads successfully, it
// will be integrally scaled down, preserving aspect ratio, to a size no smaller
// than |size|. Otherwise, an empty bitmap is returned.
GFX_EXPORT SkBitmap
CreateSkBitmapFromAndroidResource(const char* name, gfx::Size size);
// Converts |skbitmap| to a Java-backed bitmap (android.graphics.Bitmap).
// Note: |skbitmap| is assumed to be non-null, non-empty and one of RGBA_8888 or
// RGB_565 formats.