1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertTrue; 24 import static org.mockito.Mockito.mock; 25 import static org.mockito.Mockito.reset; 26 import static org.mockito.Mockito.times; 27 import static org.mockito.Mockito.verify; 28 import static org.mockito.Mockito.verifyZeroInteractions; 29 30 import android.app.Activity; 31 import android.app.Instrumentation; 32 import android.content.Context; 33 import android.content.res.Configuration; 34 import android.os.Parcelable; 35 import android.util.AttributeSet; 36 import android.view.KeyEvent; 37 import android.view.View; 38 import android.view.autofill.AutofillValue; 39 import android.widget.TimePicker; 40 41 import androidx.test.InstrumentationRegistry; 42 import androidx.test.annotation.UiThreadTest; 43 import androidx.test.filters.MediumTest; 44 import androidx.test.rule.ActivityTestRule; 45 import androidx.test.runner.AndroidJUnit4; 46 47 import com.android.compatibility.common.util.CtsKeyEventUtil; 48 import com.android.compatibility.common.util.CtsTouchUtils; 49 50 import org.junit.Before; 51 import org.junit.Rule; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 55 import java.util.ArrayList; 56 import java.util.Calendar; 57 import java.util.Collections; 58 import java.util.GregorianCalendar; 59 import java.util.concurrent.atomic.AtomicInteger; 60 61 /** 62 * Test {@link TimePicker}. 63 */ 64 @MediumTest 65 @RunWith(AndroidJUnit4.class) 66 public class TimePickerTest { 67 private Instrumentation mInstrumentation; 68 private Activity mActivity; 69 private TimePicker mTimePicker; 70 71 @Rule 72 public ActivityTestRule<TimePickerCtsActivity> mActivityRule = 73 new ActivityTestRule<>(TimePickerCtsActivity.class); 74 75 @Before setup()76 public void setup() { 77 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 78 mActivity = mActivityRule.getActivity(); 79 mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock); 80 } 81 82 @Test testConstructors()83 public void testConstructors() { 84 AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); 85 assertNotNull(attrs); 86 87 new TimePicker(mActivity); 88 89 new TimePicker(mActivity, attrs); 90 new TimePicker(mActivity, null); 91 92 new TimePicker(mActivity, attrs, 0); 93 new TimePicker(mActivity, null, 0); 94 new TimePicker(mActivity, attrs, 0); 95 new TimePicker(mActivity, null, android.R.attr.timePickerStyle); 96 new TimePicker(mActivity, null, 0, android.R.style.Widget_Material_TimePicker); 97 new TimePicker(mActivity, null, 0, android.R.style.Widget_Material_Light_TimePicker); 98 } 99 100 @Test(expected=NullPointerException.class) testConstructorNullContext1()101 public void testConstructorNullContext1() { 102 new TimePicker(null); 103 } 104 105 @Test(expected=NullPointerException.class) testConstructorNullContext2()106 public void testConstructorNullContext2() { 107 AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); 108 new TimePicker(null, attrs); 109 } 110 111 @Test(expected=NullPointerException.class) testConstructorNullContext3()112 public void testConstructorNullContext3() { 113 AttributeSet attrs = mActivity.getResources().getLayout(R.layout.timepicker); 114 new TimePicker(null, attrs, 0); 115 } 116 117 @UiThreadTest 118 @Test testSetEnabled()119 public void testSetEnabled() { 120 assertTrue(mTimePicker.isEnabled()); 121 122 mTimePicker.setEnabled(false); 123 assertFalse(mTimePicker.isEnabled()); 124 assertNull(mTimePicker.getAutofillValue()); 125 assertEquals(View.AUTOFILL_TYPE_NONE, mTimePicker.getAutofillType()); 126 127 mTimePicker.setEnabled(true); 128 assertTrue(mTimePicker.isEnabled()); 129 assertNotNull(mTimePicker.getAutofillValue()); 130 assertEquals(View.AUTOFILL_TYPE_DATE, mTimePicker.getAutofillType()); 131 } 132 133 @UiThreadTest 134 @Test testAutofill()135 public void testAutofill() { 136 mTimePicker.setEnabled(true); 137 138 final AtomicInteger numberOfListenerCalls = new AtomicInteger(); 139 mTimePicker.setOnTimeChangedListener((v, h, m) -> numberOfListenerCalls.incrementAndGet()); 140 141 final Calendar calendar = new GregorianCalendar(); 142 calendar.set(Calendar.HOUR_OF_DAY, 4); 143 calendar.set(Calendar.MINUTE, 20); 144 145 final AutofillValue autofilledValue = AutofillValue.forDate(calendar.getTimeInMillis()); 146 mTimePicker.autofill(autofilledValue); 147 assertEquals(autofilledValue, mTimePicker.getAutofillValue()); 148 assertEquals(4, mTimePicker.getHour()); 149 assertEquals(20, mTimePicker.getMinute()); 150 assertEquals(1, numberOfListenerCalls.get()); 151 152 // Make sure autofill() is ignored when value is null. 153 numberOfListenerCalls.set(0); 154 mTimePicker.autofill((AutofillValue) null); 155 assertEquals(autofilledValue, mTimePicker.getAutofillValue()); 156 assertEquals(4, mTimePicker.getHour()); 157 assertEquals(20, mTimePicker.getMinute()); 158 assertEquals(0, numberOfListenerCalls.get()); 159 160 // Make sure autofill() is ignored when value is not a date. 161 numberOfListenerCalls.set(0); 162 mTimePicker.autofill(AutofillValue.forText("Y U NO IGNORE ME?")); 163 assertEquals(autofilledValue, mTimePicker.getAutofillValue()); 164 assertEquals(4, mTimePicker.getHour()); 165 assertEquals(20, mTimePicker.getMinute()); 166 assertEquals(0, numberOfListenerCalls.get()); 167 168 // Make sure getAutofillValue() is reset when value is manually filled. 169 mTimePicker.autofill(autofilledValue); // 04:20 170 mTimePicker.setHour(10); 171 calendar.setTimeInMillis(mTimePicker.getAutofillValue().getDateValue()); 172 assertEquals(10, calendar.get(Calendar.HOUR)); 173 mTimePicker.autofill(autofilledValue); // 04:20 174 mTimePicker.setMinute(8); 175 calendar.setTimeInMillis(mTimePicker.getAutofillValue().getDateValue()); 176 assertEquals(8, calendar.get(Calendar.MINUTE)); 177 } 178 179 @UiThreadTest 180 @Test testSetOnTimeChangedListener()181 public void testSetOnTimeChangedListener() { 182 // On time change listener is notified on every call to setCurrentHour / setCurrentMinute. 183 // We want to make sure that before we register our listener, we initialize the time picker 184 // to the time that is explicitly different from the values we'll be testing for in both 185 // hour and minute. Otherwise if the test happens to run at the time that ends in 186 // "minuteForTesting" minutes, we'll get two onTimeChanged callbacks with identical values. 187 final int initialHour = 10; 188 final int initialMinute = 38; 189 final int hourForTesting = 13; 190 final int minuteForTesting = 50; 191 192 mTimePicker.setHour(initialHour); 193 mTimePicker.setMinute(initialMinute); 194 195 // Now register the listener 196 TimePicker.OnTimeChangedListener mockOnTimeChangeListener = 197 mock(TimePicker.OnTimeChangedListener.class); 198 mTimePicker.setOnTimeChangedListener(mockOnTimeChangeListener); 199 mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting)); 200 mTimePicker.setCurrentMinute(Integer.valueOf(minuteForTesting)); 201 // We're expecting two onTimeChanged callbacks, one with new hour and one with new 202 // hour+minute 203 verify(mockOnTimeChangeListener, times(1)).onTimeChanged( 204 mTimePicker, hourForTesting, initialMinute); 205 verify(mockOnTimeChangeListener, times(1)).onTimeChanged( 206 mTimePicker, hourForTesting, minuteForTesting); 207 208 // set the same hour as current 209 reset(mockOnTimeChangeListener); 210 mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting)); 211 verifyZeroInteractions(mockOnTimeChangeListener); 212 213 mTimePicker.setCurrentHour(Integer.valueOf(hourForTesting + 1)); 214 verify(mockOnTimeChangeListener, times(1)).onTimeChanged( 215 mTimePicker, hourForTesting + 1, minuteForTesting); 216 217 // set the same minute as current 218 reset(mockOnTimeChangeListener); 219 mTimePicker.setCurrentMinute(minuteForTesting); 220 verifyZeroInteractions(mockOnTimeChangeListener); 221 222 reset(mockOnTimeChangeListener); 223 mTimePicker.setCurrentMinute(minuteForTesting + 1); 224 verify(mockOnTimeChangeListener, times(1)).onTimeChanged( 225 mTimePicker, hourForTesting + 1, minuteForTesting + 1); 226 227 // change time picker mode 228 reset(mockOnTimeChangeListener); 229 mTimePicker.setIs24HourView(!mTimePicker.is24HourView()); 230 verifyZeroInteractions(mockOnTimeChangeListener); 231 } 232 233 @UiThreadTest 234 @Test testAccessCurrentHour()235 public void testAccessCurrentHour() { 236 // AM/PM mode 237 mTimePicker.setIs24HourView(false); 238 239 mTimePicker.setCurrentHour(0); 240 assertEquals(Integer.valueOf(0), mTimePicker.getCurrentHour()); 241 242 mTimePicker.setCurrentHour(12); 243 assertEquals(Integer.valueOf(12), mTimePicker.getCurrentHour()); 244 245 mTimePicker.setCurrentHour(13); 246 assertEquals(Integer.valueOf(13), mTimePicker.getCurrentHour()); 247 248 mTimePicker.setCurrentHour(23); 249 assertEquals(Integer.valueOf(23), mTimePicker.getCurrentHour()); 250 251 // for 24 hour mode 252 mTimePicker.setIs24HourView(true); 253 254 mTimePicker.setCurrentHour(0); 255 assertEquals(Integer.valueOf(0), mTimePicker.getCurrentHour()); 256 257 mTimePicker.setCurrentHour(13); 258 assertEquals(Integer.valueOf(13), mTimePicker.getCurrentHour()); 259 260 mTimePicker.setCurrentHour(23); 261 assertEquals(Integer.valueOf(23), mTimePicker.getCurrentHour()); 262 } 263 264 @UiThreadTest 265 @Test testAccessHour()266 public void testAccessHour() { 267 // AM/PM mode 268 mTimePicker.setIs24HourView(false); 269 270 mTimePicker.setHour(0); 271 assertEquals(0, mTimePicker.getHour()); 272 273 mTimePicker.setHour(12); 274 assertEquals(12, mTimePicker.getHour()); 275 276 mTimePicker.setHour(13); 277 assertEquals(13, mTimePicker.getHour()); 278 279 mTimePicker.setHour(23); 280 assertEquals(23, mTimePicker.getHour()); 281 282 // for 24 hour mode 283 mTimePicker.setIs24HourView(true); 284 285 mTimePicker.setHour(0); 286 assertEquals(0, mTimePicker.getHour()); 287 288 mTimePicker.setHour(13); 289 assertEquals(13, mTimePicker.getHour()); 290 291 mTimePicker.setHour(23); 292 assertEquals(23, mTimePicker.getHour()); 293 } 294 295 @UiThreadTest 296 @Test testAccessIs24HourView()297 public void testAccessIs24HourView() { 298 assertFalse(mTimePicker.is24HourView()); 299 300 mTimePicker.setIs24HourView(true); 301 assertTrue(mTimePicker.is24HourView()); 302 303 mTimePicker.setIs24HourView(false); 304 assertFalse(mTimePicker.is24HourView()); 305 } 306 307 @UiThreadTest 308 @Test testAccessCurrentMinute()309 public void testAccessCurrentMinute() { 310 mTimePicker.setCurrentMinute(0); 311 assertEquals(Integer.valueOf(0), mTimePicker.getCurrentMinute()); 312 313 mTimePicker.setCurrentMinute(12); 314 assertEquals(Integer.valueOf(12), mTimePicker.getCurrentMinute()); 315 316 mTimePicker.setCurrentMinute(33); 317 assertEquals(Integer.valueOf(33), mTimePicker.getCurrentMinute()); 318 319 mTimePicker.setCurrentMinute(59); 320 assertEquals(Integer.valueOf(59), mTimePicker.getCurrentMinute()); 321 } 322 323 @UiThreadTest 324 @Test testAccessMinute()325 public void testAccessMinute() { 326 mTimePicker.setMinute(0); 327 assertEquals(0, mTimePicker.getMinute()); 328 329 mTimePicker.setMinute(12); 330 assertEquals(12, mTimePicker.getMinute()); 331 332 mTimePicker.setMinute(33); 333 assertEquals(33, mTimePicker.getMinute()); 334 335 mTimePicker.setMinute(59); 336 assertEquals(59, mTimePicker.getMinute()); 337 } 338 339 @Test testGetBaseline()340 public void testGetBaseline() { 341 assertEquals(-1, mTimePicker.getBaseline()); 342 } 343 344 @Test testOnSaveInstanceStateAndOnRestoreInstanceState()345 public void testOnSaveInstanceStateAndOnRestoreInstanceState() { 346 MyTimePicker source = new MyTimePicker(mActivity); 347 MyTimePicker dest = new MyTimePicker(mActivity); 348 int expectHour = (dest.getCurrentHour() + 10) % 24; 349 int expectMinute = (dest.getCurrentMinute() + 10) % 60; 350 source.setCurrentHour(expectHour); 351 source.setCurrentMinute(expectMinute); 352 353 Parcelable p = source.onSaveInstanceState(); 354 dest.onRestoreInstanceState(p); 355 356 assertEquals(Integer.valueOf(expectHour), dest.getCurrentHour()); 357 assertEquals(Integer.valueOf(expectMinute), dest.getCurrentMinute()); 358 } 359 isWatch()360 private boolean isWatch() { 361 return (mActivity.getResources().getConfiguration().uiMode 362 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH; 363 } 364 365 @Test testKeyboardTabTraversalModeClock()366 public void testKeyboardTabTraversalModeClock() throws Throwable { 367 if (isWatch()) { 368 return; 369 } 370 mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock); 371 372 mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(false)); 373 mInstrumentation.waitForIdleSync(); 374 verifyTimePickerKeyboardTraversal( 375 true /* goForward */, 376 false /* is24HourView */); 377 verifyTimePickerKeyboardTraversal( 378 false /* goForward */, 379 false /* is24HourView */); 380 381 mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(true)); 382 mInstrumentation.waitForIdleSync(); 383 verifyTimePickerKeyboardTraversal( 384 true /* goForward */, 385 true /* is24HourView */); 386 verifyTimePickerKeyboardTraversal( 387 false /* goForward */, 388 true /* is24HourView */); 389 } 390 391 @Test testKeyboardTabTraversalModeSpinner()392 public void testKeyboardTabTraversalModeSpinner() throws Throwable { 393 if (isWatch()) { 394 return; 395 } 396 // Hide timepicker_clock so that timepicker_spinner would be visible. 397 mActivityRule.runOnUiThread(() -> 398 mActivity.findViewById(R.id.timepicker_clock).setVisibility(View.GONE)); 399 mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_spinner); 400 401 mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(false)); 402 mInstrumentation.waitForIdleSync(); 403 404 // Spinner time-picker doesn't explicitly define a focus order. Just make sure inputs 405 // are able to be traversed (added to focusables). 406 ArrayList<View> focusables = new ArrayList<>(); 407 mTimePicker.addFocusables(focusables, View.FOCUS_FORWARD); 408 assertTrue(focusables.contains(mTimePicker.getHourView())); 409 assertTrue(focusables.contains(mTimePicker.getMinuteView())); 410 assertTrue(focusables.contains(mTimePicker.getAmView())); 411 focusables.clear(); 412 413 mActivityRule.runOnUiThread(() -> mTimePicker.setIs24HourView(true)); 414 mInstrumentation.waitForIdleSync(); 415 mTimePicker.addFocusables(focusables, View.FOCUS_FORWARD); 416 assertTrue(focusables.contains(mTimePicker.getHourView())); 417 assertTrue(focusables.contains(mTimePicker.getMinuteView())); 418 } 419 420 @Test testKeyboardInputModeClockAmPm()421 public void testKeyboardInputModeClockAmPm() throws Throwable { 422 if (isWatch()) { 423 return; 424 } 425 final int initialHour = 6; 426 final int initialMinute = 59; 427 prepareForKeyboardInput(initialHour, initialMinute, false /* is24hFormat */, 428 true /* isClockMode */); 429 430 // Input valid hour. 431 assertEquals(initialHour, mTimePicker.getHour()); 432 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 433 mTimePicker.getHourView()); 434 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 435 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_0); 436 assertEquals(10, mTimePicker.getHour()); 437 assertTrue(mTimePicker.getMinuteView().hasFocus()); 438 439 // Input valid minute. 440 assertEquals(initialMinute, mTimePicker.getMinute()); 441 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 442 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 443 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 444 assertEquals(43, mTimePicker.getMinute()); 445 assertTrue(mTimePicker.getAmView().hasFocus()); 446 447 // Accepting AM changes nothing. 448 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); 449 assertEquals(10, mTimePicker.getHour()); 450 assertEquals(43, mTimePicker.getMinute()); 451 452 // Focus PM radio. 453 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 454 assertTrue(mTimePicker.getPmView().hasFocus()); 455 // Still nothing has changed. 456 assertEquals(10, mTimePicker.getHour()); 457 assertEquals(43, mTimePicker.getMinute()); 458 // Select PM and verify the hour has changed. 459 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); 460 assertEquals(22, mTimePicker.getHour()); 461 assertEquals(43, mTimePicker.getMinute()); 462 // Set AM again. 463 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 464 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 465 assertTrue(mTimePicker.getAmView().hasFocus()); 466 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_ENTER); 467 assertEquals(10, mTimePicker.getHour()); 468 469 // Re-focus the hour view. 470 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 471 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 472 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 473 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 474 assertTrue(mTimePicker.getHourView().hasFocus()); 475 476 // Input an invalid value (larger than 12). 477 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 478 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 479 // Force setting the hour by moving to minute. 480 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 481 // After sending 1 and 3 only 1 is accepted. 482 assertEquals(1, mTimePicker.getHour()); 483 assertEquals(43, mTimePicker.getMinute()); 484 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 485 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 486 // The hour view still has focus. 487 assertTrue(mTimePicker.getHourView().hasFocus()); 488 489 // This time send a valid hour (11). 490 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 491 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 492 // The value is valid. 493 assertEquals(11, mTimePicker.getHour()); 494 assertEquals(43, mTimePicker.getMinute()); 495 496 verifyModeClockMinuteInput(); 497 } 498 499 @Test testKeyboardInputModeClock24H()500 public void testKeyboardInputModeClock24H() throws Throwable { 501 if (isWatch()) { 502 return; 503 } 504 final int initialHour = 6; 505 final int initialMinute = 59; 506 prepareForKeyboardInput(initialHour, initialMinute, true /* is24hFormat */, 507 true /* isClockMode */); 508 509 // Input valid hour. 510 assertEquals(initialHour, mTimePicker.getHour()); 511 CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, 512 mTimePicker.getHourView()); 513 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 514 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_0); 515 assertEquals(10, mTimePicker.getHour()); 516 assertTrue(mTimePicker.getMinuteView().hasFocus()); 517 518 // Input valid minute. 519 assertEquals(initialMinute, mTimePicker.getMinute()); 520 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 521 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 522 assertEquals(43, mTimePicker.getMinute()); 523 524 // Re-focus the hour view. 525 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 526 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 527 assertTrue(mTimePicker.getHourView().hasFocus()); 528 529 // Input an invalid value (larger than 24). 530 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); 531 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); 532 // Force setting the hour by moving to minute. 533 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 534 // After sending 2 and 5 only 2 is accepted. 535 assertEquals(2, mTimePicker.getHour()); 536 assertEquals(43, mTimePicker.getMinute()); 537 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 538 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 539 // The hour view still has focus. 540 assertTrue(mTimePicker.getHourView().hasFocus()); 541 542 // This time send a valid hour. 543 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); 544 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 545 // The value is valid. 546 assertEquals(23, mTimePicker.getHour()); 547 assertEquals(43, mTimePicker.getMinute()); 548 549 verifyModeClockMinuteInput(); 550 } 551 552 @Test testKeyboardInputModeSpinnerAmPm()553 public void testKeyboardInputModeSpinnerAmPm() throws Throwable { 554 if (isWatch()) { 555 return; 556 } 557 final int initialHour = 6; 558 final int initialMinute = 59; 559 prepareForKeyboardInput(initialHour, initialMinute, false /* is24hFormat */, 560 false /* isClockMode */); 561 562 // when testing on device with lower resolution, the Spinner mode time picker may not show 563 // completely, which will cause case fail, so in this case remove the clock time picker to 564 // focus on the test of Spinner mode 565 final TimePicker clock = mActivity.findViewById(R.id.timepicker_clock); 566 mActivityRule.runOnUiThread(() -> clock.setVisibility(View.GONE)); 567 568 assertEquals(initialHour, mTimePicker.getHour()); 569 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 570 mInstrumentation.waitForIdleSync(); 571 572 // Input invalid hour. 573 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 574 // None of the keys below should be accepted after 1 was pressed. 575 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 576 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 577 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); 578 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 579 // Since only 0, 1 or 2 are accepted for AM/PM hour mode after pressing 1, we expect the 580 // hour value to be 1. 581 assertEquals(1, mTimePicker.getHour()); 582 assertFalse(mTimePicker.getHourView().hasFocus()); 583 584 // Go back to hour view and input valid hour. 585 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 586 mInstrumentation.waitForIdleSync(); 587 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 588 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 589 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 590 assertEquals(11, mTimePicker.getHour()); 591 assertFalse(mTimePicker.getHourView().hasFocus()); 592 593 // Go back to hour view and exercise UP and DOWN keys. 594 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 595 mInstrumentation.waitForIdleSync(); 596 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); 597 assertEquals(12, mTimePicker.getHour()); 598 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 599 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 600 assertEquals(10, mTimePicker.getHour()); 601 602 // Minute input testing. 603 assertEquals(initialMinute, mTimePicker.getMinute()); 604 verifyModeSpinnerMinuteInput(); 605 606 // Reset to values preparing to test the AM/PM picker. 607 mActivityRule.runOnUiThread(() -> { 608 mTimePicker.setHour(6); 609 mTimePicker.setMinute(initialMinute); 610 }); 611 mInstrumentation.waitForIdleSync(); 612 // In spinner mode the AM view and PM view are the same. 613 assertEquals(mTimePicker.getAmView(), mTimePicker.getPmView()); 614 mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); 615 mInstrumentation.waitForIdleSync(); 616 assertTrue(mTimePicker.getAmView().hasFocus()); 617 assertEquals(6, mTimePicker.getHour()); 618 // Pressing A changes nothing. 619 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_A); 620 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 621 assertEquals(6, mTimePicker.getHour()); 622 assertEquals(initialMinute, mTimePicker.getMinute()); 623 // Pressing P switches to PM. 624 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 625 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 626 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_P); 627 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 628 assertEquals(18, mTimePicker.getHour()); 629 assertEquals(initialMinute, mTimePicker.getMinute()); 630 // Pressing P again changes nothing. 631 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 632 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 633 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_P); 634 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 635 assertEquals(18, mTimePicker.getHour()); 636 assertEquals(initialMinute, mTimePicker.getMinute()); 637 // Pressing A switches to AM. 638 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 639 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 640 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_A); 641 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 642 assertEquals(6, mTimePicker.getHour()); 643 assertEquals(initialMinute, mTimePicker.getMinute()); 644 // Given that we are already set to AM, pressing UP changes nothing. 645 mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); 646 mInstrumentation.waitForIdleSync(); 647 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 648 assertEquals(6, mTimePicker.getHour()); 649 assertEquals(initialMinute, mTimePicker.getMinute()); 650 mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); 651 mInstrumentation.waitForIdleSync(); 652 // Pressing down switches to PM. 653 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); 654 assertEquals(18, mTimePicker.getHour()); 655 assertEquals(initialMinute, mTimePicker.getMinute()); 656 mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); 657 mInstrumentation.waitForIdleSync(); 658 // Given that we are set to PM, pressing DOWN again changes nothing. 659 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); 660 assertEquals(18, mTimePicker.getHour()); 661 assertEquals(initialMinute, mTimePicker.getMinute()); 662 mActivityRule.runOnUiThread(() -> mTimePicker.getAmView().requestFocus()); 663 mInstrumentation.waitForIdleSync(); 664 // Pressing UP switches to AM. 665 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 666 assertEquals(6, mTimePicker.getHour()); 667 assertEquals(initialMinute, mTimePicker.getMinute()); 668 } 669 670 @Test testKeyboardInputModeSpinner24H()671 public void testKeyboardInputModeSpinner24H() throws Throwable { 672 if (isWatch()) { 673 return; 674 } 675 final int initialHour = 6; 676 final int initialMinute = 59; 677 prepareForKeyboardInput(initialHour, initialMinute, true /* is24hFormat */, 678 false /* isClockMode */); 679 680 // when testing on device with lower resolution, the Spinner mode time picker may not show 681 // completely, which will cause case fail, so in this case remove the clock time picker to 682 // focus on the test of Spinner mode 683 final TimePicker clock = mActivity.findViewById(R.id.timepicker_clock); 684 mActivityRule.runOnUiThread(() -> clock.setVisibility(View.GONE)); 685 686 assertEquals(initialHour, mTimePicker.getHour()); 687 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 688 mInstrumentation.waitForIdleSync(); 689 690 // Input invalid hour. 691 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); 692 // None of the keys below should be accepted after 2 was pressed. 693 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 694 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); 695 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); 696 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 697 // Only 2 is accepted (as the only 0, 1, 2, and 3 can form valid hours after pressing 2). 698 assertEquals(2, mTimePicker.getHour()); 699 assertFalse(mTimePicker.getHourView().hasFocus()); 700 701 // Go back to hour view and input valid hour. 702 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 703 mInstrumentation.waitForIdleSync(); 704 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); 705 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 706 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 707 assertEquals(23, mTimePicker.getHour()); 708 assertFalse(mTimePicker.getHourView().hasFocus()); 709 710 // Go back to hour view and exercise UP and DOWN keys. 711 mActivityRule.runOnUiThread(() -> mTimePicker.getHourView().requestFocus()); 712 mInstrumentation.waitForIdleSync(); 713 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); 714 assertEquals(0 /* 24 */, mTimePicker.getHour()); 715 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 716 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 717 assertEquals(22, mTimePicker.getHour()); 718 719 // Minute input testing. 720 assertEquals(initialMinute, mTimePicker.getMinute()); 721 verifyModeSpinnerMinuteInput(); 722 } 723 verifyModeClockMinuteInput()724 private void verifyModeClockMinuteInput() { 725 assertTrue(mTimePicker.getMinuteView().hasFocus()); 726 // Send a invalid minute. 727 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); 728 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_7); 729 // Sent 6 and 7 but only 6 was valid. 730 assertEquals(6, mTimePicker.getMinute()); 731 // No matter what other invalid values we send, the minute is unchanged and the focus is 732 // kept. 733 // 61 invalid. 734 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_1); 735 assertTrue(mTimePicker.getMinuteView().hasFocus()); 736 // 62 invalid. 737 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_2); 738 assertTrue(mTimePicker.getMinuteView().hasFocus()); 739 // 63 invalid. 740 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 741 assertTrue(mTimePicker.getMinuteView().hasFocus()); 742 assertEquals(6, mTimePicker.getMinute()); 743 // Refocus. 744 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 745 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 746 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 747 assertTrue(mTimePicker.getMinuteView().hasFocus()); 748 749 // In the end pass a valid minute. 750 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); 751 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_9); 752 assertEquals(59, mTimePicker.getMinute()); 753 } 754 verifyModeSpinnerMinuteInput()755 private void verifyModeSpinnerMinuteInput() throws Throwable { 756 mActivityRule.runOnUiThread(() -> mTimePicker.getMinuteView().requestFocus()); 757 mInstrumentation.waitForIdleSync(); 758 assertTrue(mTimePicker.getMinuteView().hasFocus()); 759 760 // Input invalid minute. 761 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_6); 762 // None of the keys below should be accepted after 6 was pressed. 763 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_3); 764 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 765 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_5); 766 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 767 // Only 6 is accepted (as the only valid minute value that starts with 6 is 6 itself). 768 assertEquals(6, mTimePicker.getMinute()); 769 770 // Go back to minute view and input valid minute. 771 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 772 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 773 assertTrue(mTimePicker.getMinuteView().hasFocus()); 774 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_4); 775 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_8); 776 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_TAB); 777 assertEquals(48, mTimePicker.getMinute()); 778 779 // Go back to minute view and exercise UP and DOWN keys. 780 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTimePicker, 781 KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SHIFT_LEFT); 782 assertTrue(mTimePicker.getMinuteView().hasFocus()); 783 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_DOWN); 784 assertEquals(49, mTimePicker.getMinute()); 785 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 786 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTimePicker, KeyEvent.KEYCODE_DPAD_UP); 787 assertEquals(47, mTimePicker.getMinute()); 788 } 789 prepareForKeyboardInput(int initialHour, int initialMinute, boolean is24hFormat, boolean isClockMode)790 private void prepareForKeyboardInput(int initialHour, int initialMinute, boolean is24hFormat, 791 boolean isClockMode) throws Throwable { 792 mTimePicker = isClockMode 793 ? (TimePicker) mActivity.findViewById(R.id.timepicker_clock) 794 : (TimePicker) mActivity.findViewById(R.id.timepicker_spinner); 795 796 mActivityRule.runOnUiThread(() -> { 797 /* hide one of the widgets to assure they fit onto the screen */ 798 if (isClockMode) { 799 mActivity.findViewById(R.id.timepicker_spinner).setVisibility(View.GONE); 800 } else { 801 mActivity.findViewById(R.id.timepicker_clock).setVisibility(View.GONE); 802 } 803 mTimePicker.setIs24HourView(is24hFormat); 804 mTimePicker.setHour(initialHour); 805 mTimePicker.setMinute(initialMinute); 806 mTimePicker.requestFocus(); 807 }); 808 mInstrumentation.waitForIdleSync(); 809 } 810 verifyTimePickerKeyboardTraversal(boolean goForward, boolean is24HourView)811 private void verifyTimePickerKeyboardTraversal(boolean goForward, boolean is24HourView) 812 throws Throwable { 813 ArrayList<View> forwardViews = new ArrayList<>(); 814 String summary = (goForward ? " forward " : " backward ") 815 + "traversal, is24HourView=" + is24HourView; 816 assertNotNull("Unexpected NULL hour view for" + summary, mTimePicker.getHourView()); 817 forwardViews.add(mTimePicker.getHourView()); 818 assertNotNull("Unexpected NULL minute view for" + summary, mTimePicker.getMinuteView()); 819 forwardViews.add(mTimePicker.getMinuteView()); 820 if (!is24HourView) { 821 assertNotNull("Unexpected NULL AM view for" + summary, mTimePicker.getAmView()); 822 forwardViews.add(mTimePicker.getAmView()); 823 assertNotNull("Unexpected NULL PM view for" + summary, mTimePicker.getPmView()); 824 forwardViews.add(mTimePicker.getPmView()); 825 } 826 827 if (!goForward) { 828 Collections.reverse(forwardViews); 829 } 830 831 final int viewsSize = forwardViews.size(); 832 for (int i = 0; i < viewsSize; i++) { 833 final View currentView = forwardViews.get(i); 834 String afterKeyCodeFormattedString = ""; 835 int goForwardKeyCode = KeyEvent.KEYCODE_TAB; 836 int modifierKeyCodeToHold = KeyEvent.KEYCODE_SHIFT_LEFT; 837 838 if (i == 0) { 839 // Make sure we always start by focusing the 1st element in the list. 840 mActivityRule.runOnUiThread(currentView::requestFocus); 841 } else { 842 if (goForward) { 843 afterKeyCodeFormattedString = " after pressing=" 844 + KeyEvent.keyCodeToString(goForwardKeyCode); 845 } else { 846 afterKeyCodeFormattedString = " after pressing=" 847 + KeyEvent.keyCodeToString(modifierKeyCodeToHold) 848 + "+" + KeyEvent.keyCodeToString(goForwardKeyCode) + " for" + summary; 849 } 850 } 851 852 assertTrue("View='" + currentView + "'" + " with index " + i + " is not enabled" 853 + afterKeyCodeFormattedString + " for" + summary, currentView.isEnabled()); 854 assertTrue("View='" + currentView + "'" + " with index " + i + " is not focused" 855 + afterKeyCodeFormattedString + " for" + summary, currentView.isFocused()); 856 857 if (i < viewsSize - 1) { 858 if (goForward) { 859 CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, currentView, goForwardKeyCode); 860 } else { 861 CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, currentView, 862 goForwardKeyCode, modifierKeyCodeToHold); 863 } 864 } 865 } 866 } 867 868 private class MyTimePicker extends TimePicker { MyTimePicker(Context context)869 public MyTimePicker(Context context) { 870 super(context); 871 } 872 873 @Override onRestoreInstanceState(Parcelable state)874 protected void onRestoreInstanceState(Parcelable state) { 875 super.onRestoreInstanceState(state); 876 } 877 878 @Override onSaveInstanceState()879 protected Parcelable onSaveInstanceState() { 880 return super.onSaveInstanceState(); 881 } 882 } 883 } 884