1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.android_webview.test;
6
7import android.content.Context;
8import android.graphics.Rect;
9import android.test.InstrumentationTestCase;
10import android.test.suitebuilder.annotation.SmallTest;
11import android.view.View.MeasureSpec;
12import android.view.View;
13import android.widget.OverScroller;
14
15import org.chromium.android_webview.AwScrollOffsetManager;
16import org.chromium.base.test.util.Feature;
17
18public class AwScrollOffsetManagerTest extends InstrumentationTestCase {
19    private static class TestScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate {
20        private int mOverScrollDeltaX;
21        private int mOverScrollDeltaY;
22        private int mOverScrollCallCount;
23        private int mScrollX;
24        private int mScrollY;
25        private int mNativeScrollX;
26        private int mNativeScrollY;
27        private int mInvalidateCount;
28
29        public int getOverScrollDeltaX() {
30            return mOverScrollDeltaX;
31        }
32
33        public int getOverScrollDeltaY() {
34            return mOverScrollDeltaY;
35        }
36
37        public int getOverScrollCallCount() {
38            return mOverScrollCallCount;
39        }
40
41        public int getScrollX() {
42            return mScrollX;
43        }
44
45        public int getScrollY() {
46            return mScrollY;
47        }
48
49        public int getNativeScrollX() {
50            return mNativeScrollX;
51        }
52
53        public int getNativeScrollY() {
54            return mNativeScrollY;
55        }
56
57        public int getInvalidateCount() {
58            return mInvalidateCount;
59        }
60
61        @Override
62        public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY,
63                int scrollRangeX, int scrollRangeY, boolean isTouchEvent) {
64            mOverScrollDeltaX = deltaX;
65            mOverScrollDeltaY = deltaY;
66            mOverScrollCallCount += 1;
67        }
68
69        @Override
70        public void scrollContainerViewTo(int x, int y) {
71            mScrollX = x;
72            mScrollY = y;
73        }
74
75        @Override
76        public void scrollNativeTo(int x, int y) {
77            mNativeScrollX = x;
78            mNativeScrollY = y;
79        }
80
81        @Override
82        public int getContainerViewScrollX() {
83            return mScrollX;
84        }
85
86        @Override
87        public int getContainerViewScrollY() {
88            return mScrollY;
89        }
90
91        @Override
92        public void invalidate() {
93            mInvalidateCount += 1;
94        }
95    }
96
97    private void simulateScrolling(AwScrollOffsetManager offsetManager,
98            TestScrollOffsetManagerDelegate delegate, int scrollX, int scrollY) {
99        // Scrolling is a two-phase action. First we ask the manager to scroll
100        int callCount = delegate.getOverScrollCallCount();
101        offsetManager.scrollContainerViewTo(scrollX, scrollY);
102        // The manager then asks the delegate to overscroll the view.
103        assertEquals(callCount + 1, delegate.getOverScrollCallCount());
104        assertEquals(scrollX, delegate.getOverScrollDeltaX() + delegate.getScrollX());
105        assertEquals(scrollY, delegate.getOverScrollDeltaY() + delegate.getScrollY());
106        // As a response to that the menager expects the view to call back with the new scroll.
107        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
108    }
109
110    private void simlateOverScrollPropagation(AwScrollOffsetManager offsetManager,
111            TestScrollOffsetManagerDelegate delegate) {
112        assertTrue(delegate.getOverScrollCallCount() > 0);
113
114        offsetManager.onContainerViewOverScrolled(
115                delegate.getOverScrollDeltaX() + delegate.getScrollX(),
116                delegate.getOverScrollDeltaY() + delegate.getScrollY(), false, false);
117    }
118
119    @SmallTest
120    @Feature({"AndroidWebView"})
121    public void testWhenContentSizeMatchesView() {
122        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
123        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
124        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
125
126        final int width = 132;
127        final int height = 212;
128        final int scrollX = 11;
129        final int scrollY = 13;
130
131        offsetManager.setMaxScrollOffset(0, 0);
132        offsetManager.setContainerViewSize(width, height);
133
134        assertEquals(width, offsetManager.computeHorizontalScrollRange());
135        assertEquals(height, offsetManager.computeVerticalScrollRange());
136
137        // Since the view size and contents size are equal no scrolling should be possible.
138        assertEquals(0, offsetManager.computeMaximumHorizontalScrollOffset());
139        assertEquals(0, offsetManager.computeMaximumVerticalScrollOffset());
140
141        // Scrolling should generate overscroll but not update the scroll offset.
142        simulateScrolling(offsetManager, delegate, scrollX, scrollY);
143        assertEquals(scrollX, delegate.getOverScrollDeltaX());
144        assertEquals(scrollY, delegate.getOverScrollDeltaY());
145        assertEquals(0, delegate.getScrollX());
146        assertEquals(0, delegate.getScrollY());
147        assertEquals(0, delegate.getNativeScrollX());
148        assertEquals(0, delegate.getNativeScrollY());
149
150        // Scrolling to 0,0 should result in no deltas.
151        simulateScrolling(offsetManager, delegate, 0, 0);
152        assertEquals(0, delegate.getOverScrollDeltaX());
153        assertEquals(0, delegate.getOverScrollDeltaY());
154
155        // Negative scrolling should result in negative deltas but no scroll offset update.
156        simulateScrolling(offsetManager, delegate, -scrollX, -scrollY);
157        assertEquals(-scrollX, delegate.getOverScrollDeltaX());
158        assertEquals(-scrollY, delegate.getOverScrollDeltaY());
159        assertEquals(0, delegate.getScrollX());
160        assertEquals(0, delegate.getScrollY());
161        assertEquals(0, delegate.getNativeScrollX());
162        assertEquals(0, delegate.getNativeScrollY());
163    }
164
165    private static final int VIEW_WIDTH = 211;
166    private static final int VIEW_HEIGHT = 312;
167    private static final int MAX_HORIZONTAL_OFFSET = 757;
168    private static final int MAX_VERTICAL_OFFSET = 127;
169    private static final int CONTENT_WIDTH = VIEW_WIDTH + MAX_HORIZONTAL_OFFSET;
170    private static final int CONTENT_HEIGHT = VIEW_HEIGHT + MAX_VERTICAL_OFFSET;
171
172    @SmallTest
173    @Feature({"AndroidWebView"})
174    public void testScrollRangeAndMaxOffset() {
175        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
176        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
177        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
178
179        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
180        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
181
182        assertEquals(CONTENT_WIDTH, offsetManager.computeHorizontalScrollRange());
183        assertEquals(CONTENT_HEIGHT, offsetManager.computeVerticalScrollRange());
184
185        assertEquals(MAX_HORIZONTAL_OFFSET, offsetManager.computeMaximumHorizontalScrollOffset());
186        assertEquals(MAX_VERTICAL_OFFSET, offsetManager.computeMaximumVerticalScrollOffset());
187
188        // Scrolling beyond the maximum should be clamped.
189        final int scrollX = MAX_HORIZONTAL_OFFSET + 10;
190        final int scrollY = MAX_VERTICAL_OFFSET + 11;
191
192        simulateScrolling(offsetManager, delegate, scrollX, scrollY);
193        assertEquals(scrollX, delegate.getOverScrollDeltaX());
194        assertEquals(scrollY, delegate.getOverScrollDeltaY());
195        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
196        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
197        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
198        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
199
200        // Scrolling to negative coordinates should be clamped back to 0,0.
201        simulateScrolling(offsetManager, delegate, -scrollX, -scrollY);
202        assertEquals(0, delegate.getScrollX());
203        assertEquals(0, delegate.getScrollY());
204        assertEquals(0, delegate.getNativeScrollX());
205        assertEquals(0, delegate.getNativeScrollY());
206
207        // The onScrollChanged method is callable by third party code and should also be clamped
208        offsetManager.onContainerViewScrollChanged(scrollX, scrollY);
209        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
210        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
211
212        offsetManager.onContainerViewScrollChanged(-scrollX, -scrollY);
213        assertEquals(0, delegate.getNativeScrollX());
214        assertEquals(0, delegate.getNativeScrollY());
215    }
216
217    @SmallTest
218    @Feature({"AndroidWebView"})
219    public void testDelegateCanOverrideScroll() {
220        final int overrideScrollX = 10;
221        final int overrideScrollY = 10;
222
223        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() {
224            @Override
225            public int getContainerViewScrollX() {
226                return overrideScrollX;
227            }
228
229            @Override
230            public int getContainerViewScrollY() {
231                return overrideScrollY;
232            }
233        };
234        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
235        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
236
237        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
238        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
239
240        offsetManager.onContainerViewOverScrolled(0, 0, false, false);
241        assertEquals(overrideScrollX, delegate.getNativeScrollX());
242        assertEquals(overrideScrollY, delegate.getNativeScrollY());
243    }
244
245    @SmallTest
246    @Feature({"AndroidWebView"})
247    public void testDelegateOverridenScrollsDontExceedBounds() {
248        final int overrideScrollX = MAX_HORIZONTAL_OFFSET + 10;
249        final int overrideScrollY = MAX_VERTICAL_OFFSET + 20;
250        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() {
251            @Override
252            public int getContainerViewScrollX() {
253                return overrideScrollX;
254            }
255
256            @Override
257            public int getContainerViewScrollY() {
258                return overrideScrollY;
259            }
260        };
261        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
262        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
263
264        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
265        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
266
267        offsetManager.onContainerViewOverScrolled(0, 0, false, false);
268        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
269        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
270    }
271
272    @SmallTest
273    @Feature({"AndroidWebView"})
274    public void testScrollContainerViewTo() {
275        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
276        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
277        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
278
279        final int scrollX = 31;
280        final int scrollY = 41;
281
282        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
283        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
284
285        assertEquals(0, delegate.getOverScrollDeltaX());
286        assertEquals(0, delegate.getOverScrollDeltaY());
287        int callCount = delegate.getOverScrollCallCount();
288
289        offsetManager.scrollContainerViewTo(scrollX, scrollY);
290        assertEquals(callCount + 1, delegate.getOverScrollCallCount());
291        assertEquals(scrollX, delegate.getOverScrollDeltaX());
292        assertEquals(scrollY, delegate.getOverScrollDeltaY());
293    }
294
295    @SmallTest
296    @Feature({"AndroidWebView"})
297    public void testOnContainerViewOverScrolled() {
298        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
299        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
300        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
301
302        final int scrollX = 31;
303        final int scrollY = 41;
304
305        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
306        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
307
308        assertEquals(0, delegate.getScrollX());
309        assertEquals(0, delegate.getScrollY());
310        assertEquals(0, delegate.getNativeScrollX());
311        assertEquals(0, delegate.getNativeScrollY());
312
313        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
314        assertEquals(scrollX, delegate.getScrollX());
315        assertEquals(scrollY, delegate.getScrollY());
316        assertEquals(scrollX, delegate.getNativeScrollX());
317        assertEquals(scrollY, delegate.getNativeScrollY());
318    }
319
320    @SmallTest
321    @Feature({"AndroidWebView"})
322    public void testDefersScrollUntilTouchEnd() {
323        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
324        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
325        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
326
327        final int scrollX = 31;
328        final int scrollY = 41;
329
330        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
331        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
332
333        offsetManager.setProcessingTouchEvent(true);
334        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
335        assertEquals(scrollX, delegate.getScrollX());
336        assertEquals(scrollY, delegate.getScrollY());
337        assertEquals(0, delegate.getNativeScrollX());
338        assertEquals(0, delegate.getNativeScrollY());
339
340        offsetManager.setProcessingTouchEvent(false);
341        assertEquals(scrollX, delegate.getScrollX());
342        assertEquals(scrollY, delegate.getScrollY());
343        assertEquals(scrollX, delegate.getNativeScrollX());
344        assertEquals(scrollY, delegate.getNativeScrollY());
345    }
346
347    @SmallTest
348    @Feature({"AndroidWebView"})
349    public void testFlingScroll() {
350        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
351        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
352        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
353
354        offsetManager.flingScroll(0, 101);
355        assertTrue(!scroller.isFinished());
356        assertTrue(delegate.getInvalidateCount() == 1);
357        assertEquals(101, (int) scroller.getCurrVelocity());
358
359        offsetManager.flingScroll(111, 0);
360        assertTrue(!scroller.isFinished());
361        assertTrue(delegate.getInvalidateCount() == 2);
362        assertEquals(111, (int) scroller.getCurrVelocity());
363    }
364
365    @SmallTest
366    @Feature({"AndroidWebView"})
367    public void testRequestChildRectangleOnScreenDontScrollIfAlreadyThere() {
368        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
369        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
370        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
371
372        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
373        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
374
375        offsetManager.requestChildRectangleOnScreen(0, 0,
376                new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true);
377        assertEquals(0, delegate.getOverScrollDeltaX());
378        assertEquals(0, delegate.getOverScrollDeltaY());
379        assertEquals(0, delegate.getScrollX());
380        assertEquals(0, delegate.getScrollY());
381
382        offsetManager.requestChildRectangleOnScreen(3 * VIEW_WIDTH / 4, 3 * VIEW_HEIGHT / 4,
383                new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true);
384        assertEquals(0, delegate.getOverScrollDeltaX());
385        assertEquals(0, delegate.getOverScrollDeltaY());
386        assertEquals(0, delegate.getScrollX());
387        assertEquals(0, delegate.getScrollY());
388    }
389
390    @SmallTest
391    @Feature({"AndroidWebView"})
392    public void testRequestChildRectangleOnScreenScrollToBottom() {
393        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
394        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
395        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
396
397        final int rectWidth = 2;
398        final int rectHeight = 3;
399
400        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
401        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
402
403        offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth,
404                CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true);
405        simlateOverScrollPropagation(offsetManager, delegate);
406        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX());
407        assertEquals(CONTENT_HEIGHT - rectHeight - VIEW_HEIGHT / 3, delegate.getOverScrollDeltaY());
408        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
409        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
410    }
411
412    @SmallTest
413    @Feature({"AndroidWebView"})
414    public void testRequestChildRectangleOnScreenScrollToBottomLargeRect() {
415        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
416        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
417        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
418
419        final int rectWidth = VIEW_WIDTH;
420        final int rectHeight = VIEW_HEIGHT;
421
422        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
423        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
424
425        offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth,
426                CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true);
427        simlateOverScrollPropagation(offsetManager, delegate);
428        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX());
429        assertEquals(MAX_VERTICAL_OFFSET, delegate.getOverScrollDeltaY());
430        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
431        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
432    }
433
434    @SmallTest
435    @Feature({"AndroidWebView"})
436    public void testRequestChildRectangleOnScreenScrollToTop() {
437        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
438        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
439        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
440
441        final int rectWidth = 2;
442        final int rectHeight = 3;
443
444        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
445        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
446        simulateScrolling(offsetManager, delegate,
447                CONTENT_WIDTH - VIEW_WIDTH, CONTENT_HEIGHT - VIEW_HEIGHT);
448
449        offsetManager.requestChildRectangleOnScreen(0, 0,
450                new Rect(0, 0, rectWidth, rectHeight), true);
451        simlateOverScrollPropagation(offsetManager, delegate);
452        assertEquals(-CONTENT_WIDTH + VIEW_WIDTH, delegate.getOverScrollDeltaX());
453        assertEquals(-CONTENT_HEIGHT + VIEW_HEIGHT, delegate.getOverScrollDeltaY());
454        assertEquals(0, delegate.getScrollX());
455        assertEquals(0, delegate.getScrollX());
456    }
457}
458