blob: 0886aa3c16a68c2e77fbe3bb673a667b1d30ae1d [file] [log] [blame]
/*
* 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.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.autofill.AutofillValue;
import android.widget.TimePicker;
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;
}
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();
}
}
}