Add a header view to show the country in RegionZonePicker
Extra fixes in this CL
- Minor string update in time zone picker.
Use date_time_search_region string in search bar,
and date_time_set_timezone_title string for lower case "zone".
- Fixed b/76893139. Remove the unnecessary top padding in RecyclerView.
Create a new layout file time_zone_items_list.xml without the padding.
- Add missing return statement when region ISO code
is invalid in RegionZonePicker#getAllTimeZoneInfos
Bug: 76209571
Bug: 76893139
Test: m RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.datetime.timezone
Test: Verified that the strings are updated in the UI
Change-Id: I1fb3e2abf80da3cb53cfbc3363bbe46e40a6ac22
diff --git a/res/layout/time_zone_items_list.xml b/res/layout/time_zone_items_list.xml
new file mode 100644
index 0000000..8c9dd94
--- /dev/null
+++ b/res/layout/time_zone_items_list.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical"/>
diff --git a/res/xml/time_zone_prefs.xml b/res/xml/time_zone_prefs.xml
index f80de8c..598aa65 100644
--- a/res/xml/time_zone_prefs.xml
+++ b/res/xml/time_zone_prefs.xml
@@ -27,7 +27,7 @@
android:summary="@string/summary_placeholder" />
<com.android.settingslib.RestrictedPreference
android:key="region_zone"
- android:title="@string/date_time_select_zone"
+ android:title="@string/date_time_set_timezone_title"
android:summary="@string/summary_placeholder" />
<com.android.settingslib.widget.FooterPreference/>
</PreferenceCategory>
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
index 5a614a3..f83d841 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapter.java
@@ -18,6 +18,7 @@
import android.icu.text.BreakIterator;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.support.v7.widget.RecyclerView;
@@ -40,48 +41,98 @@
* {@class AdapterItem} must be provided when an instance is created.
*/
public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
- extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
+ extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ @VisibleForTesting
+ static final int TYPE_HEADER = 0;
+ @VisibleForTesting
+ static final int TYPE_ITEM = 1;
private final List<T> mOriginalItems;
private final OnListItemClickListener<T> mOnListItemClickListener;
private final Locale mLocale;
private final boolean mShowItemSummary;
+ private final boolean mShowHeader;
+ private final CharSequence mHeaderText;
private List<T> mItems;
private ArrayFilter mFilter;
- public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T>
- onListItemClickListener, Locale locale, boolean showItemSummary) {
+ /**
+ * @param headerText the text shown in the header, or null to show no header.
+ */
+ public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener<T> onListItemClickListener,
+ Locale locale, boolean showItemSummary, @Nullable CharSequence headerText) {
mOriginalItems = items;
mItems = items;
mOnListItemClickListener = onListItemClickListener;
mLocale = locale;
mShowItemSummary = showItemSummary;
+ mShowHeader = headerText != null;
+ mHeaderText = headerText;
setHasStableIds(true);
}
@NonNull
@Override
- public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- final View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.time_zone_search_item, parent, false);
- return new ItemViewHolder(view, mOnListItemClickListener);
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ switch(viewType) {
+ case TYPE_HEADER: {
+ final View view = inflater.inflate(R.layout.preference_category_material_settings,
+ parent, false);
+ return new HeaderViewHolder(view);
+ }
+ case TYPE_ITEM: {
+ final View view = inflater.inflate(R.layout.time_zone_search_item, parent, false);
+ return new ItemViewHolder(view, mOnListItemClickListener);
+ }
+ default:
+ throw new IllegalArgumentException("Unexpected viewType: " + viewType);
+ }
}
@Override
- public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
- holder.setAdapterItem(mItems.get(position));
- holder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ if (holder instanceof HeaderViewHolder) {
+ ((HeaderViewHolder) holder).setText(mHeaderText);
+ } else if (holder instanceof ItemViewHolder) {
+ ItemViewHolder<T> itemViewHolder = (ItemViewHolder<T>) holder;
+ itemViewHolder.setAdapterItem(getDataItem(position));
+ itemViewHolder.mSummaryFrame.setVisibility(mShowItemSummary ? View.VISIBLE : View.GONE);
+ }
}
@Override
public long getItemId(int position) {
- return getItem(position).getItemId();
+ // Data item can't have negative id
+ return isPositionHeader(position) ? -1 : getDataItem(position).getItemId();
}
@Override
public int getItemCount() {
- return mItems.size();
+ return mItems.size() + getHeaderCount();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return isPositionHeader(position) ? TYPE_HEADER : TYPE_ITEM;
+ }
+
+ /*
+ * Avoid being overridden by making the method final, since constructor shouldn't invoke
+ * overridable method.
+ */
+ @Override
+ public final void setHasStableIds(boolean hasStableIds) {
+ super.setHasStableIds(hasStableIds);
+ }
+
+ private int getHeaderCount() {
+ return mShowHeader ? 1 : 0;
+ }
+
+ private boolean isPositionHeader(int position) {
+ return mShowHeader && position == 0;
}
public @NonNull ArrayFilter getFilter() {
@@ -91,8 +142,12 @@
return mFilter;
}
- public T getItem(int position) {
- return mItems.get(position);
+ /**
+ * @throws IndexOutOfBoundsException if the view type at the position is a header
+ */
+ @VisibleForTesting
+ public T getDataItem(int position) {
+ return mItems.get(position - getHeaderCount());
}
public interface AdapterItem {
@@ -100,10 +155,28 @@
CharSequence getSummary();
String getIconText();
String getCurrentTime();
+
+ /**
+ * @return unique non-negative number
+ */
long getItemId();
String[] getSearchKeys();
}
+ private static class HeaderViewHolder extends RecyclerView.ViewHolder {
+ private final TextView mTextView;
+
+ public HeaderViewHolder(View itemView) {
+ super(itemView);
+ mTextView = itemView.findViewById(android.R.id.title);
+ }
+
+ public void setText(CharSequence text) {
+ mTextView.setText(text);
+ }
+ }
+
+ @VisibleForTesting
public static class ItemViewHolder<T extends BaseTimeZoneAdapter.AdapterItem>
extends RecyclerView.ViewHolder implements View.OnClickListener {
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
index d734542..4d46c5c 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPicker.java
@@ -23,6 +23,7 @@
import android.icu.text.DateFormat;
import android.icu.text.SimpleDateFormat;
import android.icu.util.Calendar;
+import android.support.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.datetime.timezone.model.TimeZoneData;
@@ -47,10 +48,17 @@
@Override
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
mAdapter = new ZoneAdapter(getContext(), getAllTimeZoneInfos(timeZoneData),
- this::onListItemClick, getLocale());
+ this::onListItemClick, getLocale(), getHeaderText());
return mAdapter;
}
+ /**
+ * @return the text shown in the header, or null to show no header.
+ */
+ protected @Nullable CharSequence getHeaderText() {
+ return null;
+ }
+
private void onListItemClick(TimeZoneInfoItem item) {
final TimeZoneInfo timeZoneInfo = item.mTimeZoneInfo;
getActivity().setResult(Activity.RESULT_OK, prepareResultData(timeZoneInfo));
@@ -66,9 +74,11 @@
protected static class ZoneAdapter extends BaseTimeZoneAdapter<TimeZoneInfoItem> {
public ZoneAdapter(Context context, List<TimeZoneInfo> timeZones,
- OnListItemClickListener<TimeZoneInfoItem> onListItemClickListener, Locale locale) {
+ OnListItemClickListener<TimeZoneInfoItem> onListItemClickListener, Locale locale,
+ CharSequence headerText) {
super(createTimeZoneInfoItems(context, timeZones, locale),
- onListItemClickListener, locale, true /* showItemSummary */);
+ onListItemClickListener, locale, true /* showItemSummary */,
+ headerText /* headerText */);
}
private static List<TimeZoneInfoItem> createTimeZoneInfoItems(Context context,
diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
index 5150045..7bf8abc 100644
--- a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
+++ b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java
@@ -44,7 +44,7 @@
* The search matches the prefix of words in the search text.
*/
public abstract class BaseTimeZonePicker extends InstrumentedFragment
- implements SearchView.OnQueryTextListener{
+ implements SearchView.OnQueryTextListener {
public static final String EXTRA_RESULT_REGION_ID =
"com.android.settings.datetime.timezone.result_region_id";
@@ -84,7 +84,7 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.recycler_view, container, false);
+ final View view = inflater.inflate(R.layout.time_zone_items_list, container, false);
mRecyclerView = view.findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
diff --git a/src/com/android/settings/datetime/timezone/RegionSearchPicker.java b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
index ca4e0bc..134b6ac 100644
--- a/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
+++ b/src/com/android/settings/datetime/timezone/RegionSearchPicker.java
@@ -48,7 +48,7 @@
private TimeZoneData mTimeZoneData;
public RegionSearchPicker() {
- super(R.string.date_time_select_region, R.string.search_settings, true, true);
+ super(R.string.date_time_select_region, R.string.date_time_search_region, true, true);
}
@Override
@@ -60,7 +60,8 @@
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
mTimeZoneData = timeZoneData;
mAdapter = new BaseTimeZoneAdapter<>(createAdapterItem(timeZoneData.getRegionIds()),
- this::onListItemClick, getLocale(), false);
+ this::onListItemClick, getLocale(), false /* showItemSummary */,
+ null /* headerText */);
return mAdapter;
}
diff --git a/src/com/android/settings/datetime/timezone/RegionZonePicker.java b/src/com/android/settings/datetime/timezone/RegionZonePicker.java
index 37365a8..add50b8 100644
--- a/src/com/android/settings/datetime/timezone/RegionZonePicker.java
+++ b/src/com/android/settings/datetime/timezone/RegionZonePicker.java
@@ -18,7 +18,10 @@
import android.content.Intent;
import android.icu.text.Collator;
+import android.icu.text.LocaleDisplayNames;
import android.icu.util.TimeZone;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -43,8 +46,10 @@
public static final String EXTRA_REGION_ID =
"com.android.settings.datetime.timezone.region_id";
+ private @Nullable String mRegionName;
+
public RegionZonePicker() {
- super(R.string.date_time_select_zone, R.string.search_settings, true, false);
+ super(R.string.date_time_set_timezone_title, R.string.search_settings, true, false);
}
@Override
@@ -52,6 +57,21 @@
return MetricsProto.MetricsEvent.SETTINGS_ZONE_PICKER_TIME_ZONE;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale());
+ final String regionId =
+ getArguments() == null ? null : getArguments().getString(EXTRA_REGION_ID);
+ mRegionName = regionId == null ? null : localeDisplayNames.regionDisplayName(regionId);
+ }
+
+ @Override
+ protected @Nullable CharSequence getHeaderText() {
+ return mRegionName;
+ }
+
/**
* Add the extra region id into the result.
*/
@@ -67,6 +87,7 @@
if (getArguments() == null) {
Log.e(TAG, "getArguments() == null");
getActivity().finish();
+ return Collections.emptyList();
}
String regionId = getArguments().getString(EXTRA_REGION_ID);
@@ -75,6 +96,7 @@
if (filteredCountryTimeZones == null) {
Log.e(TAG, "region id is not valid: " + regionId);
getActivity().finish();
+ return Collections.emptyList();
}
// It could be a timely operations if there are many time zones. A region in time zone data
diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java
index c85c598..a240646 100644
--- a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneAdapterTest.java
@@ -62,7 +62,7 @@
observer.await();
assertThat(adapter.getItemCount()).isEqualTo(items.length);
for (int i = 0; i < items.length; i++) {
- assertThat(adapter.getItem(i)).isEqualTo(items[i]);
+ assertThat(adapter.getDataItem(i)).isEqualTo(items[i]);
}
}
@@ -90,7 +90,8 @@
private static class TestTimeZoneAdapter extends BaseTimeZoneAdapter<TestItem> {
public TestTimeZoneAdapter(List<TestItem> items) {
- super(items, position -> {}, Locale.US, false);
+ super(items, position -> {}, Locale.US, false /* showItemSummary */,
+ null /* headerText */);
}
}
diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java
index 3491b03..ef80968 100644
--- a/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/timezone/BaseTimeZoneInfoPickerTest.java
@@ -61,13 +61,13 @@
BaseTimeZoneAdapter adapter = picker.createAdapter(mock(TimeZoneData.class));
Truth.assertThat(adapter.getItemCount()).isEqualTo(2);
- BaseTimeZoneAdapter.AdapterItem item1 = adapter.getItem(0);
+ BaseTimeZoneAdapter.AdapterItem item1 = adapter.getDataItem(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);
+ BaseTimeZoneAdapter.AdapterItem item2 = adapter.getDataItem(1);
Truth.assertThat(item2.getTitle().toString()).isEqualTo("New York");
Truth.assertThat(item2.getSummary().toString()).isEqualTo("Eastern Time (GMT-05:00)");
Truth.assertThat(item2.getCurrentTime())
diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java
index 007d3c5..9d650cc 100644
--- a/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/timezone/FixedOffsetPickerTest.java
@@ -65,10 +65,10 @@
TestFixedOffsetPicker picker = new TestFixedOffsetPicker();
BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(mFinder));
assertThat(adapter.getItemCount()).isEqualTo(12 + 1 + 14); // 27 GMT offsets from -12 to +14
- AdapterItem utc = adapter.getItem(0);
+ AdapterItem utc = adapter.getDataItem(0);
assertThat(utc.getTitle().toString()).isEqualTo("Coordinated Universal Time");
assertThat(utc.getSummary().toString()).isEqualTo("GMT+00:00");
- AdapterItem gmtMinus12 = adapter.getItem(1);
+ AdapterItem gmtMinus12 = adapter.getDataItem(1);
assertThat(gmtMinus12.getTitle().toString()).isEqualTo("GMT-12:00");
assertThat(gmtMinus12.getSummary().toString()).isEmpty();
}
diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
index 8da9cbf..02a3122 100644
--- a/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
+++ b/tests/robotests/src/com/android/settings/datetime/timezone/RegionSearchPickerTest.java
@@ -66,7 +66,7 @@
RegionSearchPicker picker = new RegionSearchPicker();
BaseTimeZoneAdapter adapter = picker.createAdapter(new TimeZoneData(finder));
assertEquals(1, adapter.getItemCount());
- AdapterItem item = adapter.getItem(0);
+ AdapterItem item = adapter.getDataItem(0);
assertEquals("United States", item.getTitle().toString());
assertThat(Arrays.asList(item.getSearchKeys())).contains("United States");
}
@@ -86,8 +86,8 @@
RegionSearchPicker picker = new RegionSearchPicker();
BaseTimeZoneAdapter<RegionItem> adapter = picker.createAdapter(new TimeZoneData(finder));
// Prepare and bind a new ItemViewHolder with United States
- ItemViewHolder viewHolder = adapter.onCreateViewHolder(
- new LinearLayout(RuntimeEnvironment.application), 0);
+ ItemViewHolder viewHolder = (ItemViewHolder) adapter.onCreateViewHolder(
+ new LinearLayout(RuntimeEnvironment.application), BaseTimeZoneAdapter.TYPE_ITEM);
adapter.onBindViewHolder(viewHolder, 0);
assertEquals(1, adapter.getItemCount());