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:
99
res/layout/time_zone_search_item.xml
Normal file
99
res/layout/time_zone_search_item.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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.
|
||||
-->
|
||||
<!-- similar to preference_material.xml but textview for emoji country flag
|
||||
instead of an ImageView -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/icon_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="-4dp"
|
||||
android:minWidth="60dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
<!-- It's not ImageView because the icon is Unicode emoji. -->
|
||||
<TextView
|
||||
android:id="@+id/icon_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:contentDescription=""
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:importantForAccessibility="no"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView android:id="@android:id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/Preference_TextAppearanceMaterialSubhead"
|
||||
android:ellipsize="marquee" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/summary_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@android:id/title"
|
||||
android:layout_alignLeft="@android:id/title">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<!-- Use layout_alignParentStart and layout_toStartOf to make the TextView multi-lines
|
||||
if needed -->
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="10"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@+id/current_time"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
26
res/menu/time_zone_base_search_menu.xml
Normal file
26
res/menu/time_zone_base_search_menu.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/time_zone_search_menu"
|
||||
android:title="@string/search_settings"
|
||||
android:icon="@*android:drawable/ic_search_api_material"
|
||||
android:showAsAction="always|collapseActionView"
|
||||
android:actionViewClass="android.widget.SearchView" />
|
||||
|
||||
</menu>
|
@@ -748,20 +748,34 @@
|
||||
<string name="date_time_set_date_title">Date</string>
|
||||
<!-- Date & time setting screen setting option title -->
|
||||
<string name="date_time_set_date">Set date</string>
|
||||
<!-- Setting option title to select region in time zone setting screen [CHAR LIMIT=30] -->
|
||||
<string name="date_time_select_region">Region</string>
|
||||
<!-- Setting option title to select time zone in time zone setting screen [CHAR LIMIT=30]-->
|
||||
<string name="date_time_select_zone">Time Zone</string>
|
||||
<!-- Setting option title in time zone setting screen [CHAR LIMIT=30] -->
|
||||
<string name="date_time_select_fixed_offset_time_zones">Select UTC offset</string>
|
||||
<!-- Menu item on Select time zone screen -->
|
||||
<string name="zone_list_menu_sort_alphabetically">Sort alphabetically</string>
|
||||
<!-- Menu item on Select time zone screen -->
|
||||
<string name="zone_list_menu_sort_by_timezone">Sort by time zone</string>
|
||||
<!-- Label describing when a given time zone changes to DST or standard time -->
|
||||
<string name="zone_change_to_from_dst"><xliff:g id="time_type" example="Pacific Summer Time">%1$s</xliff:g> starts on <xliff:g id="transition_date" example="Mar 11 2018">%2$s</xliff:g>.</string>
|
||||
<!-- Label describing a exemplar location and time zone offset[CHAR LIMIT=NONE] -->
|
||||
<string name="zone_info_exemplar_location_and_offset"><xliff:g id="exemplar_location" example="Los Angeles">%1$s</xliff:g> (<xliff:g id="offset" example="GMT-08:00">%2$s</xliff:g>)</string>
|
||||
<!-- Label describing a time zone offset and name[CHAR LIMIT=NONE] -->
|
||||
<string name="zone_info_offset_and_name"><xliff:g id="time_type" example="Pacific Time">%2$s</xliff:g> (<xliff:g id="offset" example="GMT-08:00">%1$s</xliff:g>)</string>
|
||||
<!-- Label describing a time zone and changes to DST or standard time [CHAR LIMIT=NONE] -->
|
||||
<string name="zone_info_footer">Uses <xliff:g id="offset_and_name" example="Pacific Time (GMT-08:00)">%1$s</xliff:g>. <xliff:g id="dst_time_type" example="Pacific Daylight Time">%2$s</xliff:g> starts on <xliff:g id="transition_date" example="Mar 11 2018">%3$s</xliff:g>.</string>
|
||||
<!-- Label describing a time zone without DST [CHAR LIMIT=NONE] -->
|
||||
<string name="zone_info_footer_no_dst">Uses <xliff:g id="offset_and_name" example="GMT-08:00 Pacific Time">%1$s</xliff:g>. No daylight savings time.</string>
|
||||
<!-- Describes the time type "daylight savings time" (used in zone_change_to_from_dst, when no zone specific name is available) -->
|
||||
<string name="zone_time_type_dst">Daylight savings time</string>
|
||||
<!-- Describes the time type "standard time" (used in zone_change_to_from_dst, when no zone specific name is available) -->
|
||||
<string name="zone_time_type_standard">Standard time</string>
|
||||
<!-- The menu item to switch to selecting a time zone by region (default) -->
|
||||
<string name="zone_menu_by_region">Time zone by region</string>
|
||||
<string name="zone_menu_by_region">Show time zones by region</string>
|
||||
<!-- The menu item to switch to selecting a time zone with a fixed offset (such as UTC or GMT+0200) -->
|
||||
<string name="zone_menu_by_offset">Fixed offset time zones</string>
|
||||
<string name="zone_menu_by_offset">Show time zones by UTC offset</string>
|
||||
|
||||
<!-- Title string shown above DatePicker, letting a user select system date
|
||||
[CHAR LIMIT=20] -->
|
||||
|
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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.BreakIterator;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.datetime.timezone.BaseTimeZonePicker.OnListItemClickListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Used with {@class BaseTimeZonePicker}. It renders text in each item into list view. A list of
|
||||
* {@class AdapterItem} must be provided when an instance is created.
|
||||
*/
|
||||
public class BaseTimeZoneAdapter<T extends BaseTimeZoneAdapter.AdapterItem>
|
||||
extends RecyclerView.Adapter<BaseTimeZoneAdapter.ItemViewHolder> {
|
||||
|
||||
private final List<T> mOriginalItems;
|
||||
private final OnListItemClickListener mOnListItemClickListener;
|
||||
private final Locale mLocale;
|
||||
private final boolean mShowItemSummary;
|
||||
|
||||
private List<T> mItems;
|
||||
private ArrayFilter mFilter;
|
||||
|
||||
public BaseTimeZoneAdapter(List<T> items, OnListItemClickListener
|
||||
onListItemClickListener, Locale locale, boolean showItemSummary) {
|
||||
mOriginalItems = items;
|
||||
mItems = items;
|
||||
mOnListItemClickListener = onListItemClickListener;
|
||||
mLocale = locale;
|
||||
mShowItemSummary = showItemSummary;
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
|
||||
final AdapterItem item = mItems.get(position);
|
||||
holder.mSummaryFrame.setVisibility(
|
||||
mShowItemSummary ? View.VISIBLE : View.GONE);
|
||||
holder.mTitleView.setText(item.getTitle());
|
||||
holder.mIconTextView.setText(item.getIconText());
|
||||
holder.mSummaryView.setText(item.getSummary());
|
||||
holder.mTimeView.setText(item.getCurrentTime());
|
||||
holder.setPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).getItemId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
public @NonNull
|
||||
Filter getFilter() {
|
||||
if (mFilter == null) {
|
||||
mFilter = new ArrayFilter();
|
||||
}
|
||||
return mFilter;
|
||||
}
|
||||
|
||||
public T getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
public interface AdapterItem {
|
||||
CharSequence getTitle();
|
||||
CharSequence getSummary();
|
||||
String getIconText();
|
||||
String getCurrentTime();
|
||||
long getItemId();
|
||||
String[] getSearchKeys();
|
||||
}
|
||||
|
||||
public static class ItemViewHolder extends RecyclerView.ViewHolder
|
||||
implements View.OnClickListener{
|
||||
|
||||
final OnListItemClickListener mOnListItemClickListener;
|
||||
final View mSummaryFrame;
|
||||
final TextView mTitleView;
|
||||
final TextView mIconTextView;
|
||||
final TextView mSummaryView;
|
||||
final TextView mTimeView;
|
||||
private int mPosition;
|
||||
|
||||
public ItemViewHolder(View itemView, OnListItemClickListener onListItemClickListener) {
|
||||
super(itemView);
|
||||
itemView.setOnClickListener(this);
|
||||
mSummaryFrame = itemView.findViewById(R.id.summary_frame);
|
||||
mTitleView = itemView.findViewById(android.R.id.title);
|
||||
mIconTextView = itemView.findViewById(R.id.icon_text);
|
||||
mSummaryView = itemView.findViewById(android.R.id.summary);
|
||||
mTimeView = itemView.findViewById(R.id.current_time);
|
||||
mOnListItemClickListener = onListItemClickListener;
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mOnListItemClickListener.onListItemClick(mPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>An array filter constrains the content of the array adapter with
|
||||
* a prefix. Each item that does not start with the supplied prefix
|
||||
* is removed from the list.</p>
|
||||
*
|
||||
* The filtering operation is not optimized, due to small data size (~260 regions),
|
||||
* require additional pre-processing. Potentially, a trie structure can be used to match
|
||||
* prefixes of the search keys.
|
||||
*/
|
||||
private class ArrayFilter extends Filter {
|
||||
|
||||
private BreakIterator mBreakIterator = BreakIterator.getWordInstance(mLocale);
|
||||
|
||||
@WorkerThread
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence prefix) {
|
||||
final List<T> newItems;
|
||||
if (TextUtils.isEmpty(prefix)) {
|
||||
newItems = mOriginalItems;
|
||||
} else {
|
||||
final String prefixString = prefix.toString().toLowerCase(mLocale);
|
||||
newItems = new ArrayList<>();
|
||||
|
||||
for (T item : mOriginalItems) {
|
||||
outer:
|
||||
for (String searchKey : item.getSearchKeys()) {
|
||||
searchKey = searchKey.toLowerCase(mLocale);
|
||||
// First match against the whole, non-splitted value
|
||||
if (searchKey.startsWith(prefixString)) {
|
||||
newItems.add(item);
|
||||
break outer;
|
||||
} else {
|
||||
mBreakIterator.setText(searchKey);
|
||||
for (int wordStart = 0, wordLimit = mBreakIterator.next();
|
||||
wordLimit != BreakIterator.DONE;
|
||||
wordStart = wordLimit,
|
||||
wordLimit = mBreakIterator.next()) {
|
||||
if (mBreakIterator.getRuleStatus() != BreakIterator.WORD_NONE
|
||||
&& searchKey.startsWith(prefixString, wordStart)) {
|
||||
newItems.add(item);
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final FilterResults results = new FilterResults();
|
||||
results.values = newItems;
|
||||
results.count = newItems.size();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
mItems = (List<T>) results.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.icu.text.DateFormat;
|
||||
import android.icu.text.SimpleDateFormat;
|
||||
import android.icu.util.Calendar;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Render a list of {@class TimeZoneInfo} into the list view in {@class BaseTimeZonePicker}
|
||||
*/
|
||||
public abstract class BaseTimeZoneInfoPicker extends BaseTimeZonePicker {
|
||||
protected static final String TAG = "RegionZoneSearchPicker";
|
||||
protected ZoneAdapter mAdapter;
|
||||
|
||||
protected BaseTimeZoneInfoPicker(int titleResId, int searchHintResId,
|
||||
boolean searchEnabled, boolean defaultExpandSearch) {
|
||||
super(titleResId, searchHintResId, searchEnabled, defaultExpandSearch);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
|
||||
mAdapter = new ZoneAdapter(getContext(), getAllTimeZoneInfos(timeZoneData),
|
||||
this::onListItemClick, getLocale());
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
private void onListItemClick(int position) {
|
||||
final TimeZoneInfo timeZoneInfo = mAdapter.getItem(position).mTimeZoneInfo;
|
||||
getActivity().setResult(Activity.RESULT_OK, prepareResultData(timeZoneInfo));
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
protected Intent prepareResultData(TimeZoneInfo selectedTimeZoneInfo) {
|
||||
return new Intent().putExtra(EXTRA_RESULT_TIME_ZONE_ID, selectedTimeZoneInfo.getId());
|
||||
}
|
||||
|
||||
public abstract List<TimeZoneInfo> getAllTimeZoneInfos(TimeZoneData timeZoneData);
|
||||
|
||||
protected static class ZoneAdapter extends BaseTimeZoneAdapter<TimeZoneInfoItem> {
|
||||
|
||||
public ZoneAdapter(Context context, List<TimeZoneInfo> timeZones,
|
||||
OnListItemClickListener onListItemClickListener, Locale locale) {
|
||||
super(createTimeZoneInfoItems(context, timeZones, locale),
|
||||
onListItemClickListener, locale, true /* showItemSummary */);
|
||||
}
|
||||
|
||||
private static List<TimeZoneInfoItem> createTimeZoneInfoItems(Context context,
|
||||
List<TimeZoneInfo> timeZones, Locale locale) {
|
||||
final DateFormat currentTimeFormat = new SimpleDateFormat(
|
||||
android.text.format.DateFormat.getTimeFormatString(context), locale);
|
||||
final ArrayList<TimeZoneInfoItem> results = new ArrayList<>(timeZones.size());
|
||||
final Resources resources = context.getResources();
|
||||
long i = 0;
|
||||
for (TimeZoneInfo timeZone : timeZones) {
|
||||
results.add(new TimeZoneInfoItem(i++, timeZone, resources, currentTimeFormat));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TimeZoneInfoItem implements BaseTimeZoneAdapter.AdapterItem {
|
||||
private final long mItemId;
|
||||
private final TimeZoneInfo mTimeZoneInfo;
|
||||
private final Resources mResources;
|
||||
private final DateFormat mTimeFormat;
|
||||
private final String mTitle;
|
||||
private final String[] mSearchKeys;
|
||||
|
||||
private TimeZoneInfoItem(long itemId, TimeZoneInfo timeZoneInfo, Resources resources,
|
||||
DateFormat timeFormat) {
|
||||
mItemId = itemId;
|
||||
mTimeZoneInfo = timeZoneInfo;
|
||||
mResources = resources;
|
||||
mTimeFormat = timeFormat;
|
||||
mTitle = createTitle(timeZoneInfo);
|
||||
mSearchKeys = new String[] { mTitle };
|
||||
}
|
||||
|
||||
private static String createTitle(TimeZoneInfo timeZoneInfo) {
|
||||
String name = timeZoneInfo.getExemplarLocation();
|
||||
if (name == null) {
|
||||
name = timeZoneInfo.getGenericName();
|
||||
}
|
||||
if (name == null && timeZoneInfo.getTimeZone().inDaylightTime(new Date())) {
|
||||
name = timeZoneInfo.getDaylightName();
|
||||
}
|
||||
if (name == null) {
|
||||
name = timeZoneInfo.getStandardName();
|
||||
}
|
||||
if (name == null) {
|
||||
name = String.valueOf(timeZoneInfo.getGmtOffset());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
String name = mTimeZoneInfo.getGenericName();
|
||||
if (name == null) {
|
||||
if (mTimeZoneInfo.getTimeZone().inDaylightTime(new Date())) {
|
||||
name = mTimeZoneInfo.getDaylightName();
|
||||
} else {
|
||||
name = mTimeZoneInfo.getStandardName();
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
return mTimeZoneInfo.getGmtOffset();
|
||||
} else {
|
||||
return SpannableUtil.getResourcesText(mResources,
|
||||
R.string.zone_info_offset_and_name, mTimeZoneInfo.getGmtOffset(), name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentTime() {
|
||||
return mTimeFormat.format(Calendar.getInstance(mTimeZoneInfo.getTimeZone()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId() {
|
||||
return mItemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSearchKeys() {
|
||||
return mSearchKeys;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneDataLoader;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* It's abstract class. Subclass should use it with {@class BaseTimeZoneAdapter} and
|
||||
* {@class AdapterItem} to provide a list view with text search capability.
|
||||
* The search matches the prefix of words in the search text.
|
||||
*/
|
||||
public abstract class BaseTimeZonePicker extends InstrumentedFragment
|
||||
implements SearchView.OnQueryTextListener{
|
||||
|
||||
public static final String EXTRA_RESULT_REGION_ID =
|
||||
"com.android.settings.datetime.timezone.result_region_id";
|
||||
public static final String EXTRA_RESULT_TIME_ZONE_ID =
|
||||
"com.android.settings.datetime.timezone.result_time_zone_id";
|
||||
private final int mTitleResId;
|
||||
private final int mSearchHintResId;
|
||||
private final boolean mSearchEnabled;
|
||||
private final boolean mDefaultExpandSearch;
|
||||
|
||||
protected Locale mLocale;
|
||||
private BaseTimeZoneAdapter mAdapter;
|
||||
private RecyclerView mRecyclerView;
|
||||
private TimeZoneData mTimeZoneData;
|
||||
|
||||
private SearchView mSearchView;
|
||||
|
||||
/**
|
||||
* Constructor called by subclass.
|
||||
* @param defaultExpandSearch whether expand the search view when first launching the fragment
|
||||
*/
|
||||
protected BaseTimeZonePicker(int titleResId, int searchHintResId,
|
||||
boolean searchEnabled, boolean defaultExpandSearch) {
|
||||
mTitleResId = titleResId;
|
||||
mSearchHintResId = searchHintResId;
|
||||
mSearchEnabled = searchEnabled;
|
||||
mDefaultExpandSearch = defaultExpandSearch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
getActivity().setTitle(mTitleResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.recycler_view, container, false);
|
||||
mRecyclerView = view.findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
|
||||
LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
// Initialize TimeZoneDataLoader only when mRecyclerView is ready to avoid race
|
||||
// during onDateLoaderReady callback.
|
||||
getLoaderManager().initLoader(0, null, new TimeZoneDataLoader.LoaderCreator(
|
||||
getContext(), this::onTimeZoneDataReady));
|
||||
return view;
|
||||
}
|
||||
|
||||
public void onTimeZoneDataReady(TimeZoneData timeZoneData) {
|
||||
if (mTimeZoneData == null && timeZoneData != null) {
|
||||
mTimeZoneData = timeZoneData;
|
||||
mAdapter = createAdapter(mTimeZoneData);
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Locale getLocale() {
|
||||
return getContext().getResources().getConfiguration().getLocales().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when TimeZoneData is ready.
|
||||
*/
|
||||
protected abstract BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData);
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (mSearchEnabled) {
|
||||
inflater.inflate(R.menu.time_zone_base_search_menu, menu);
|
||||
|
||||
final MenuItem searchMenuItem = menu.findItem(R.id.time_zone_search_menu);
|
||||
mSearchView = (SearchView) searchMenuItem.getActionView();
|
||||
|
||||
mSearchView.setQueryHint(getText(mSearchHintResId));
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
|
||||
if (mDefaultExpandSearch) {
|
||||
searchMenuItem.expandActionView();
|
||||
mSearchView.setIconified(false);
|
||||
mSearchView.setActivated(true);
|
||||
mSearchView.setQuery("", true /* submit */);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.getFilter().filter(newText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
// TODO: use a new metrics id?
|
||||
return MetricsEvent.ZONE_PICKER;
|
||||
}
|
||||
|
||||
public interface OnListItemClickListener {
|
||||
void onListItemClick(int position);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.util.TimeZone;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Render a list of fixed offset time zone {@class TimeZoneInfo} into a list view.
|
||||
*/
|
||||
public class FixedOffsetPicker extends BaseTimeZoneInfoPicker {
|
||||
/**
|
||||
* Range of integer fixed UTC offsets shown in the pickers.
|
||||
*/
|
||||
private static final int MIN_HOURS_OFFSET = -14;
|
||||
private static final int MAX_HOURS_OFFSET = +12;
|
||||
|
||||
public FixedOffsetPicker() {
|
||||
super(R.string.date_time_select_fixed_offset_time_zones,
|
||||
R.string.search_settings, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TimeZoneInfo> getAllTimeZoneInfos(TimeZoneData timeZoneData) {
|
||||
return loadFixedOffsets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TimeZoneInfo} for each fixed offset time zone, such as UTC or GMT+4. The
|
||||
* returned list will be sorted in a reasonable way for display.
|
||||
*/
|
||||
private List<TimeZoneInfo> loadFixedOffsets() {
|
||||
final TimeZoneInfo.Formatter formatter = new TimeZoneInfo.Formatter(getLocale(),
|
||||
new Date());
|
||||
final List<TimeZoneInfo> timeZoneInfos = new ArrayList<>();
|
||||
timeZoneInfos.add(formatter.format(TimeZone.getFrozenTimeZone("Etc/UTC")));
|
||||
for (int hoursOffset = MAX_HOURS_OFFSET; hoursOffset >= MIN_HOURS_OFFSET; --hoursOffset) {
|
||||
if (hoursOffset == 0) {
|
||||
// UTC is handled above, so don't add GMT +/-0 again.
|
||||
continue;
|
||||
}
|
||||
final String id = String.format(Locale.US, "Etc/GMT%+d", hoursOffset);
|
||||
timeZoneInfos.add(formatter.format(TimeZone.getFrozenTimeZone(id)));
|
||||
}
|
||||
return Collections.unmodifiableList(timeZoneInfos);
|
||||
}
|
||||
}
|
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Paint;
|
||||
import android.icu.text.Collator;
|
||||
import android.icu.text.LocaleDisplayNames;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Render a list of regions into a list view.
|
||||
*/
|
||||
public class RegionSearchPicker extends BaseTimeZonePicker {
|
||||
private static final int REQUEST_CODE_ZONE_PICKER = 1;
|
||||
private static final String TAG = "RegionSearchPicker";
|
||||
|
||||
private BaseTimeZoneAdapter<RegionItem> mAdapter;
|
||||
private TimeZoneData mTimeZoneData;
|
||||
|
||||
public RegionSearchPicker() {
|
||||
super(R.string.date_time_select_region, R.string.search_settings, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData) {
|
||||
mTimeZoneData = timeZoneData;
|
||||
mAdapter = new BaseTimeZoneAdapter<>(createAdapterItem(timeZoneData.getRegionIds()),
|
||||
this::onListItemClick, getLocale(), false);
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
private void onListItemClick(int position) {
|
||||
final String regionId = mAdapter.getItem(position).getId();
|
||||
final FilteredCountryTimeZones countryTimeZones = mTimeZoneData.lookupCountryTimeZones(
|
||||
regionId);
|
||||
final Activity activity = getActivity();
|
||||
if (countryTimeZones == null || countryTimeZones.getTimeZoneIds().isEmpty()) {
|
||||
Log.e(TAG, "Region has no time zones: " + regionId);
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
activity.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> timeZoneIds = countryTimeZones.getTimeZoneIds();
|
||||
// Choose the time zone associated the region if there is only one time zone in that region
|
||||
if (timeZoneIds.size() == 1) {
|
||||
final Intent resultData = new Intent()
|
||||
.putExtra(EXTRA_RESULT_REGION_ID, regionId)
|
||||
.putExtra(EXTRA_RESULT_TIME_ZONE_ID, timeZoneIds.get(0));
|
||||
getActivity().setResult(Activity.RESULT_OK, resultData);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
// Launch the zone picker and let the user choose a time zone from the list of
|
||||
// time zones associated with the region.
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(RegionZonePicker.EXTRA_REGION_ID, regionId);
|
||||
new SubSettingLauncher(getContext())
|
||||
.setDestination(RegionZonePicker.class.getCanonicalName())
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(getMetricsCategory())
|
||||
.setResultListener(this, REQUEST_CODE_ZONE_PICKER)
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_CODE_ZONE_PICKER) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
getActivity().setResult(Activity.RESULT_OK, data);
|
||||
}
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
private List<RegionItem> createAdapterItem(Set<String> regionIds) {
|
||||
final Collator collator = Collator.getInstance(getLocale());
|
||||
final TreeSet<RegionItem> items = new TreeSet<>(new RegionInfoComparator(collator));
|
||||
final Paint paint = new Paint();
|
||||
final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale());
|
||||
long i = 0;
|
||||
for (String regionId : regionIds) {
|
||||
String name = localeDisplayNames.regionDisplayName(regionId);
|
||||
String regionalIndicator = createRegionalIndicator(regionId, paint);
|
||||
items.add(new RegionItem(i++, regionId, name, regionalIndicator));
|
||||
}
|
||||
return new ArrayList<>(items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Unicode Region Indicator Symbol for a given region id (a.k.a flag emoji). If the
|
||||
* system can't render a flag for this region or the input is not a region id, this returns
|
||||
* {@code null}.
|
||||
*
|
||||
* @param id the two-character region id.
|
||||
* @param paint Paint contains the glyph
|
||||
* @return a String representing the flag of the region or {@code null}.
|
||||
*/
|
||||
private static String createRegionalIndicator(String id, Paint paint) {
|
||||
if (id.length() != 2) {
|
||||
return null;
|
||||
}
|
||||
final char c1 = id.charAt(0);
|
||||
final char c2 = id.charAt(1);
|
||||
if ('A' > c1 || c1 > 'Z' || 'A' > c2 || c2 > 'Z') {
|
||||
return null;
|
||||
}
|
||||
// Regional Indicator A is U+1F1E6 which is 0xD83C 0xDDE6 in UTF-16.
|
||||
final String regionalIndicator = new String(
|
||||
new char[]{0xd83c, (char) (0xdde6 - 'A' + c1), 0xd83c, (char) (0xdde6 - 'A' + c2)});
|
||||
if (!paint.hasGlyph(regionalIndicator)) {
|
||||
return null;
|
||||
}
|
||||
return regionalIndicator;
|
||||
}
|
||||
|
||||
private static class RegionItem implements BaseTimeZoneAdapter.AdapterItem {
|
||||
|
||||
private final String mId;
|
||||
private final String mName;
|
||||
private final String mRegionalIndicator;
|
||||
private final long mItemId;
|
||||
private final String[] mSearchKeys;
|
||||
|
||||
RegionItem(long itemId, String id, String name, String regionalIndicator) {
|
||||
mId = id;
|
||||
mName = name;
|
||||
mRegionalIndicator = regionalIndicator;
|
||||
mItemId = itemId;
|
||||
// Allow to search with ISO_3166-1 alpha-2 code. It's handy for english users in some
|
||||
// countries, e.g. US for United States. It's not best search keys for users, but
|
||||
// ICU doesn't have the data for the alias names of a region.
|
||||
mSearchKeys = new String[] {mId, mName};
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconText() {
|
||||
return mRegionalIndicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentTime() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId() {
|
||||
return mItemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSearchKeys() {
|
||||
return mSearchKeys;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RegionInfoComparator implements Comparator<RegionItem> {
|
||||
private final Collator mCollator;
|
||||
|
||||
RegionInfoComparator(Collator collator) {
|
||||
mCollator = collator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(RegionItem r1, RegionItem r2) {
|
||||
return mCollator.compare(r1.getTitle(), r2.getTitle());
|
||||
}
|
||||
}
|
||||
}
|
133
src/com/android/settings/datetime/timezone/RegionZonePicker.java
Normal file
133
src/com/android/settings/datetime/timezone/RegionZonePicker.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.Intent;
|
||||
import android.icu.text.Collator;
|
||||
import android.icu.util.TimeZone;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones;
|
||||
import com.android.settings.datetime.timezone.model.TimeZoneData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Given a region, render a list of time zone {@class TimeZoneInfo} into a list view.
|
||||
*/
|
||||
public class RegionZonePicker extends BaseTimeZoneInfoPicker {
|
||||
|
||||
public static final String EXTRA_REGION_ID =
|
||||
"com.android.settings.datetime.timezone.region_id";
|
||||
|
||||
public RegionZonePicker() {
|
||||
super(R.string.date_time_select_zone, R.string.search_settings, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the extra region id into the result.
|
||||
*/
|
||||
@Override
|
||||
protected Intent prepareResultData(TimeZoneInfo selectedTimeZoneInfo) {
|
||||
final Intent intent = super.prepareResultData(selectedTimeZoneInfo);
|
||||
intent.putExtra(EXTRA_RESULT_REGION_ID, getArguments().getString(EXTRA_REGION_ID));
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TimeZoneInfo> getAllTimeZoneInfos(TimeZoneData timeZoneData) {
|
||||
if (getArguments() == null) {
|
||||
Log.e(TAG, "getArguments() == null");
|
||||
getActivity().finish();
|
||||
}
|
||||
String regionId = getArguments().getString(EXTRA_REGION_ID);
|
||||
|
||||
FilteredCountryTimeZones filteredCountryTimeZones = timeZoneData.lookupCountryTimeZones(
|
||||
regionId);
|
||||
if (filteredCountryTimeZones == null) {
|
||||
Log.e(TAG, "region id is not valid: " + regionId);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
// It could be a timely operations if there are many time zones. A region in time zone data
|
||||
// contains a maximum of 29 time zones currently. It may change in the future, but it's
|
||||
// unlikely to be changed drastically.
|
||||
return getRegionTimeZoneInfo(filteredCountryTimeZones.getTimeZoneIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link TimeZoneInfo} objects. The returned list will be sorted properly for
|
||||
* display in the locale.It may be smaller than the input collection, if equivalent IDs are
|
||||
* passed in.
|
||||
*
|
||||
* @param timeZoneIds a list of Olson IDs.
|
||||
*/
|
||||
public List<TimeZoneInfo> getRegionTimeZoneInfo(Collection<String> timeZoneIds) {
|
||||
final TimeZoneInfo.Formatter formatter = new TimeZoneInfo.Formatter(getLocale(),
|
||||
new Date());
|
||||
final TreeSet<TimeZoneInfo> timeZoneInfos =
|
||||
new TreeSet<>(new TimeZoneInfoComparator(Collator.getInstance(getLocale()),
|
||||
new Date()));
|
||||
|
||||
for (final String timeZoneId : timeZoneIds) {
|
||||
final TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId);
|
||||
// Skip time zone ICU isn't aware.
|
||||
if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
|
||||
continue;
|
||||
}
|
||||
timeZoneInfos.add(formatter.format(timeZone));
|
||||
}
|
||||
return Collections.unmodifiableList(new ArrayList<>(timeZoneInfos));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class TimeZoneInfoComparator implements Comparator<TimeZoneInfo> {
|
||||
private Collator mCollator;
|
||||
private final Date mNow;
|
||||
|
||||
@VisibleForTesting
|
||||
TimeZoneInfoComparator(Collator collator, Date now) {
|
||||
mCollator = collator;
|
||||
mNow = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(TimeZoneInfo tzi1, TimeZoneInfo tzi2) {
|
||||
int result = Integer.compare(tzi1.getTimeZone().getOffset(mNow.getTime()),
|
||||
tzi2.getTimeZone().getOffset(mNow.getTime()));
|
||||
if (result == 0) {
|
||||
result = Integer.compare(tzi1.getTimeZone().getRawOffset(),
|
||||
tzi2.getTimeZone().getRawOffset());
|
||||
}
|
||||
if (result == 0) {
|
||||
result = mCollator.compare(tzi1.getExemplarLocation(), tzi2.getExemplarLocation());
|
||||
}
|
||||
if (result == 0 && tzi1.getGenericName() != null && tzi2.getGenericName() != null) {
|
||||
result = mCollator.compare(tzi1.getGenericName(), tzi2.getGenericName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.annotation.StringRes;
|
||||
import android.content.res.Resources;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class SpannableUtil {
|
||||
|
||||
/**
|
||||
* {@class Resources} has no method to format string resource with {@class Spannable} a
|
||||
* rguments. It's a helper method for this purpose.
|
||||
*/
|
||||
public static Spannable getResourcesText(Resources res, @StringRes int resId,
|
||||
Object... args) {
|
||||
final Locale locale = res.getConfiguration().getLocales().get(0);
|
||||
final SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
new Formatter(builder, locale).format(res.getString(resId), args);
|
||||
return builder;
|
||||
}
|
||||
}
|
@@ -57,7 +57,7 @@ public class TimeZoneData {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TimeZoneData(CountryZonesFinder countryZonesFinder) {
|
||||
public TimeZoneData(CountryZonesFinder countryZonesFinder) {
|
||||
mCountryZonesFinder = countryZonesFinder;
|
||||
mRegionIds = getNormalizedRegionIds(mCountryZonesFinder.lookupAllCountryIsoCodes());
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
@@ -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)");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user