blob: 63a68e99b788fdcfce568a319322519466721089 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.view;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Random;
public class ScrollViewCaptureHelperTest {
private FrameLayout mParent;
private ScrollView mTarget;
private LinearLayout mContent;
private WindowManager mWm;
private WindowManager.LayoutParams mWindowLayoutParams;
private static final int CHILD_VIEWS = 12;
public static final int CHILD_VIEW_HEIGHT = 300;
private static final int WINDOW_WIDTH = 800;
private static final int WINDOW_HEIGHT = 1200;
private static final int CAPTURE_HEIGHT = 600;
private Random mRandom;
private static float sDensity;
@BeforeClass
public static void setUpClass() {
sDensity = getContext().getResources().getDisplayMetrics().density;
}
@Before
@UiThreadTest
public void setUp() {
mRandom = new Random();
mParent = new FrameLayout(getContext());
mTarget = new ScrollView(getContext());
mParent.addView(mTarget, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContent = new LinearLayout(getContext());
mContent.setOrientation(LinearLayout.VERTICAL);
mTarget.addView(mContent, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
for (int i = 0; i < CHILD_VIEWS; i++) {
TextView view = new TextView(getContext());
view.setText("Child #" + i);
view.setTextColor(Color.WHITE);
view.setTextSize(30f);
view.setBackgroundColor(Color.rgb(mRandom.nextFloat(), mRandom.nextFloat(),
mRandom.nextFloat()));
mContent.addView(view, new ViewGroup.LayoutParams(MATCH_PARENT, CHILD_VIEW_HEIGHT));
}
// Window -> Parent -> Target -> Content
mWm = getContext().getSystemService(WindowManager.class);
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(WINDOW_WIDTH, WINDOW_HEIGHT,
TYPE_APPLICATION_OVERLAY, FLAG_NOT_TOUCHABLE, PixelFormat.OPAQUE);
mWindowLayoutParams.setTitle("ScrollViewCaptureHelper");
mWindowLayoutParams.gravity = Gravity.CENTER;
mWm.addView(mParent, mWindowLayoutParams);
}
@After
@UiThreadTest
public void tearDown() {
mWm.removeViewImmediate(mParent);
}
@Test
@UiThreadTest
public void onPrepareForStart() {
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
}
static void assertEmpty(Rect r) {
if (r != null && !r.isEmpty()) {
fail("Not true that " + r + " is empty");
}
}
static void assertContains(Rect parent, Rect child) {
if (!parent.contains(child)) {
fail("Not true that " + parent + " contains " + child);
}
}
static void assertRectEquals(Rect parent, Rect child) {
if (!parent.equals(child)) {
fail("Not true that " + parent + " is equal to " + child);
}
}
static Rect getVisibleRect(View v) {
Rect r = new Rect(0, 0, v.getWidth(), v.getHeight());
v.getLocalVisibleRect(r);
return r;
}
static int assertScrollToY(View v, int scrollY) {
v.scrollTo(0, scrollY);
int dest = v.getScrollY();
assertEquals(scrollY, dest);
return scrollY;
}
static void assertCapturedAreaCompletelyVisible(int startScrollY, Rect requestRect,
Rect localVisibleNow) {
Rect captured = new Rect(localVisibleNow);
captured.offset(0, -startScrollY); // make relative
if (!captured.contains(requestRect)) {
fail("Not true that all of " + requestRect + " is contained by " + captured);
}
}
static void assertCapturedAreaPartiallyVisible(int startScrollY, Rect requestRect,
Rect localVisibleNow) {
Rect captured = new Rect(localVisibleNow);
captured.offset(0, -startScrollY); // make relative
if (!Rect.intersects(captured, requestRect)) {
fail("Not true that any of " + requestRect + " intersects " + captured);
}
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromTop() {
final int startScrollY = assertScrollToY(mTarget, 0);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
// The result is an empty rectangle and no scrolling, since it
// is not possible to physically scroll further up to make the
// requested area visible at all (it doesn't exist).
assertEmpty(result);
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromTop() {
final int startScrollY = assertScrollToY(mTarget, 0);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
// Capture between y = +1200 to +1500 pixels BELOW current top
Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
WINDOW_HEIGHT + CAPTURE_HEIGHT);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
assertRectEquals(request, result);
assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromMiddle() {
final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
assertRectEquals(request, result);
assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromMiddle() {
final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
WINDOW_HEIGHT + CAPTURE_HEIGHT);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
assertRectEquals(request, result);
assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onScrollRequested_up_fromBottom() {
final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
assertRectEquals(request, result);
assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onScrollRequested_down_fromBottom() {
final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
WINDOW_HEIGHT + CAPTURE_HEIGHT);
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
// The result is an empty rectangle and no scrolling, since it
// is not possible to physically scroll further down to make the
// requested area visible at all (it doesn't exist).
assertEmpty(result);
}
@Test
@UiThreadTest
public void onScrollRequested_offTopEdge() {
final int startScrollY = assertScrollToY(mTarget, 0);
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
// Create a request which lands halfway off the top of the content
//from -1500 to -900, (starting at 1200 = -300 to +300 within the content)
int top = 0;
Rect request = new Rect(
0, top - (CAPTURE_HEIGHT / 2),
scrollBounds.width(), top + (CAPTURE_HEIGHT / 2));
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
// The result is a partial result
Rect expectedResult = new Rect(request);
expectedResult.top += 300; // top half clipped
assertRectEquals(expectedResult, result);
assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onScrollRequested_offBottomEdge() {
final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2); // 2400
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
svc.onPrepareForStart(mTarget, scrollBounds);
// Create a request which lands halfway off the bottom of the content
//from 600 to to 1200, (starting at 2400 = 3000 to 3600 within the content)
int bottom = WINDOW_HEIGHT;
Rect request = new Rect(
0, bottom - (CAPTURE_HEIGHT / 2),
scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2));
Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
Rect expectedResult = new Rect(request);
expectedResult.bottom -= 300; // bottom half clipped
assertRectEquals(expectedResult, result);
assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
}
@Test
@UiThreadTest
public void onPrepareForEnd() {
ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
svc.onPrepareForEnd(mTarget);
}
}