Time zone, Region, UTC picker

- Extract most common view related codes into BaseTimeZoneAdapter
  and BaseTimeZonePicker. Subclass handles the text formatting and
  order.
- Search view is added compared to previous version of time
  zone picker
- SpannableUtil is added to preserve spannable when formatting
  String resource.
- Fix the bug using GMT+<arabic> as time zone id. b/73132985
- Fix Talkback treating flags on screens as a separate element

Bug: 72146259
Bug: 73132985
Bug: 73952488
Test: mm RunSettingsRoboTests
Change-Id: I42c6ac369199c09d11e7f5cc4707358fa4780fed
This commit is contained in:
Victor Chang
2018-02-28 19:31:31 +00:00
parent 22a39c2b93
commit fbd30acef0
17 changed files with 1603 additions and 3 deletions

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import android.support.v7.widget.RecyclerView.AdapterDataObserver;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static com.google.common.truth.Truth.assertThat;
@RunWith(SettingsRobolectricTestRunner.class)
public class BaseTimeZoneAdapterTest {
@Test
public void testFilter() throws InterruptedException {
TestItem US = new TestItem("United States");
TestItem HK = new TestItem("Hong Kong");
TestItem UK = new TestItem("United Kingdom", new String[] { "United Kingdom",
"Great Britain"});
TestItem secretCountry = new TestItem("no name", new String[] { "Secret"});
List<TestItem> items = new ArrayList<>();
items.add(US);
items.add(HK);
items.add(UK);
items.add(secretCountry);
TestTimeZoneAdapter adapter = new TestTimeZoneAdapter(items);
assertSearch(adapter, "", items.toArray(new TestItem[items.size()]));
assertSearch(adapter, "Unit", US, UK);
assertSearch(adapter, "kon", HK);
assertSearch(adapter, "brit", UK);
assertSearch(adapter, "sec", secretCountry);
}
private void assertSearch(TestTimeZoneAdapter adapter , String searchText, TestItem... items)
throws InterruptedException {
Observer observer = new Observer(adapter);
adapter.getFilter().filter(searchText);
observer.await();
assertThat(adapter.getItemCount()).isEqualTo(items.length);
for (int i = 0; i < items.length; i++) {
assertThat(adapter.getItem(i)).isEqualTo(items[i]);
}
}
private static class Observer extends AdapterDataObserver {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final TestTimeZoneAdapter mAdapter;
public Observer(TestTimeZoneAdapter adapter) {
mAdapter = adapter;
mAdapter.registerAdapterDataObserver(this);
}
@Override
public void onChanged() {
mAdapter.unregisterAdapterDataObserver(this);
mLatch.countDown();
}
public void await() throws InterruptedException {
mLatch.await(2L, TimeUnit.SECONDS);
}
}
private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter<TestItem> {
public TestTimeZoneAdapter(List<TestItem> items) {
super(items, position -> {}, Locale.US, false);
}
}
private static class TestItem implements BaseTimeZoneAdapter.AdapterItem {
private final String mTitle;
private final String[] mSearchKeys;
TestItem(String title) {
this(title, new String[] { title });
}
TestItem(String title, String[] searchKeys) {
mTitle = title;
mSearchKeys = searchKeys;
}
@Override
public CharSequence getTitle() {
return mTitle;
}
@Override
public CharSequence getSummary() {
return null;
}
@Override
public String getIconText() {
return null;
}
@Override
public String getCurrentTime() {
return null;
}
@Override
public long getItemId() {
return 0;
}
@Override
public String[] getSearchKeys() {
return mSearchKeys;
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import android.content.Context;
import android.icu.util.TimeZone;
import com.android.settings.datetime.timezone.model.TimeZoneData;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.google.common.truth.Truth;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static org.mockito.Mockito.mock;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = { BaseTimeZoneInfoPickerTest.ShadowDataFormat.class })
public class BaseTimeZoneInfoPickerTest {
@Implements(android.text.format.DateFormat.class)
public static class ShadowDataFormat {
public static String sTimeFormatString = "";
@Implementation
public static String getTimeFormatString(Context context) {
return sTimeFormatString;
}
}
/**
* Verify the summary, title, and time label in a time zone item are formatted properly.
*/
@Test
public void createAdapter_matchTimeZoneInfoAndOrder() {
ShadowDataFormat.sTimeFormatString = "HH:MM";
BaseTimeZoneInfoPicker picker = new TestBaseTimeZoneInfoPicker();
BaseTimeZoneAdapter adapter = picker.createAdapter(mock(TimeZoneData.class));
Truth.assertThat(adapter.getItemCount()).isEqualTo(2);
BaseTimeZoneAdapter.AdapterItem item1 = adapter.getItem(0);
Truth.assertThat(item1.getTitle().toString()).isEqualTo("Los Angeles");
Truth.assertThat(item1.getSummary().toString()).isEqualTo("Pacific Time (GMT-08:00)");
Truth.assertThat(item1.getCurrentTime())
.hasLength(ShadowDataFormat.sTimeFormatString.length());
BaseTimeZoneAdapter.AdapterItem item2 = adapter.getItem(1);
Truth.assertThat(item2.getTitle().toString()).isEqualTo("New York");
Truth.assertThat(item2.getSummary().toString()).isEqualTo("Eastern Time (GMT-05:00)");
Truth.assertThat(item2.getCurrentTime())
.hasLength(ShadowDataFormat.sTimeFormatString.length());
}
public static class TestBaseTimeZoneInfoPicker extends BaseTimeZoneInfoPicker {
public TestBaseTimeZoneInfoPicker() {
super(0, 0, false, false);
}
@Override
public List<TimeZoneInfo> getAllTimeZoneInfos(TimeZoneData timeZoneData) {
TimeZoneInfo zone1 = new TimeZoneInfo.Builder(
TimeZone.getFrozenTimeZone("America/Los_Angeles"))
.setGenericName("Pacific Time")
.setStandardName("Pacific Standard Time")
.setDaylightName("Pacific Daylight Time")
.setExemplarLocation("Los Angeles")
.setGmtOffset("GMT-08:00")
.setItemId(0)
.build();
TimeZoneInfo zone2 = new TimeZoneInfo.Builder(
TimeZone.getFrozenTimeZone("America/New_York"))
.setGenericName("Eastern Time")
.setStandardName("Eastern Standard Time")
.setDaylightName("Eastern Daylight Time")
.setExemplarLocation("New York")
.setGmtOffset("GMT-05:00")
.setItemId(1)
.build();
return Arrays.asList(zone1, zone2);
}
// Make the method public
@Override
public BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
return super.createAdapter(timeZoneData);
}
@Override
protected Locale getLocale() {
return Locale.US;
}
@Override
public Context getContext() {
return RuntimeEnvironment.application;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import com.android.settings.datetime.timezone.model.TimeZoneData;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import libcore.util.CountryZonesFinder;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
public class FixedOffsetPickerTest {
@Test
public void getAllTimeZoneInfos_containsUtcAndGmtZones() {
List regionList = Collections.emptyList();
CountryZonesFinder finder = mock(CountryZonesFinder.class);
when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList);
FixedOffsetPicker picker = new FixedOffsetPicker() {
@Override
protected Locale getLocale() {
return Locale.US;
}
};
List<TimeZoneInfo> infos = picker.getAllTimeZoneInfos(new TimeZoneData(finder));
List<String> tzIds = infos.stream().map(info -> info.getId()).collect(Collectors.toList());
tzIds.contains("Etc/Utc");
tzIds.contains("Etc/GMT-12");
tzIds.contains("Etc/GMT+14");
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import com.android.settings.datetime.timezone.BaseTimeZoneAdapter.AdapterItem;
import com.android.settings.datetime.timezone.model.TimeZoneData;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import libcore.util.CountryZonesFinder;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
public class RegionSearchPickerTest {
@Test
public void createAdapter_matchRegionName() {
List regionList = new ArrayList();
regionList.add("US");
CountryZonesFinder finder = mock(CountryZonesFinder.class);
when(finder.lookupAllCountryIsoCodes()).thenReturn(regionList);
RegionSearchPicker picker = new RegionSearchPicker() {
@Override
protected Locale getLocale() {
return Locale.US;
}
};
BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(finder));
assertEquals(1, adapter.getItemCount());
AdapterItem item = adapter.getItem(0);
assertEquals("United States", item.getTitle().toString());
assertThat(Arrays.asList(item.getSearchKeys())).contains("United States");
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import android.icu.text.Collator;
import com.android.settings.datetime.timezone.RegionZonePicker.TimeZoneInfoComparator;
import com.android.settings.datetime.timezone.TimeZoneInfo.Formatter;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static com.google.common.truth.Truth.assertThat;
@RunWith(SettingsRobolectricTestRunner.class)
public class RegionZonePickerTest {
@Test
public void compareTimeZoneInfo_matchGmtOrder() {
Date now = new Date(0); // 00:00 1, Jan 1970
Formatter formatter = new Formatter(Locale.US, now);
TimeZoneInfo timeZone1 = formatter.format("Pacific/Honolulu");
TimeZoneInfo timeZone2 = formatter.format("America/Los_Angeles");
TimeZoneInfo timeZone3 = formatter.format("America/Indiana/Marengo");
TimeZoneInfo timeZone4 = formatter.format("America/New_York");
TimeZoneInfoComparator comparator =
new TimeZoneInfoComparator(Collator.getInstance(Locale.US), now);
// Verify the sorted order
List<TimeZoneInfo> list = Arrays.asList(timeZone4, timeZone2, timeZone3, timeZone1);
Collections.sort(list, comparator);
assertThat(list).isEqualTo(Arrays.asList(timeZone1, timeZone2, timeZone3, timeZone4));
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 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 com.android.settings.datetime.timezone;
import android.text.Spannable;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import static com.google.common.truth.Truth.assertThat;
@RunWith(SettingsRobolectricTestRunner.class)
public class SpannableUtilTest {
@Test
public void testFormat() {
Spannable spannable = SpannableUtil.getResourcesText(
RuntimeEnvironment.application.getResources(), R.string.zone_info_offset_and_name,
"GMT+00:00", "UTC");
assertThat(spannable.toString()).isEqualTo("UTC (GMT+00:00)");
}
}