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());