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