1 /* 2 * Copyright (C) 2022 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 */ 16 17 package com.android.internal.widget; 18 19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 21 import android.content.Context; 22 23 import androidx.test.annotation.UiThreadTest; 24 25 import android.util.AttributeSet; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.Toolbar; 30 31 32 import static org.hamcrest.Matchers.contains; 33 import static org.hamcrest.Matchers.hasSize; 34 import static org.hamcrest.Matchers.is; 35 import static org.junit.Assert.assertThat; 36 import static org.mockito.ArgumentMatchers.any; 37 import static org.mockito.ArgumentMatchers.eq; 38 import static org.mockito.Mockito.atLeastOnce; 39 import static org.mockito.Mockito.mock; 40 import static org.mockito.Mockito.never; 41 import static org.mockito.Mockito.verify; 42 import static org.mockito.Mockito.verifyNoMoreInteractions; 43 44 import androidx.test.InstrumentationRegistry; 45 import androidx.test.filters.SmallTest; 46 import androidx.test.rule.UiThreadTestRule; 47 48 import org.junit.Before; 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 import org.junit.runners.Parameterized; 53 import org.mockito.ArgumentCaptor; 54 import org.mockito.ArgumentMatchers; 55 import org.mockito.Captor; 56 import org.mockito.Mock; 57 import org.mockito.MockitoAnnotations; 58 59 import com.android.internal.R; 60 61 import java.util.Arrays; 62 import java.util.Collection; 63 import java.util.List; 64 65 @RunWith(Parameterized.class) 66 @SmallTest 67 public class LockPatternViewTest { 68 69 @Rule 70 public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule(); 71 72 private final int mViewSize; 73 private final float mDefaultError; 74 private final float mDot1x; 75 private final float mDot1y; 76 private final float mDot2x; 77 private final float mDot2y; 78 private final float mDot3x; 79 private final float mDot3y; 80 private final float mDot5x; 81 private final float mDot5y; 82 private final float mDot7x; 83 private final float mDot7y; 84 private final float mDot9x; 85 private final float mDot9y; 86 87 private Context mContext; 88 private LockPatternView mLockPatternView; 89 @Mock 90 private LockPatternView.OnPatternListener mPatternListener; 91 @Captor 92 private ArgumentCaptor<List<LockPatternView.Cell>> mCellsArgumentCaptor; 93 LockPatternViewTest(int viewSize)94 public LockPatternViewTest(int viewSize) { 95 mViewSize = viewSize; 96 float cellSize = viewSize / 3f; 97 mDefaultError = cellSize * 0.2f; 98 mDot1x = cellSize / 2f; 99 mDot1y = cellSize / 2f; 100 mDot2x = cellSize + mDot1x; 101 mDot2y = mDot1y; 102 mDot3x = cellSize + mDot2x; 103 mDot3y = mDot1y; 104 // dot4 is skipped as redundant 105 mDot5x = cellSize + mDot1x; 106 mDot5y = cellSize + mDot1y; 107 // dot6 is skipped as redundant 108 mDot7x = mDot1x; 109 mDot7y = cellSize * 2 + mDot1y; 110 // dot8 is skipped as redundant 111 mDot9x = cellSize * 2 + mDot7x; 112 mDot9y = mDot7y; 113 } 114 115 @Parameterized.Parameters primeNumbers()116 public static Collection primeNumbers() { 117 return Arrays.asList(192, 512, 768, 1024); 118 } 119 120 @Before setUp()121 public void setUp() throws Exception { 122 MockitoAnnotations.initMocks(this); 123 mContext = InstrumentationRegistry.getContext(); 124 mLockPatternView = new LockPatternView(mContext, null); 125 int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize, 126 View.MeasureSpec.EXACTLY); 127 int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(mViewSize, 128 View.MeasureSpec.EXACTLY); 129 mLockPatternView.measure(widthMeasureSpec, heightMeasureSpec); 130 mLockPatternView.layout(0, 0, mLockPatternView.getMeasuredWidth(), 131 mLockPatternView.getMeasuredHeight()); 132 } 133 134 @UiThreadTest 135 @Test downStartsPattern()136 public void downStartsPattern() { 137 mLockPatternView.setOnPatternListener(mPatternListener); 138 mLockPatternView.onTouchEvent( 139 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1)); 140 verify(mPatternListener).onPatternStart(); 141 } 142 143 @UiThreadTest 144 @Test up_completesPattern()145 public void up_completesPattern() { 146 mLockPatternView.setOnPatternListener(mPatternListener); 147 mLockPatternView.onTouchEvent( 148 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, mDot1x, mDot1y, 1)); 149 mLockPatternView.onTouchEvent( 150 MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mDot1x, mDot1y, 1)); 151 verify(mPatternListener).onPatternDetected(any()); 152 } 153 154 @UiThreadTest 155 @Test moveToDot_hitsDot()156 public void moveToDot_hitsDot() { 157 mLockPatternView.setOnPatternListener(mPatternListener); 158 mLockPatternView.onTouchEvent( 159 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); 160 mLockPatternView.onTouchEvent( 161 MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, mDot1x, mDot1y, 1)); 162 verify(mPatternListener).onPatternStart(); 163 } 164 165 @UiThreadTest 166 @Test moveOutside_doesNotHitsDot()167 public void moveOutside_doesNotHitsDot() { 168 mLockPatternView.setOnPatternListener(mPatternListener); 169 mLockPatternView.onTouchEvent( 170 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); 171 mLockPatternView.onTouchEvent( 172 MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 2f, 2f, 1)); 173 verify(mPatternListener, never()).onPatternStart(); 174 } 175 176 @UiThreadTest 177 @Test moveAlongTwoDots_hitsTwo()178 public void moveAlongTwoDots_hitsTwo() { 179 mLockPatternView.setOnPatternListener(mPatternListener); 180 mLockPatternView.onTouchEvent( 181 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); 182 makeMove(mDot1x, mDot1y, mDot2x, mDot2y, 6); 183 mLockPatternView.onTouchEvent( 184 MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot2x, mDot2y, 1)); 185 186 verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); 187 List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); 188 assertThat(patternCells, hasSize(2)); 189 assertThat(patternCells, 190 contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(0, 1))); 191 } 192 193 @UiThreadTest 194 @Test moveAlongTwoDotsDiagonally_hitsTwo()195 public void moveAlongTwoDotsDiagonally_hitsTwo() { 196 mLockPatternView.setOnPatternListener(mPatternListener); 197 mLockPatternView.onTouchEvent( 198 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); 199 makeMove(mDot1x, mDot1y, mDot5x, mDot5y, 6); 200 mLockPatternView.onTouchEvent( 201 MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, mDot5x, mDot5y, 1)); 202 203 verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); 204 List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); 205 assertThat(patternCells, hasSize(2)); 206 assertThat(patternCells, 207 contains(LockPatternView.Cell.of(0, 0), LockPatternView.Cell.of(1, 1))); 208 } 209 210 @UiThreadTest 211 @Test moveAlongZPattern_hitsDots()212 public void moveAlongZPattern_hitsDots() { 213 mLockPatternView.setOnPatternListener(mPatternListener); 214 mLockPatternView.onTouchEvent( 215 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1f, 1f, 1)); 216 makeMove(mDot1x, mDot1y, mDot3x + mDefaultError, mDot3y, 10); 217 makeMove(mDot3x - mDefaultError, mDot3y, mDot7x, mDot7y, 10); 218 makeMove(mDot7x, mDot7y - mDefaultError, mDot9x, mDot9y - mDefaultError, 10); 219 mLockPatternView.onTouchEvent( 220 MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, mViewSize - mDefaultError, 221 mViewSize - mDefaultError, 1)); 222 223 verify(mPatternListener).onPatternDetected(mCellsArgumentCaptor.capture()); 224 List<LockPatternView.Cell> patternCells = mCellsArgumentCaptor.getValue(); 225 assertThat(patternCells, hasSize(7)); 226 assertThat(patternCells, 227 contains(LockPatternView.Cell.of(0, 0), 228 LockPatternView.Cell.of(0, 1), 229 LockPatternView.Cell.of(0, 2), 230 LockPatternView.Cell.of(1, 1), 231 LockPatternView.Cell.of(2, 0), 232 LockPatternView.Cell.of(2, 1), 233 LockPatternView.Cell.of(2, 2))); 234 } 235 makeMove(float xFrom, float yFrom, float xTo, float yTo, int numberOfSteps)236 private void makeMove(float xFrom, float yFrom, float xTo, float yTo, int numberOfSteps) { 237 for (int i = 0; i < numberOfSteps; i++) { 238 float progress = i / (numberOfSteps - 1f); 239 float rest = 1f - progress; 240 mLockPatternView.onTouchEvent( 241 MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 242 /* x= */ xFrom * rest + xTo * progress, 243 /* y= */ yFrom * rest + yTo * progress, 244 1)); 245 } 246 } 247 } 248