| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.widget.cts; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| |
| import android.app.Activity; |
| import android.app.Instrumentation; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.os.Parcelable; |
| import android.util.AttributeSet; |
| import android.view.KeyEvent; |
| import android.view.View; |
| import android.view.autofill.AutofillValue; |
| import android.widget.TimePicker; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.annotation.UiThreadTest; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.rule.ActivityTestRule; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.compatibility.common.util.CtsKeyEventUtil; |
| import com.android.compatibility.common.util.CtsTouchUtils; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.GregorianCalendar; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Test {@link TimePicker}. |
| */ |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class TimePickerTest { |
| private Instrumentation mInstrumentation; |
| private Activity mActivity; |
| private TimePicker mTimePicker; |
| |
| @Rule |
| public ActivityTestRule<TimePickerCtsActivity> mActivityRule = |
| new ActivityTestRule<>(TimePickerCtsActivity.class); |
| |
| @Before |
| public void setup() { |
| mInstrumentation = InstrumentationRegistry.getInstrumentation(); |
| mActivity = mActivityRule.getActivity(); |
| mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock); |
| } |
| |
| @Test |
| public void testConstructors() { |
| AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); |
| assertNotNull(attrs); |
| |
| new TimePicker(mActivity); |
| |
| new TimePicker(mActivity, attrs); |
| new TimePicker(mActivity, null); |
| |
| new TimePicker(mActivity, attrs, 0); |
| new TimePicker(mActivity, null, 0); |
| new TimePicker(mActivity, attrs, 0); |
| new TimePicker(mActivity, null, android.R.attr.timePickerStyle); |
| new TimePicker(mActivity, null, 0, android.R.style.Widget_Material_TimePicker); |
| new TimePicker(mActivity, null, 0, android.R.style.Widget_Material_Light_TimePicker); |
| } |
| |
| @Test(expected=NullPointerException.class) |
| public void testConstructorNullContext1() { |
| new TimePicker(null); |
| } |
| |
| @Test(expected=NullPointerException.class) |
| public void testConstructorNullContext2() { |
| AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); |
| new TimePicker(null, attrs); |
| } |
| |
| @Test(expected=NullPointerException.class) |
| public void testConstructorNullContext3() { |
| AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); |
| new TimePicker(null, attrs, 0); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testSetEnabled() { |
| assertTrue(mTimePicker.isEnabled()); |
| |
| mTimePicker.setEnabled(false); |
| assertFalse(mTimePicker.isEnabled()); |
| assertNull(mTimePicker.getAutofillValue()); |
| assertEquals(View.AUTOFILL_TYPE_NONE, mTimePicker.getAutofillType()); |
| |
| mTimePicker.setEnabled(true); |
| assertTrue(mTimePicker.isEnabled()); |
| assertNotNull(mTimePicker.getAutofillValue()); |
| assertEquals(View.AUTOFILL_TYPE_DATE, mTimePicker.getAutofillType()); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAutofill() { |
| mTimePicker.setEnabled(true); |
| |
| final AtomicInteger numberOfListenerCalls = new AtomicInteger(); |
| mTimePicker.setOnTimeChangedListener((v, h, m) -> numberOfListenerCalls.incrementAndGet()); |
| |
| final Calendar calendar = new GregorianCalendar(); |
| calendar.set(Calendar.HOUR_OF_DAY, 4); |
| calendar.set(Calendar.MINUTE, 20); |
| |
| final AutofillValue autofilledValue = AutofillValue.forDate(calendar.getTimeInMillis()); |
| mTimePicker.autofill(autofilledValue); |
| assertEquals(autofilledValue, mTimePicker.getAutofillValue()); |
| assertEquals(4, mTimePicker.getHour()); |
| assertEquals(20, mTimePicker.getMinute()); |
| assertEquals(1, numberOfListenerCalls.get()); |
| |
| // Make sure autofill() is ignored when value is null. |
| numberOfListenerCalls.set(0); |
| mTimePicker.autofill((AutofillValue) null); |
| assertEquals(autofilledValue, mTimePicker.getAutofillValue()); |
| assertEquals(4, mTimePicker.getHour()); |
| assertEquals(20, mTimePicker.getMinute()); |
| assertEquals(0, numberOfListenerCalls.get()); |
| |
| // Make sure autofill() is ignored when value is not a date. |
| numberOfListenerCalls.set(0); |
| mTimePicker.autofill(AutofillValue.forText("Y U NO IGNORE ME?")); |
| assertEquals(autofilledValue, mTimePicker.getAutofillValue()); |
| assertEquals(4, mTimePicker.getHour()); |
| assertEquals(20, mTimePicker.getMinute()); |
| assertEquals(0, numberOfListenerCalls.get()); |
| |
| // Make sure getAutofillValue() is reset when value is manually filled. |
| mTimePicker.autofill(autofilledValue); // 04:20 |
| mTimePicker.setHour(10); |
| calendar.setTimeInMillis(mTimePicker.getAutofillValue().getDateValue()); |
| assertEquals(10, calendar.get(Calendar.HOUR)); |
| mTimePicker.autofill(autofilledValue); // 04:20 |
| mTimePicker.setMinute(8); |
| calendar.setTimeInMillis(mTimePicker.getAutofillValue().getDateValue()); |
| assertEquals(8, calendar.get(Calendar.MINUTE)); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testSetOnTimeChangedListener() { |
| // On time change listener is notified on every call to setCurrentHour / setCurrentMinute. |
| // We want to make sure that before we register our listener, we initialize the time picker |
| // to the time that is explicitly different from the values we'll be testing for in both |
| // hour and minute. Otherwise if the test happens to run at the time that ends in |
| // "minuteForTesting" minutes, we'll get two onTimeChanged callbacks with identical values. |
| final int initialHour = 10; |
| final int initialMinute = 38; |
| final int hourForTesting = 13; |
| final int minuteForTesting = 50; |
| |
| mTimePicker.setHour(initialHour); |
| mTimePicker.setMinute(initialMinute); |
| |
| // Now register the listener |
| TimePicker.OnTimeChangedListener mockOnTimeChangeListener = |
| mock(TimePicker.OnTimeChangedListener.class); |
| mTimePicker.setOnTimeChangedListener(mockOnTimeChangeListener); |
| mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting)); |
| mTimePicker.setCurrentMinute(Integer.valueOf(minuteForTesting)); |
| // We're expecting two onTimeChanged callbacks, one with new hour and one with new |
| // hour+minute |
| verify(mockOnTimeChangeListener, times(1)).onTimeChanged( |
| mTimePicker, hourForTesting, initialMinute); |
| verify(mockOnTimeChangeListener, times(1)).onTimeChanged( |
| mTimePicker, hourForTesting, minuteForTesting); |
| |
| // set the same hour as current |
| reset(mockOnTimeChangeListener); |
| mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting)); |
| verifyZeroInteractions(mockOnTimeChangeListener); |
| |
| mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting + 1)); |
| verify(mockOnTimeChangeListener, times(1)).onTimeChanged( |
| mTimePicker, hourForTesting + 1, minuteForTesting); |
| |
| // set the same minute as current |
| reset(mockOnTimeChangeListener); |
| mTimePicker.setCurrentMinute(minuteForTesting); |
| verifyZeroInteractions(mockOnTimeChangeListener); |
| |
| reset(mockOnTimeChangeListener); |
| mTimePicker.setCurrentMinute(minuteForTesting + 1); |
| verify(mockOnTimeChangeListener, times(1)).onTimeChanged( |
| mTimePicker, hourForTesting + 1, minuteForTesting + 1); |
| |
| // change time picker mode |
| reset(mockOnTimeChangeListener); |
| mTimePicker.setIs24HourView(!mTimePicker.is24HourView()); |
| verifyZeroInteractions(mockOnTimeChangeListener); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAccessCurrentHour() { |
| // AM/PM mode |
| mTimePicker.setIs24HourView(false); |
| |
| mTimePicker.setCurrentHour(0); |
| assertEquals(Integer.valueOf(0), mTimePicker.getCurrentHour()); |
| |
| mTimePicker.setCurrentHour(12); |
| assertEquals(Integer.valueOf(12), mTimePicker.getCurrentHour()); |
| |
| mTimePicker.setCurrentHour(13); |
| assertEquals(Integer.valueOf(13), mTimePicker.getCurrentHour()); |
| |
| mTimePicker.setCurrentHour(23); |
| assertEquals(Integer.valueOf(23), mTimePicker.getCurrentHour()); |
| |
| // for 24 hour mode |
| mTimePicker.setIs24HourView(true); |
| |
| mTimePicker.setCurrentHour(0); |
| assertEquals(Integer.valueOf(0), mTimePicker.getCurrentHour()); |
| |
| mTimePicker.setCurrentHour(13); |
| assertEquals(Integer.valueOf(13), mTimePicker.getCurrentHour()); |
| |
| mTimePicker.setCurrentHour(23); |
| assertEquals(Integer.valueOf(23), mTimePicker.getCurrentHour()); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAccessHour() { |
| // AM/PM mode |
| mTimePicker.setIs24HourView(false); |
| |
| mTimePicker.setHour(0); |
| assertEquals(0, mTimePicker.getHour()); |
| |
| mTimePicker.setHour(12); |
| assertEquals(12, mTimePicker.getHour()); |
| |
| mTimePicker.setHour(13); |
| assertEquals(13, mTimePicker.getHour()); |
| |
| mTimePicker.setHour(23); |
| assertEquals(23, mTimePicker.getHour()); |
| |
| // for 24 hour mode |
| mTimePicker.setIs24HourView(true); |
| |
| mTimePicker.setHour(0); |
| assertEquals(0, mTimePicker.getHour()); |
| |
| mTimePicker.setHour(13); |
| assertEquals(13, mTimePicker.getHour()); |
| |
| mTimePicker.setHour(23); |
| assertEquals(23, mTimePicker.getHour()); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAccessIs24HourView() { |
| assertFalse(mTimePicker.is24HourView()); |
| |
| mTimePicker.setIs24HourView(true); |
| assertTrue(mTimePicker.is24HourView()); |
| |
| mTimePicker.setIs24HourView(false); |
| assertFalse(mTimePicker.is24HourView()); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAccessCurrentMinute() { |
| mTimePicker.setCurrentMinute(0); |
| assertEquals(Integer.valueOf(0), mTimePicker.getCurrentMinute()); |
| |
| mTimePicker.setCurrentMinute(12); |
| assertEquals(Integer.valueOf(12), mTimePicker.getCurrentMinute()); |
| |
| mTimePicker.setCurrentMinute(33); |
| assertEquals(Integer.valueOf(33), mTimePicker.getCurrentMinute()); |
| |
| mTimePicker.setCurrentMinute(59); |
| assertEquals(Integer.valueOf(59), mTimePicker.getCurrentMinute()); |
| } |
| |
| @UiThreadTest |
| @Test |
| public void testAccessMinute() { |
| mTimePicker.setMinute(0); |
| assertEquals(0, mTimePicker.getMinute()); |
| |
| mTimePicker.setMinute(12); |
| assertEquals(12, mTimePicker.getMinute()); |
| |
| mTimePicker.setMinute(33); |
| assertEquals(33, mTimePicker.getMinute()); |
| |
| mTimePicker.setMinute(59); |
| assertEquals(59, mTimePicker.getMinute()); |
| } |
| |
| @Test |
| public void testGetBaseline() { |
| assertEquals(-1, mTimePicker.getBaseline()); |
| } |
| |
| @Test |
| public void testOnSaveInstanceStateAndOnRestoreInstanceState() { |
| MyTimePicker source = new MyTimePicker(mActivity); |
| MyTimePicker dest = new MyTimePicker(mActivity); |
| int expectHour = (dest.getCurrentHour() + 10) % 24; |
| int expectMinute = (dest.getCurrentMinute() + 10) % 60; |
| source.setCurrentHour(expectHour); |
| source.setCurrentMinute(expectMinute); |
| |
| Parcelable p = source.onSaveInstanceState(); |
| dest.onRestoreInstanceState(p); |
| |
| assertEquals(Integer.valueOf(expectHour), dest.getCurrentHour()); |
| assertEquals(Integer.valueOf(expectMinute), dest.getCurrentMinute()); |
| } |
| |
| private boolean isWatch() { |
| return (mActivity.getResources().getConfiguration().uiMode |
| & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH; |
| } |
| |
| @Test |
| public void testKeyboardTabTraversalModeClock() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock); |
| |
| mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(false)); |
| mInstrumentation.waitForIdleSync(); |
| verifyTimePickerKeyboardTraversal( |
| true /* goForward */, |
| false /* is24HourView */); |
| verifyTimePickerKeyboardTraversal( |
| false /* goForward */, |
| false /* is24HourView */); |
| |
| mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(true)); |
| mInstrumentation.waitForIdleSync(); |
| verifyTimePickerKeyboardTraversal( |
| true /* goForward */, |
| true /* is24HourView */); |
| verifyTimePickerKeyboardTraversal( |
| false /* goForward */, |
| true /* is24HourView */); |
| } |
| |
| @Test |
| public void testKeyboardTabTraversalModeSpinner() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| // Hide timepicker_clock so that timepicker_spinner would be visible. |
| mActivityRule.runOnUiThread(() -> |
| mActivity.findViewById(R.id.timepicker_clock).setVisibility(View.GONE)); |
| mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_spinner); |
| |
| mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(false)); |
| mInstrumentation.waitForIdleSync(); |
| |
| // Spinner time-picker doesn't explicitly define a focus order. Just make sure inputs |
| // are able to be traversed (added to focusables). |
| ArrayList<View> focusables = new ArrayList<>(); |
| mTimePicker.addFocusables(focusables, View.FOCUS_FORWARD); |
| assertTrue(focusables.contains(mTimePicker.getHourView())); |
| assertTrue(focusables.contains(mTimePicker.getMinuteView())); |
| assertTrue(focusables.contains(mTimePicker.getAmView())); |
| focusables.clear(); |
| |
| mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(true)); |
| mInstrumentation.waitForIdleSync(); |
| mTimePicker.addFocusables(focusables, View.FOCUS_FORWARD); |
| assertTrue(focusables.contains(mTimePicker.getHourView())); |
| assertTrue(focusables.contains(mTimePicker.getMinuteView())); |
| } |
| |
| @Test |
| public void testKeyboardInputModeClockAmPm() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| final int initialHour = 6; |
| final int initialMinute = 59; |
| prepareForKeyboardInput(initialHour, initialMinute, false /* is24hFormat */, |
| true /* isClockMode */); |
| |
| // Input valid hour. |
| assertEquals(initialHour, mTimePicker.getHour()); |
| CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mTimePicker.getHourView()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_0); |
| assertEquals(10, mTimePicker.getHour()); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| |
| // Input valid minute. |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(43, mTimePicker.getMinute()); |
| assertTrue(mTimePicker.getAmView().hasFocus()); |
| |
| // Accepting AM changes nothing. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); |
| assertEquals(10, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| |
| // Focus PM radio. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertTrue(mTimePicker.getPmView().hasFocus()); |
| // Still nothing has changed. |
| assertEquals(10, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| // Select PM and verify the hour has changed. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); |
| assertEquals(22, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| // Set AM again. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getAmView().hasFocus()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); |
| assertEquals(10, mTimePicker.getHour()); |
| |
| // Re-focus the hour view. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getHourView().hasFocus()); |
| |
| // Input an invalid value (larger than 12). |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| // Force setting the hour by moving to minute. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| // After sending 1 and 3 only 1 is accepted. |
| assertEquals(1, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| // The hour view still has focus. |
| assertTrue(mTimePicker.getHourView().hasFocus()); |
| |
| // This time send a valid hour (11). |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| // The value is valid. |
| assertEquals(11, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| |
| verifyModeClockMinuteInput(); |
| } |
| |
| @Test |
| public void testKeyboardInputModeClock24H() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| final int initialHour = 6; |
| final int initialMinute = 59; |
| prepareForKeyboardInput(initialHour, initialMinute, true /* is24hFormat */, |
| true /* isClockMode */); |
| |
| // Input valid hour. |
| assertEquals(initialHour, mTimePicker.getHour()); |
| CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mTimePicker.getHourView()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_0); |
| assertEquals(10, mTimePicker.getHour()); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| |
| // Input valid minute. |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| assertEquals(43, mTimePicker.getMinute()); |
| |
| // Re-focus the hour view. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getHourView().hasFocus()); |
| |
| // Input an invalid value (larger than 24). |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); |
| // Force setting the hour by moving to minute. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| // After sending 2 and 5 only 2 is accepted. |
| assertEquals(2, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| // The hour view still has focus. |
| assertTrue(mTimePicker.getHourView().hasFocus()); |
| |
| // This time send a valid hour. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| // The value is valid. |
| assertEquals(23, mTimePicker.getHour()); |
| assertEquals(43, mTimePicker.getMinute()); |
| |
| verifyModeClockMinuteInput(); |
| } |
| |
| @Test |
| public void testKeyboardInputModeSpinnerAmPm() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| final int initialHour = 6; |
| final int initialMinute = 59; |
| prepareForKeyboardInput(initialHour, initialMinute, false /* is24hFormat */, |
| false /* isClockMode */); |
| |
| // when testing on device with lower resolution, the Spinner mode time picker may not show |
| // completely, which will cause case fail, so in this case remove the clock time picker to |
| // focus on the test of Spinner mode |
| final TimePicker clock = mActivity.findViewById(R.id.timepicker_clock); |
| mActivityRule.runOnUiThread(() -> clock.setVisibility(View.GONE)); |
| |
| assertEquals(initialHour, mTimePicker.getHour()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| |
| // Input invalid hour. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| // None of the keys below should be accepted after 1 was pressed. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| // Since only 0, 1 or 2 are accepted for AM/PM hour mode after pressing 1, we expect the |
| // hour value to be 1. |
| assertEquals(1, mTimePicker.getHour()); |
| assertFalse(mTimePicker.getHourView().hasFocus()); |
| |
| // Go back to hour view and input valid hour. |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(11, mTimePicker.getHour()); |
| assertFalse(mTimePicker.getHourView().hasFocus()); |
| |
| // Go back to hour view and exercise UP and DOWN keys. |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); |
| assertEquals(12, mTimePicker.getHour()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| assertEquals(10, mTimePicker.getHour()); |
| |
| // Minute input testing. |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| verifyModeSpinnerMinuteInput(); |
| |
| // Reset to values preparing to test the AM/PM picker. |
| mActivityRule.runOnUiThread(() -> { |
| mTimePicker.setHour(6); |
| mTimePicker.setMinute(initialMinute); |
| }); |
| mInstrumentation.waitForIdleSync(); |
| // In spinner mode the AM view and PM view are the same. |
| assertEquals(mTimePicker.getAmView(), mTimePicker.getPmView()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mTimePicker.getAmView().hasFocus()); |
| assertEquals(6, mTimePicker.getHour()); |
| // Pressing A changes nothing. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_A); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(6, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| // Pressing P switches to PM. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_P); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(18, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| // Pressing P again changes nothing. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_P); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(18, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| // Pressing A switches to AM. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_A); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(6, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| // Given that we are already set to AM, pressing UP changes nothing. |
| mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| assertEquals(6, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| // Pressing down switches to PM. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); |
| assertEquals(18, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| // Given that we are set to PM, pressing DOWN again changes nothing. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); |
| assertEquals(18, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| // Pressing UP switches to AM. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| assertEquals(6, mTimePicker.getHour()); |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| } |
| |
| @Test |
| public void testKeyboardInputModeSpinner24H() throws Throwable { |
| if (isWatch()) { |
| return; |
| } |
| final int initialHour = 6; |
| final int initialMinute = 59; |
| prepareForKeyboardInput(initialHour, initialMinute, true /* is24hFormat */, |
| false /* isClockMode */); |
| |
| // when testing on device with lower resolution, the Spinner mode time picker may not show |
| // completely, which will cause case fail, so in this case remove the clock time picker to |
| // focus on the test of Spinner mode |
| final TimePicker clock = mActivity.findViewById(R.id.timepicker_clock); |
| mActivityRule.runOnUiThread(() -> clock.setVisibility(View.GONE)); |
| |
| assertEquals(initialHour, mTimePicker.getHour()); |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| |
| // Input invalid hour. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); |
| // None of the keys below should be accepted after 2 was pressed. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| // Only 2 is accepted (as the only 0, 1, 2, and 3 can form valid hours after pressing 2). |
| assertEquals(2, mTimePicker.getHour()); |
| assertFalse(mTimePicker.getHourView().hasFocus()); |
| |
| // Go back to hour view and input valid hour. |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(23, mTimePicker.getHour()); |
| assertFalse(mTimePicker.getHourView().hasFocus()); |
| |
| // Go back to hour view and exercise UP and DOWN keys. |
| mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); |
| assertEquals(0 /* 24 */, mTimePicker.getHour()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| assertEquals(22, mTimePicker.getHour()); |
| |
| // Minute input testing. |
| assertEquals(initialMinute, mTimePicker.getMinute()); |
| verifyModeSpinnerMinuteInput(); |
| } |
| |
| private void verifyModeClockMinuteInput() { |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| // Send a invalid minute. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_7); |
| // Sent 6 and 7 but only 6 was valid. |
| assertEquals(6, mTimePicker.getMinute()); |
| // No matter what other invalid values we send, the minute is unchanged and the focus is |
| // kept. |
| // 61 invalid. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| // 62 invalid. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| // 63 invalid. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| assertEquals(6, mTimePicker.getMinute()); |
| // Refocus. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| |
| // In the end pass a valid minute. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_9); |
| assertEquals(59, mTimePicker.getMinute()); |
| } |
| |
| private void verifyModeSpinnerMinuteInput() throws Throwable { |
| mActivityRule.runOnUiThread(() -> mTimePicker.getMinuteView().requestFocus()); |
| mInstrumentation.waitForIdleSync(); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| |
| // Input invalid minute. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); |
| // None of the keys below should be accepted after 6 was pressed. |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| // Only 6 is accepted (as the only valid minute value that starts with 6 is 6 itself). |
| assertEquals(6, mTimePicker.getMinute()); |
| |
| // Go back to minute view and input valid minute. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_8); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); |
| assertEquals(48, mTimePicker.getMinute()); |
| |
| // Go back to minute view and exercise UP and DOWN keys. |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, |
| KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); |
| assertTrue(mTimePicker.getMinuteView().hasFocus()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); |
| assertEquals(49, mTimePicker.getMinute()); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); |
| assertEquals(47, mTimePicker.getMinute()); |
| } |
| |
| private void prepareForKeyboardInput(int initialHour, int initialMinute, boolean is24hFormat, |
| boolean isClockMode) throws Throwable { |
| mTimePicker = isClockMode |
| ? (TimePicker) mActivity.findViewById(R.id.timepicker_clock) |
| : (TimePicker) mActivity.findViewById(R.id.timepicker_spinner); |
| |
| mActivityRule.runOnUiThread(() -> { |
| /* hide one of the widgets to assure they fit onto the screen */ |
| if (isClockMode) { |
| mActivity.findViewById(R.id.timepicker_spinner).setVisibility(View.GONE); |
| } else { |
| mActivity.findViewById(R.id.timepicker_clock).setVisibility(View.GONE); |
| } |
| mTimePicker.setIs24HourView(is24hFormat); |
| mTimePicker.setHour(initialHour); |
| mTimePicker.setMinute(initialMinute); |
| mTimePicker.requestFocus(); |
| }); |
| mInstrumentation.waitForIdleSync(); |
| } |
| |
| private void verifyTimePickerKeyboardTraversal(boolean goForward, boolean is24HourView) |
| throws Throwable { |
| ArrayList<View> forwardViews = new ArrayList<>(); |
| String summary = (goForward ? " forward " : " backward ") |
| + "traversal, is24HourView=" + is24HourView; |
| assertNotNull("Unexpected NULL hour view for" + summary, mTimePicker.getHourView()); |
| forwardViews.add(mTimePicker.getHourView()); |
| assertNotNull("Unexpected NULL minute view for" + summary, mTimePicker.getMinuteView()); |
| forwardViews.add(mTimePicker.getMinuteView()); |
| if (!is24HourView) { |
| assertNotNull("Unexpected NULL AM view for" + summary, mTimePicker.getAmView()); |
| forwardViews.add(mTimePicker.getAmView()); |
| assertNotNull("Unexpected NULL PM view for" + summary, mTimePicker.getPmView()); |
| forwardViews.add(mTimePicker.getPmView()); |
| } |
| |
| if (!goForward) { |
| Collections.reverse(forwardViews); |
| } |
| |
| final int viewsSize = forwardViews.size(); |
| for (int i = 0; i < viewsSize; i++) { |
| final View currentView = forwardViews.get(i); |
| String afterKeyCodeFormattedString = ""; |
| int goForwardKeyCode = KeyEvent.KEYCODE_TAB; |
| int modifierKeyCodeToHold = KeyEvent.KEYCODE_SHIFT_LEFT; |
| |
| if (i == 0) { |
| // Make sure we always start by focusing the 1st element in the list. |
| mActivityRule.runOnUiThread(currentView::requestFocus); |
| } else { |
| if (goForward) { |
| afterKeyCodeFormattedString = " after pressing=" |
| + KeyEvent.keyCodeToString(goForwardKeyCode); |
| } else { |
| afterKeyCodeFormattedString = " after pressing=" |
| + KeyEvent.keyCodeToString(modifierKeyCodeToHold) |
| + "+" + KeyEvent.keyCodeToString(goForwardKeyCode) + " for" + summary; |
| } |
| } |
| |
| assertTrue("View='" + currentView + "'" + " with index " + i + " is not enabled" |
| + afterKeyCodeFormattedString + " for" + summary, currentView.isEnabled()); |
| assertTrue("View='" + currentView + "'" + " with index " + i + " is not focused" |
| + afterKeyCodeFormattedString + " for" + summary, currentView.isFocused()); |
| |
| if (i < viewsSize - 1) { |
| if (goForward) { |
| CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, currentView, goForwardKeyCode); |
| } else { |
| CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, currentView, |
| goForwardKeyCode, modifierKeyCodeToHold); |
| } |
| } |
| } |
| } |
| |
| private class MyTimePicker extends TimePicker { |
| public MyTimePicker(Context context) { |
| super(context); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Parcelable state) { |
| super.onRestoreInstanceState(state); |
| } |
| |
| @Override |
| protected Parcelable onSaveInstanceState() { |
| return super.onSaveInstanceState(); |
| } |
| } |
| } |