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