1 /* 2 * Copyright 2018 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 androidx.leanback.widget.picker; 18 19 20 import static androidx.test.espresso.Espresso.onView; 21 import static androidx.test.espresso.action.ViewActions.pressKey; 22 import static androidx.test.espresso.matcher.ViewMatchers.withId; 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import android.view.KeyEvent; 26 import android.view.View; 27 28 import androidx.leanback.test.R; 29 import androidx.leanback.testutils.PollingCheck; 30 import androidx.recyclerview.widget.RecyclerView; 31 import androidx.test.ext.junit.runners.AndroidJUnit4; 32 import androidx.test.filters.LargeTest; 33 import androidx.test.rule.ActivityTestRule; 34 35 import org.jspecify.annotations.NonNull; 36 import org.junit.Before; 37 import org.junit.Rule; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.ExecutionException; 43 import java.util.concurrent.Future; 44 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.TimeoutException; 46 47 @RunWith(AndroidJUnit4.class) 48 public class PinPickerTest { 49 @Rule 50 public final ActivityTestRule<PinPickerActivity> mActivityTestRule; 51 PinPickerTest()52 public PinPickerTest() { 53 mActivityTestRule = new ActivityTestRule<>(PinPickerActivity.class); 54 } 55 56 private PinPicker mPinPicker; 57 58 @Before setUp()59 public void setUp() { 60 mPinPicker = mActivityTestRule.getActivity().findViewById(R.id.test_picker); 61 } 62 waitStable()63 private void waitStable() { 64 PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() { 65 @Override 66 public boolean canProceed() { 67 for (int i = 0; i < 4; i++) { 68 if (mPinPicker.mColumnViews.get(i).getScrollState() 69 != RecyclerView.SCROLL_STATE_IDLE) { 70 return false; 71 } 72 } 73 return true; 74 } 75 }); 76 } 77 waitPin(final String pin)78 private void waitPin(final String pin) { 79 PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() { 80 @Override 81 public boolean canProceed() { 82 return pin.equals(mPinPicker.getPin()); 83 } 84 }); 85 } 86 87 @Test 88 @LargeTest keyInputTest()89 public void keyInputTest() throws Exception { 90 final CompletableFuture<String> futurePin = new CompletableFuture<>(); 91 getInstrumentation().runOnMainSync(new Runnable() { 92 @Override 93 public void run() { 94 mPinPicker.resetPin(); 95 mPinPicker.setOnClickListener(new View.OnClickListener() { 96 @Override 97 public void onClick(View v) { 98 futurePin.complete(mPinPicker.getPin()); 99 } 100 }); 101 } 102 }); 103 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_1)); 104 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_2)); 105 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_3)); 106 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_4)); 107 108 waitPin("1234"); 109 110 getInstrumentation().runOnMainSync(new Runnable() { 111 @Override 112 public void run() { 113 mPinPicker.resetPin(); 114 } 115 }); 116 waitPin("0000"); 117 } 118 119 @Test 120 @LargeTest dpadInputTest()121 public void dpadInputTest() { 122 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 123 waitStable(); 124 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_CENTER)); 125 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 126 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 127 waitStable(); 128 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_CENTER)); 129 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 130 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 131 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 132 waitStable(); 133 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_CENTER)); 134 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 135 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 136 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 137 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN)); 138 waitStable(); 139 onView(withId(R.id.test_picker)).perform(pressKey(KeyEvent.KEYCODE_DPAD_CENTER)); 140 waitPin("1234"); 141 } 142 143 private static class CompletableFuture<V> implements Future<V> { 144 145 private volatile V mValue; 146 private CountDownLatch mLatch = new CountDownLatch(1); 147 complete(V value)148 public void complete(V value) { 149 mValue = value; 150 mLatch.countDown(); 151 } 152 153 @Override cancel(boolean mayInterruptIfRunning)154 public boolean cancel(boolean mayInterruptIfRunning) { 155 throw new UnsupportedOperationException(); 156 } 157 158 @Override isCancelled()159 public boolean isCancelled() { 160 throw new UnsupportedOperationException(); 161 } 162 163 @Override isDone()164 public boolean isDone() { 165 return mLatch.getCount() == 0; 166 } 167 168 @Override get()169 public V get() throws InterruptedException, ExecutionException { 170 mLatch.await(); 171 return mValue; 172 } 173 174 @Override get(long timeout, @NonNull TimeUnit unit)175 public V get(long timeout, @NonNull TimeUnit unit) 176 throws InterruptedException, ExecutionException, TimeoutException { 177 mLatch.await(timeout, unit); 178 return mValue; 179 } 180 } 181 } 182