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