blob: 34d97b2f3ea32a99578b2cc9476477e1ca9f3029 [file] [log] [blame]
/*
* Copyright (C) 2017 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 androidx.leanback.widget.picker;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.leanback.test.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class DatePickerTest {
private static final String TAG = "DatePickerTest";
private static final long TRANSITION_LENGTH = 1000;
Context mContext;
View mViewAbove;
DatePicker mDatePickerView;
ViewGroup mDatePickerInnerView;
View mViewBelow;
@Rule
public ActivityTestRule<DatePickerActivity> mActivityTestRule =
new ActivityTestRule<>(DatePickerActivity.class, false, false);
private DatePickerActivity mActivity;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
}
public void initActivity(Intent intent) throws Throwable {
mActivity = mActivityTestRule.launchActivity(intent);
mDatePickerView = (DatePicker) mActivity.findViewById(R.id.date_picker);
mDatePickerInnerView = (ViewGroup) mDatePickerView.findViewById(R.id.picker);
mDatePickerView.setActivatedVisibleItemCount(3);
mDatePickerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDatePickerView.setActivated(!mDatePickerView.isActivated());
}
});
if (intent.getIntExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets) == R.layout.datepicker_with_other_widgets) {
mViewAbove = mActivity.findViewById(R.id.above_picker);
mViewBelow = mActivity.findViewById(R.id.below_picker);
} else if (intent.getIntExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets) == R.layout.datepicker_alone) {
// A layout with only a DatePicker widget that is initially activated.
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mDatePickerView.setActivated(true);
}
});
Thread.sleep(500);
}
}
@Test
public void testFocusTravel() throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
assertThat("TextView above should have focus initially", mViewAbove.hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("DatePicker should have focus now", mDatePickerView.isFocused(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The first column of DatePicker should hold focus",
mDatePickerInnerView.getChildAt(0).hasFocus(), is(true));
// skipping the separator in the child indices
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The second column of DatePicker should hold focus",
mDatePickerInnerView.getChildAt(2).hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The third column of DatePicker should hold focus",
mDatePickerInnerView.getChildAt(4).hasFocus(), is(true));
}
@Test
public void testFocusRetainedForASelectedColumn()
throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
mDatePickerView.setFocusable(true);
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("DatePicker should have focus when it's focusable",
mDatePickerView.isFocused(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("After the first activation, the first column of DatePicker should hold focus",
mDatePickerInnerView.getChildAt(0).hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The third column of DatePicker should hold focus",
mDatePickerInnerView.getChildAt(4).hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("After the first deactivation, the DatePicker itself should hold focus",
mDatePickerView.isFocused(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("After the second activation, the last selected column (3rd) should hold focus",
mDatePickerInnerView.getChildAt(4).hasFocus(), is(true));
}
@Test
public void testFocusSkippedWhenDatePickerUnFocusable()
throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
mDatePickerView.setFocusable(false);
assertThat("TextView above should have focus initially.", mViewAbove.hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("DatePicker should be skipped and TextView below should have focus.",
mViewBelow.hasFocus(), is(true));
}
@Test
public void testTemporaryFocusLossWhenDeactivated()
throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
final int[] currentFocusChangeCountForViewAbove = {0};
mDatePickerView.setFocusable(true);
Log.d(TAG, "view above: " + mViewAbove);
mViewAbove.setOnFocusChangeListener(new View.OnFocusChangeListener(){
@Override
public void onFocusChange(View v, boolean hasFocus) {
currentFocusChangeCountForViewAbove[0]++;
}
});
assertThat("TextView above should have focus initially.", mViewAbove.hasFocus(), is(true));
// Traverse to the third column of date picker
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
// Click once to activate
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
// Traverse to the third column
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
// Click to deactivate. Before that we remember the focus change count for the view above.
// This view should NOT receive temporary focus when DatePicker is deactivated, and
// DatePicker itself should capture the focus.
int[] lastFocusChangeCountForViewAbove = {currentFocusChangeCountForViewAbove[0]};
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("DatePicker should have focus now since it's focusable",
mDatePickerView.isFocused(), is(true));
assertThat("Focus change count of view above should not be changed after last click.",
currentFocusChangeCountForViewAbove[0], is(lastFocusChangeCountForViewAbove[0]));
}
@Test
public void testTemporaryFocusLossWhenActivated() throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
final int[] currentFocusChangeCountForColumns = {0, 0, 0};
mDatePickerView.setFocusable(true);
mDatePickerInnerView.getChildAt(0).setOnFocusChangeListener(
new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
currentFocusChangeCountForColumns[0]++;
}
});
mDatePickerInnerView.getChildAt(2).setOnFocusChangeListener(
new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
currentFocusChangeCountForColumns[1]++;
}
});
mDatePickerInnerView.getChildAt(4).setOnFocusChangeListener(
new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
currentFocusChangeCountForColumns[2]++;
}
});
// Traverse to the third column of date picker
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
// Click once to activate
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
// Traverse to the third column
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
// Click to deactivate
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
// Click again. The focus should NOT be temporarily moved to the other columns and the third
// column should receive focus.
// Before that we will remember the last focus change count to compare it against after the
// click.
int[] lastFocusChangeCountForColumns = {currentFocusChangeCountForColumns[0],
currentFocusChangeCountForColumns[1], currentFocusChangeCountForColumns[2]};
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
assertThat("Focus change count of column 0 should not be changed after last click.",
currentFocusChangeCountForColumns[0], is(lastFocusChangeCountForColumns[0]));
assertThat("Focus change count of column 1 should not be changed after last click.",
currentFocusChangeCountForColumns[1], is(lastFocusChangeCountForColumns[1]));
assertThat("Focus change count of column 2 should not be changed after last click.",
currentFocusChangeCountForColumns[2], is(lastFocusChangeCountForColumns[2]));
}
@Test
public void testInitiallyActiveDatePicker()
throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_alone);
initActivity(intent);
assertThat("The first column of DatePicker should initially hold focus",
mDatePickerInnerView.getChildAt(0).hasFocus(), is(true));
// focus on first column
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The first column of DatePicker should still hold focus after scrolling down",
mDatePickerInnerView.getChildAt(0).hasFocus(), is(true));
// focus on second column
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The second column of DatePicker should hold focus after scrolling right",
mDatePickerInnerView.getChildAt(2).hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The second column of DatePicker should still hold focus after scrolling down",
mDatePickerInnerView.getChildAt(2).hasFocus(), is(true));
// focus on third column
sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The third column of DatePicker should hold focus after scrolling right",
mDatePickerInnerView.getChildAt(4).hasFocus(), is(true));
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
assertThat("The third column of DatePicker should still hold focus after scrolling down",
mDatePickerInnerView.getChildAt(4).hasFocus(), is(true));
}
@Test
public void testInvisibleColumnsAlpha() throws Throwable {
Intent intent = new Intent();
intent.putExtra(DatePickerActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.datepicker_with_other_widgets);
initActivity(intent);
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mDatePickerView.updateDate(2017, 2, 21, false);
}
});
Thread.sleep(TRANSITION_LENGTH);
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mDatePickerView.updateDate(2017, 2, 20, false);
}
});
Thread.sleep(TRANSITION_LENGTH);
sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
Thread.sleep(TRANSITION_LENGTH);
// Click once to activate
sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Thread.sleep(TRANSITION_LENGTH);
int activeColumn = 0;
// For the inactive columns: the alpha for all the rows except the selected row should be
// zero: Picker#mInvisibleColumnAlpha (they should all be invisible).
for (int i = 0; i < 3; i++) {
ViewGroup gridView = (ViewGroup) mDatePickerInnerView.getChildAt(2 * i);
int childCount = gridView.getChildCount();
int alpha1RowsCount = 0;
int alphaNonZeroRowsCount = 0;
for (int j = 0; j < childCount; j++) {
View pickerItem = gridView.getChildAt(j);
if (pickerItem.getAlpha() > 0) {
alphaNonZeroRowsCount++;
}
if (pickerItem.getAlpha() == 1) {
alpha1RowsCount++;
}
}
if (i == activeColumn) {
assertThat("The active column " + i + " should have only one row with an alpha of "
+ "1", alpha1RowsCount, is(1));
assertTrue("The active column " + i + " should have more than one view with alpha "
+ "greater than 1", alphaNonZeroRowsCount > 1);
} else {
assertThat("The inactive column " + i + " should have only one row with an alpha of"
+ " 1", alpha1RowsCount, is(1));
assertThat("The inactive column " + i + " should have only one row with a non-zero"
+ " alpha", alphaNonZeroRowsCount, is(1));
}
}
}
@Test
public void testExtractSeparatorsForDifferentLocales() throws Throwable {
// date pattern for en_US (English)
DatePicker datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "M/d/y";
}
};
List<CharSequence> actualSeparators = datePicker.extractSeparators();
List<String> expectedSeparators = Arrays.asList(new String[]{"", "/", "/", ""});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for fa_IR (Farsi)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "y/M/d";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"", "/", "/", ""});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for ar_EG (Arabic)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "d/M/y";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"", "/", "/", ""});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for cs_CZ (Czech)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "d. M. y";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"", ".", ".", ""});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for hr_HR (Croatian)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "dd. MM. y.";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"", ".", ".", "."});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for hr_HR (Bulgarian)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "d.MM.y 'r'.";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"", ".", ".", "r."});
assertEquals(expectedSeparators, actualSeparators);
// date pattern for en_XA (English pseudo-locale)
datePicker = new DatePicker(mContext, null) {
@Override
String getBestYearMonthDayPattern(String datePickerFormat) {
return "[M/d/y]";
}
};
actualSeparators = datePicker.extractSeparators();
expectedSeparators = Arrays.asList(new String[]{"[", "/", "/", "]"});
assertEquals(expectedSeparators, actualSeparators);
}
private void sendKeys(int ...keys) {
for (int i = 0; i < keys.length; i++) {
InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
}
}
}