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 */
16
17package androidx.leanback.widget.picker;
18
19import static org.hamcrest.CoreMatchers.is;
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.junit.Assert.assertEquals;
22
23import android.content.Context;
24import android.content.Intent;
25import android.support.test.InstrumentationRegistry;
26import android.support.test.filters.LargeTest;
27import android.support.test.rule.ActivityTestRule;
28import android.support.test.runner.AndroidJUnit4;
29import android.view.KeyEvent;
30import android.view.View;
31import android.view.ViewGroup;
32
33import androidx.leanback.test.R;
34
35import org.junit.Before;
36import org.junit.Rule;
37import org.junit.Test;
38import org.junit.runner.RunWith;
39
40import java.util.Arrays;
41import java.util.List;
42
43@LargeTest
44@RunWith(AndroidJUnit4.class)
45public class TimePickerTest {
46
47    private static final String TAG = "TimePickerTest";
48    private static final long TRANSITION_LENGTH = 1000;
49    private static final long UPDATE_LENGTH = 1000;
50
51    View mViewAbove;
52    TimePicker mTimePicker12HourView;
53    TimePicker mTimePicker24HourView;
54    View mViewBelow;
55
56    @Rule
57    public ActivityTestRule<TimePickerActivity> mActivityTestRule =
58            new ActivityTestRule<>(TimePickerActivity.class, false, false);
59    private TimePickerActivity mActivity;
60    private Context mContext;
61
62    @Before
63    public void setUp() throws Exception {
64        mContext = InstrumentationRegistry.getTargetContext();
65    }
66
67    public void initActivity(Intent intent) throws Throwable {
68        mActivity = mActivityTestRule.launchActivity(intent);
69        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
70        mTimePicker12HourView = (TimePicker) mActivity.findViewById(R.id.time_picker12);
71        mTimePicker12HourView.setActivatedVisibleItemCount(3);
72        mTimePicker12HourView.setOnClickListener(new View.OnClickListener() {
73            @Override
74            public void onClick(View v) {
75                mTimePicker12HourView.setActivated(!mTimePicker12HourView.isActivated());
76            }
77        });
78
79        if (intent.getIntExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
80                R.layout.timepicker_with_other_widgets) == R.layout.timepicker_with_other_widgets) {
81            mViewAbove = mActivity.findViewById(R.id.above_picker);
82            mViewBelow = mActivity.findViewById(R.id.below_picker);
83            mTimePicker24HourView = (TimePicker) mActivity.findViewById(R.id.time_picker24);
84            mTimePicker24HourView.setActivatedVisibleItemCount(3);
85            mTimePicker24HourView.setOnClickListener(new View.OnClickListener() {
86                @Override
87                public void onClick(View v) {
88                    mTimePicker24HourView.setActivated(!mTimePicker24HourView.isActivated());
89                }
90            });
91        } else if (intent.getIntExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
92                R.layout.timepicker_with_other_widgets) == R.layout.timepicker_alone) {
93            // A layout with only a TimePicker widget that is initially activated.
94            mActivityTestRule.runOnUiThread(new Runnable() {
95                @Override
96                public void run() {
97                    mTimePicker12HourView.setActivated(true);
98                }
99            });
100            Thread.sleep(500);
101        }
102    }
103
104    @Test
105    public void testSetHourIn24hFormat() throws Throwable {
106        Intent intent = new Intent();
107        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
108                R.layout.timepicker_with_other_widgets);
109        initActivity(intent);
110
111        mActivityTestRule.runOnUiThread(new Runnable() {
112            @Override
113            public void run() {
114                mTimePicker24HourView.setHour(0);
115            }
116        });
117        Thread.sleep(UPDATE_LENGTH);
118        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
119                mTimePicker24HourView.getHour(), is(0));
120
121        mActivityTestRule.runOnUiThread(new Runnable() {
122            @Override
123            public void run() {
124                mTimePicker24HourView.setHour(11);
125            }
126        });
127        Thread.sleep(UPDATE_LENGTH);
128        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
129                mTimePicker24HourView.getHour(), is(11));
130
131        mActivityTestRule.runOnUiThread(new Runnable() {
132            @Override
133            public void run() {
134                mTimePicker24HourView.setHour(12);
135            }
136        });
137        Thread.sleep(UPDATE_LENGTH);
138        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
139                mTimePicker24HourView.getHour(), is(12));
140
141        mActivityTestRule.runOnUiThread(new Runnable() {
142            @Override
143            public void run() {
144                mTimePicker24HourView.setHour(13);
145            }
146        });
147        Thread.sleep(UPDATE_LENGTH);
148        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
149                mTimePicker24HourView.getHour(), is(13));
150
151        mActivityTestRule.runOnUiThread(new Runnable() {
152            @Override
153            public void run() {
154                mTimePicker24HourView.setHour(23);
155            }
156        });
157        Thread.sleep(UPDATE_LENGTH);
158        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
159                mTimePicker24HourView.getHour(), is(23));
160    }
161
162    @Test
163    public void testSetHourIn12hFormat() throws Throwable {
164        Intent intent = new Intent();
165        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
166                R.layout.timepicker_with_other_widgets);
167        initActivity(intent);
168        mActivityTestRule.runOnUiThread(new Runnable() {
169            @Override
170            public void run() {
171                mTimePicker12HourView.setHour(0);
172            }
173        });
174        Thread.sleep(UPDATE_LENGTH);
175        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
176                mTimePicker12HourView.getHour(), is(0));
177
178        mActivityTestRule.runOnUiThread(new Runnable() {
179            @Override
180            public void run() {
181                mTimePicker12HourView.setHour(11);
182            }
183        });
184        Thread.sleep(UPDATE_LENGTH);
185        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
186                mTimePicker12HourView.getHour(), is(11));
187
188        mActivityTestRule.runOnUiThread(new Runnable() {
189            @Override
190            public void run() {
191                mTimePicker12HourView.setHour(12);
192            }
193        });
194        Thread.sleep(UPDATE_LENGTH);
195        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
196                mTimePicker12HourView.getHour(), is(12));
197
198        mActivityTestRule.runOnUiThread(new Runnable() {
199            @Override
200            public void run() {
201                mTimePicker12HourView.setHour(13);
202            }
203        });
204        Thread.sleep(UPDATE_LENGTH);
205        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
206                mTimePicker12HourView.getHour(), is(13));
207
208        mActivityTestRule.runOnUiThread(new Runnable() {
209            @Override
210            public void run() {
211                mTimePicker12HourView.setHour(23);
212            }
213        });
214        Thread.sleep(UPDATE_LENGTH);
215        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
216                mTimePicker12HourView.getHour(), is(23));
217    }
218
219    @Test
220    public void testSetMinuteIn24hFormat() throws Throwable {
221        Intent intent = new Intent();
222        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
223                R.layout.timepicker_with_other_widgets);
224        initActivity(intent);
225        mActivityTestRule.runOnUiThread(new Runnable() {
226            @Override
227            public void run() {
228                mTimePicker24HourView.setMinute(0);
229            }
230        });
231        Thread.sleep(UPDATE_LENGTH);
232        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
233                mTimePicker24HourView.getMinute(), is(0));
234
235        mActivityTestRule.runOnUiThread(new Runnable() {
236            @Override
237            public void run() {
238                mTimePicker24HourView.setMinute(11);
239            }
240        });
241        Thread.sleep(UPDATE_LENGTH);
242        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
243                mTimePicker24HourView.getMinute(), is(11));
244
245        mActivityTestRule.runOnUiThread(new Runnable() {
246            @Override
247            public void run() {
248                mTimePicker24HourView.setMinute(59);
249            }
250        });
251        Thread.sleep(UPDATE_LENGTH);
252        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
253                mTimePicker24HourView.getMinute(), is(59));
254    }
255
256    @Test
257    public void testSetMinuteIn12hFormat() throws Throwable {
258        Intent intent = new Intent();
259        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
260                R.layout.timepicker_with_other_widgets);
261        initActivity(intent);
262        mActivityTestRule.runOnUiThread(new Runnable() {
263            @Override
264            public void run() {
265                mTimePicker12HourView.setMinute(0);
266            }
267        });
268        Thread.sleep(UPDATE_LENGTH);
269        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
270                mTimePicker12HourView.getMinute(), is(0));
271
272        mActivityTestRule.runOnUiThread(new Runnable() {
273            @Override
274            public void run() {
275                mTimePicker12HourView.setMinute(11);
276            }
277        });
278        Thread.sleep(UPDATE_LENGTH);
279        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
280                mTimePicker12HourView.getMinute(), is(11));
281
282        mActivityTestRule.runOnUiThread(new Runnable() {
283            @Override
284            public void run() {
285                mTimePicker12HourView.setMinute(59);
286            }
287        });
288        Thread.sleep(UPDATE_LENGTH);
289        assertThat("TimePicker in 24-hour mode returns a different hour in getHour()",
290                mTimePicker12HourView.getMinute(), is(59));
291
292    }
293
294    @Test
295    public void testAmToPmTransition() throws Throwable {
296        Intent intent = new Intent();
297        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
298                R.layout.timepicker_with_other_widgets);
299        initActivity(intent);
300        mActivityTestRule.runOnUiThread(new Runnable() {
301            @Override
302            public void run() {
303                mTimePicker12HourView.setHour(0);
304                mTimePicker12HourView.setMinute(47);
305            }
306        });
307        Thread.sleep(UPDATE_LENGTH);
308        assertThat("TimePicker in 12-hour mode returns a different hour in getHour()",
309                mTimePicker12HourView.getHour(), is(0));
310        assertThat("TimePicker in 12-hour mode returns a different hour in getMinute()",
311                mTimePicker12HourView.getMinute(), is(47));
312
313        // traverse to the AM/PM column of 12 hour TimePicker widget
314        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
315        Thread.sleep(TRANSITION_LENGTH);
316        // Click once to activate
317        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
318        Thread.sleep(TRANSITION_LENGTH);
319        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
320        Thread.sleep(TRANSITION_LENGTH);
321        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
322        Thread.sleep(TRANSITION_LENGTH);
323        // scroll down to PM value
324        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
325        Thread.sleep(TRANSITION_LENGTH);
326        // Click now to deactivate
327        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
328        Thread.sleep(TRANSITION_LENGTH);
329
330        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
331                mTimePicker12HourView.getHour(), is(12));
332        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
333                mTimePicker12HourView.getMinute(), is(47));
334    }
335
336    @Test
337    public void testPmToAmTransition() throws Throwable {
338        Intent intent = new Intent();
339        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
340                R.layout.timepicker_with_other_widgets);
341        initActivity(intent);
342        mActivityTestRule.runOnUiThread(new Runnable() {
343            @Override
344            public void run() {
345                mTimePicker12HourView.setHour(12);
346                mTimePicker12HourView.setMinute(47);
347            }
348        });
349        Thread.sleep(UPDATE_LENGTH);
350        assertThat("TimePicker in 12-hour mode returns a different hour in getHour()",
351                mTimePicker12HourView.getHour(), is(12));
352        assertThat("TimePicker in 12-hour mode returns a different hour in getMinute()",
353                mTimePicker12HourView.getMinute(), is(47));
354
355        // traverse to the AM/PM column of 12 hour TimePicker widget
356        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
357        Thread.sleep(TRANSITION_LENGTH);
358        // Click once to activate
359        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
360        Thread.sleep(TRANSITION_LENGTH);
361        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
362        Thread.sleep(TRANSITION_LENGTH);
363        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
364        Thread.sleep(TRANSITION_LENGTH);
365        // scroll down to PM value
366        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
367        Thread.sleep(TRANSITION_LENGTH);
368        // Click now to deactivate
369        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
370        Thread.sleep(TRANSITION_LENGTH);
371
372        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
373                mTimePicker12HourView.getHour(), is(0));
374        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
375                mTimePicker12HourView.getMinute(), is(47));
376    }
377
378    @Test
379    public void test12To24HourFormatTransition() throws Throwable {
380        Intent intent = new Intent();
381        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
382                R.layout.timepicker_with_other_widgets);
383        initActivity(intent);
384        mActivityTestRule.runOnUiThread(new Runnable() {
385            @Override
386            public void run() {
387                mTimePicker12HourView.setHour(14);
388                mTimePicker12HourView.setMinute(47);
389            }
390        });
391        Thread.sleep(UPDATE_LENGTH);
392        assertThat("TimePicker should be in 12-hour format.", mTimePicker12HourView.is24Hour(),
393                is(false));
394        mActivityTestRule.runOnUiThread(new Runnable() {
395            @Override
396            public void run() {
397                mTimePicker12HourView.setIs24Hour(true);
398            }
399        });
400        Thread.sleep(UPDATE_LENGTH);
401        assertThat("TimePicker should now be in 24-hour format.", mTimePicker12HourView.is24Hour(),
402                is(true));
403        // The hour and minute should not be changed.
404        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
405                mTimePicker12HourView.getHour(), is(14));
406        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
407                mTimePicker12HourView.getMinute(), is(47));
408    }
409
410    @Test
411    public void test24To12HourFormatTransition() throws Throwable {
412        Intent intent = new Intent();
413        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
414                R.layout.timepicker_with_other_widgets);
415        initActivity(intent);
416        mActivityTestRule.runOnUiThread(new Runnable() {
417            @Override
418            public void run() {
419                mTimePicker24HourView.setHour(14);
420                mTimePicker24HourView.setMinute(47);
421            }
422        });
423        Thread.sleep(UPDATE_LENGTH);
424        assertThat("TimePicker should be in 12-hour format.", mTimePicker24HourView.is24Hour(),
425                is(true));
426        mActivityTestRule.runOnUiThread(new Runnable() {
427            @Override
428            public void run() {
429                mTimePicker24HourView.setIs24Hour(false);
430            }
431        });
432        Thread.sleep(UPDATE_LENGTH);
433        assertThat("TimePicker should now be in 24-hour format.", mTimePicker24HourView.is24Hour(),
434                is(false));
435        // The hour and minute should not be changed.
436        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
437                mTimePicker24HourView.getHour(), is(14));
438        assertThat("TimePicker in 24-hour mode returns a different hour in getHour() returns",
439                mTimePicker24HourView.getMinute(), is(47));
440    }
441
442    @Test
443    public void testInitiallyActiveTimePicker()
444            throws Throwable {
445        Intent intent = new Intent();
446        intent.putExtra(TimePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
447                R.layout.timepicker_alone);
448        initActivity(intent);
449
450        mActivityTestRule.runOnUiThread(new Runnable() {
451            @Override
452            public void run() {
453                mTimePicker12HourView.setHour(14);
454                mTimePicker12HourView.setMinute(47);
455            }
456        });
457        Thread.sleep(UPDATE_LENGTH);
458
459        ViewGroup mTimePickerInnerView = (ViewGroup) mTimePicker12HourView.findViewById(
460                R.id.picker);
461
462        assertThat("The first column of TimePicker should initially hold focus",
463                mTimePickerInnerView.getChildAt(0).hasFocus(), is(true));
464
465        // focus on first column
466        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
467        Thread.sleep(TRANSITION_LENGTH);
468        assertThat("The first column of TimePicker should still hold focus after scrolling down",
469                mTimePickerInnerView.getChildAt(0).hasFocus(), is(true));
470
471        // focus on second column
472        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
473        Thread.sleep(TRANSITION_LENGTH);
474        assertThat("The second column of TimePicker should hold focus after scrolling right",
475                mTimePickerInnerView.getChildAt(2).hasFocus(), is(true));
476
477        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
478        Thread.sleep(TRANSITION_LENGTH);
479        assertThat("The second column of TimePicker should still hold focus after scrolling down",
480                mTimePickerInnerView.getChildAt(2).hasFocus(), is(true));
481
482        // focus on third column
483        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
484        Thread.sleep(TRANSITION_LENGTH);
485        assertThat("The third column of TimePicker should hold focus after scrolling right",
486                mTimePickerInnerView.getChildAt(3).hasFocus(), is(true));
487
488        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
489        Thread.sleep(TRANSITION_LENGTH);
490        assertThat("The third column of TimePicker should still hold focus after scrolling down",
491                mTimePickerInnerView.getChildAt(3).hasFocus(), is(true));
492    }
493
494    @Test
495    public void testExtractSeparatorsForDifferentLocales() throws Throwable {
496        // A typical time pattern for different locales in 12-hour format
497        TimePicker timePicker = new TimePicker(mContext, null) {
498            @Override
499            String getBestHourMinutePattern() {
500                return "h:mm a";
501            }
502        };
503        List<CharSequence> actualSeparators = timePicker.extractSeparators();
504        List<String> expectedSeparators = Arrays.asList(new String[] {"", ":", "", ""});
505        assertEquals(expectedSeparators, actualSeparators);
506
507        // time pattern for ja_JP in 12 hour format
508        timePicker = new TimePicker(mContext, null) {
509            @Override
510            String getBestHourMinutePattern() {
511                return "aK:mm";
512            }
513
514            @Override
515            public boolean is24Hour() {
516                return false;
517            }
518        };
519        actualSeparators = timePicker.extractSeparators();
520        expectedSeparators = Arrays.asList(new String[] {"", "", ":", ""});
521        assertEquals(expectedSeparators, actualSeparators);
522
523        // time pattern for fr_CA in 24 hour format
524        timePicker = new TimePicker(mContext, null) {
525            @Override
526            String getBestHourMinutePattern() {
527                return "HH 'h' mm";
528            }
529
530            @Override
531            public boolean is24Hour() {
532                return true;
533            }
534        };
535        actualSeparators = timePicker.extractSeparators();
536        expectedSeparators = Arrays.asList(new String[] {"", "h", ""});
537        assertEquals(expectedSeparators, actualSeparators);
538
539        // time pattern for hsb_DE in 24 hour format
540        timePicker = new TimePicker(mContext, null) {
541            @Override
542            String getBestHourMinutePattern() {
543                return "H:mm 'hodz'";
544            }
545
546            @Override
547            public boolean is24Hour() {
548                return true;
549            }
550        };
551        actualSeparators = timePicker.extractSeparators();
552        expectedSeparators = Arrays.asList(new String[] {"", ":", "hodz"});
553        assertEquals(expectedSeparators, actualSeparators);
554
555        // time pattern for ko_KR in 12 hour format
556        timePicker = new TimePicker(mContext, null) {
557            @Override
558            String getBestHourMinutePattern() {
559                return "a h:mm";
560            }
561
562            @Override
563            public boolean is24Hour() {
564                return false;
565            }
566        };
567        actualSeparators = timePicker.extractSeparators();
568        expectedSeparators = Arrays.asList(new String[] {"", "", ":", ""});
569        assertEquals(expectedSeparators, actualSeparators);
570
571        // time pattern for fa_IR in 24 hour format
572        timePicker = new TimePicker(mContext, null) {
573            @Override
574            String getBestHourMinutePattern() {
575                return "H:mm";
576            }
577
578            @Override
579            public boolean is24Hour() {
580                return true;
581            }
582        };
583        actualSeparators = timePicker.extractSeparators();
584        expectedSeparators = Arrays.asList(new String[] {"", ":", ""});
585        assertEquals(expectedSeparators, actualSeparators);
586    }
587
588    private void sendKeys(int ...keys) {
589        for (int i = 0; i < keys.length; i++) {
590            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
591        }
592    }
593}
594