1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.managedprovisioning.common;
17
18import static android.support.test.InstrumentationRegistry.getTargetContext;
19
20import static org.hamcrest.Matchers.equalTo;
21import static org.hamcrest.Matchers.lessThanOrEqualTo;
22import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
23import static org.junit.Assert.assertThat;
24import static org.mockito.Matchers.any;
25import static org.mockito.Mockito.verify;
26import static org.mockito.Mockito.verifyNoMoreInteractions;
27import static org.mockito.Mockito.verifyZeroInteractions;
28
29import android.graphics.Rect;
30import android.support.test.filters.SmallTest;
31import android.view.TouchDelegate;
32import android.view.View;
33
34import org.junit.Before;
35import org.junit.Test;
36import org.mockito.ArgumentCaptor;
37import org.mockito.Captor;
38import org.mockito.Mock;
39import org.mockito.MockitoAnnotations;
40
41@SmallTest
42public class TouchTargetEnforcerTest {
43    private static final float SAMPLE_DENSITY = 2.625f;
44    private static final int TOLERANCE_PX = 2;
45    private static final int OFFSET_DEFAULT = 500;
46
47    @Mock private View mViewAncestor;
48    @Captor private ArgumentCaptor<Runnable> mArgumentCaptor;
49    private TouchTargetEnforcer mEnforcer;
50    private View mView;
51    private int mEdgeValue;
52
53    private Rect mCapturedBounds;
54    private View mCapturedTargetView;
55
56    @Before
57    public void setUp() {
58        MockitoAnnotations.initMocks(this);
59        mView = new View(getTargetContext());
60        mEdgeValue = Math.round(TouchTargetEnforcer.MIN_TARGET_DP * SAMPLE_DENSITY);
61
62        mEnforcer = new TouchTargetEnforcer(SAMPLE_DENSITY, (bounds, target) -> {
63            mCapturedBounds = bounds;
64            mCapturedTargetView = target;
65            return new TouchDelegate(mCapturedBounds, mCapturedTargetView);
66        });
67
68        mCapturedBounds = null;
69        mCapturedTargetView = null;
70    }
71
72    @Test
73    public void expansionNeededWidth() {
74        testExpansionNeeded(mEdgeValue - 1, mEdgeValue + 1);
75    }
76
77    @Test
78    public void expansionNeededHeight() {
79        testExpansionNeeded(mEdgeValue + 1, mEdgeValue - 1);
80    }
81
82    @Test
83    public void expansionNeededWidthHeight() {
84        testExpansionNeeded(mEdgeValue - 1, mEdgeValue - 1);
85    }
86
87    private void testExpansionNeeded(int width, int height) {
88        setViewDimen(width, height);
89        assertExpansionNeeded(width, height);
90    }
91
92    private void assertExpansionNeeded(int width, int height) {
93        // when
94        mEnforcer.enforce(mView, mViewAncestor);
95        mView.getViewTreeObserver().dispatchOnGlobalLayout(); // force try register Touch Delegate
96
97        // then
98        verify(mViewAncestor).post(mArgumentCaptor.capture()); // prep capture TouchDelegate args
99        mArgumentCaptor.getValue().run(); // capture args under mCapturedBounds
100        //noinspection ResultOfMethodCallIgnored
101        verify(mViewAncestor).getTouchDelegate();
102        verify(mViewAncestor).setTouchDelegate(any());
103        verifyNoMoreInteractions(mViewAncestor);
104
105        // verify end state correct
106        assertBoundsCorrect(width, mCapturedBounds.width());
107        assertBoundsCorrect(height, mCapturedBounds.height());
108    }
109
110    @Test
111    public void expansionNotNeeded() {
112        // given
113        setViewDimen(mEdgeValue + 1, mEdgeValue + 1);
114        assertThat(mView.getWidth(), greaterThanOrEqualTo(mEdgeValue));
115        assertThat(mView.getHeight(), greaterThanOrEqualTo(mEdgeValue));
116
117        // when
118        mEnforcer.enforce(mView, mViewAncestor);
119        mView.getViewTreeObserver().dispatchOnGlobalLayout(); // force UI queue to add a Runnable
120
121        // then
122        verifyZeroInteractions(mViewAncestor);
123    }
124
125    @Test
126    public void doesNotCrashOnEdges() {
127        setViewDimen(0, 0, 0);
128        assertExpansionNeeded(0, 0);
129    }
130
131    private void assertBoundsCorrect(int oldValue, int newValue) {
132        assertThat(newValue, greaterThanOrEqualTo(mEdgeValue));
133        if (oldValue >= mEdgeValue) {
134            assertThat(newValue, equalTo(oldValue));
135        } else {
136            assertThat(mCapturedBounds.width(), lessThanOrEqualTo(mEdgeValue + TOLERANCE_PX));
137        }
138    }
139
140    private void setViewDimen(int width, int height) {
141        setViewDimen(width, height, OFFSET_DEFAULT);
142    }
143
144    private void setViewDimen(int width, int height, int offset) {
145        mView.setLeft(offset);
146        mView.setRight(offset + width);
147        mView.setTop(offset);
148        mView.setBottom(offset + height);
149    }
150}