Merge "Remove dead code in time zone picker" into pi-dev

This commit is contained in:
Victor Chang
2018-03-30 18:35:52 +00:00
committed by Android (Google) Code Review
12 changed files with 0 additions and 1075 deletions

View File

@@ -1,44 +0,0 @@
<!--
Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/tz_region_spinner_layout"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/colorAccent"
android:gravity="center_vertical"
android:paddingEnd="@dimen/switchbar_subsettings_margin_end"
android:orientation="horizontal">
<Spinner
android:id="@+id/tz_region_spinner"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:paddingStart="64dp"
android:layout_weight="1"
android:background="@drawable/app_filter_spinner_background"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/tz_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@@ -1,205 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import android.graphics.Paint;
import android.icu.text.Collator;
import android.icu.text.LocaleDisplayNames;
import android.icu.text.TimeZoneFormat;
import android.icu.text.TimeZoneNames;
import android.icu.text.TimeZoneNames.NameType;
import android.icu.util.Region;
import android.icu.util.Region.RegionType;
import android.icu.util.TimeZone;
import android.icu.util.TimeZone.SystemTimeZoneType;
import com.android.settingslib.datetime.ZoneGetter;
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.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;
/**
* Provides data for manual selection of time zones based associated to regions. This class makes no
* attempt to avoid IO and processing intensive actions. This means it should not be called from the
* UI thread.
*/
public class DataLoader {
private static final int MIN_HOURS_OFFSET = -14;
private static final int MAX_HOURS_OFFSET = +12;
private final Locale mLocale;
private final Collator mCollator;
private final LocaleDisplayNames mLocaleDisplayNames;
private final TimeZoneFormat mTimeZoneFormat;
private final Paint mPaint;
private final AtomicLong nextItemId = new AtomicLong(1);
private final long mNow = System.currentTimeMillis();
public DataLoader(Locale locale) {
mLocale = locale;
mCollator = Collator.getInstance(locale);
mLocaleDisplayNames = LocaleDisplayNames.getInstance(locale);
mTimeZoneFormat = TimeZoneFormat.getInstance(locale);
mPaint = new Paint();
}
/**
* Returns a {@link RegionInfo} object for each region that has selectable time zones. The
* returned list will be sorted properly for display in the locale.
*/
public List<RegionInfo> loadRegionInfos() {
final Set<Region> regions = Region.getAvailable(RegionType.TERRITORY);
final TreeSet<RegionInfo> regionInfos = new TreeSet<>(new RegionInfoComparator());
for (final Region region : regions) {
final String regionId = region.toString();
final Set<String> timeZoneIds = getTimeZoneIds(regionId);
if (timeZoneIds.isEmpty()) {
continue;
}
final String name = mLocaleDisplayNames.regionDisplayName(regionId);
final String regionalIndicator = createRegionalIndicator(regionId);
regionInfos.add(new RegionInfo(regionId, name, regionalIndicator, timeZoneIds));
}
return Collections.unmodifiableList(new ArrayList<>(regionInfos));
}
/**
* 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> loadTimeZoneInfos(Collection<String> timeZoneIds) {
final TreeSet<TimeZoneInfo> timeZoneInfos = new TreeSet<>(new TimeZoneInfoComparator());
outer:
for (final String timeZoneId : timeZoneIds) {
final TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId);
for (final TimeZoneInfo other : timeZoneInfos) {
if (other.getTimeZone().hasSameRules(timeZone)) {
continue outer;
}
}
timeZoneInfos.add(createTimeZoneInfo(timeZone));
}
return Collections.unmodifiableList(new ArrayList<>(timeZoneInfos));
}
/**
* 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.
*/
public List<TimeZoneInfo> loadFixedOffsets() {
final List<TimeZoneInfo> timeZoneInfos = new ArrayList<>();
timeZoneInfos.add(createTimeZoneInfo(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("Etc/GMT%+d", hoursOffset);
timeZoneInfos.add(createTimeZoneInfo(TimeZone.getFrozenTimeZone(id)));
}
return Collections.unmodifiableList(timeZoneInfos);
}
/**
* Gets the set of ids for relevant TimeZones in the given region.
*/
private Set<String> getTimeZoneIds(String regionId) {
return TimeZone.getAvailableIDs(
SystemTimeZoneType.CANONICAL_LOCATION, regionId, /* rawOffset */ null);
}
private TimeZoneInfo createTimeZoneInfo(TimeZone timeZone) {
// Every timezone we handle must be an OlsonTimeZone.
final String id = timeZone.getID();
final TimeZoneNames timeZoneNames = mTimeZoneFormat.getTimeZoneNames();
final java.util.TimeZone javaTimeZone = android.icu.impl.TimeZoneAdapter.wrap(timeZone);
final CharSequence gmtOffset = ZoneGetter.getGmtOffsetText(mTimeZoneFormat, mLocale,
javaTimeZone, new Date(mNow));
return new TimeZoneInfo.Builder(timeZone)
.setGenericName(timeZoneNames.getDisplayName(id, NameType.LONG_GENERIC, mNow))
.setStandardName(timeZoneNames.getDisplayName(id, NameType.LONG_STANDARD, mNow))
.setDaylightName(timeZoneNames.getDisplayName(id, NameType.LONG_DAYLIGHT, mNow))
.setExemplarLocation(timeZoneNames.getExemplarLocationName(id))
.setGmtOffset(gmtOffset)
.setItemId(nextItemId.getAndIncrement())
.build();
}
/**
* 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.
* @return a String representing the flag of the region or {@code null}.
*/
private String createRegionalIndicator(String id) {
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 (!mPaint.hasGlyph(regionalIndicator)) {
return null;
}
return regionalIndicator;
}
private class TimeZoneInfoComparator implements Comparator<TimeZoneInfo> {
@Override
public int compare(TimeZoneInfo tzi1, TimeZoneInfo tzi2) {
int 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;
}
}
private class RegionInfoComparator implements Comparator<RegionInfo> {
@Override
public int compare(RegionInfo r1, RegionInfo r2) {
return mCollator.compare(r1.getName(), r2.getName());
}
}
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Data object describing a geographical region.
*
* Regions are roughly equivalent to countries, but not every region is a country (for example "U.S.
* overseas territories" is treated as a country).
*/
public class RegionInfo {
private final String mId;
private final String mName;
private final String mRegionalIndicator;
private final Collection<String> mTimeZoneIds;
public RegionInfo(String id, String name, String regionalIndicator,
Collection<String> timeZoneIds) {
mId = id;
mName = name;
mRegionalIndicator = regionalIndicator;
mTimeZoneIds = Collections.unmodifiableList(new ArrayList<>(timeZoneIds));
}
public String getId() {
return mId;
}
public String getName() {
return mName;
}
public Collection<String> getTimeZoneIds() {
return mTimeZoneIds;
}
@Override
public String toString() {
return mRegionalIndicator != null ? mRegionalIndicator + " " + mName : mName;
}
}

View File

@@ -1,213 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import android.content.Context;
import android.graphics.Typeface;
import android.icu.impl.OlsonTimeZone;
import android.icu.text.DateFormat;
import android.icu.text.DisplayContext;
import android.icu.text.SimpleDateFormat;
import android.icu.util.Calendar;
import android.icu.util.TimeZone;
import android.icu.util.TimeZoneTransition;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.settings.R;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* Adapter for showing {@link TimeZoneInfo} objects in a recycler view.
*/
class TimeZoneAdapter extends RecyclerView.Adapter {
static final int VIEW_TYPE_NORMAL = 1;
static final int VIEW_TYPE_SELECTED = 2;
private final DateFormat mTimeFormat;
private final DateFormat mDateFormat;
private final View.OnClickListener mOnClickListener;
private final Context mContext;
private final String mCurrentTimeZone;
private List<TimeZoneInfo> mTimeZoneInfos;
TimeZoneAdapter(View.OnClickListener onClickListener, Context context) {
mOnClickListener = onClickListener;
mContext = context;
// Use android.text.format.DateFormat to observe 24-hour settings and find the best pattern
// using ICU with skeleton.
mTimeFormat = new SimpleDateFormat(
android.text.format.DateFormat.getTimeFormatString(context),
Locale.getDefault());
mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.MEDIUM);
mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
mCurrentTimeZone = TimeZone.getDefault().getID();
setHasStableIds(true);
}
@Override
public long getItemId(int position) {
return getItem(position).getItemId();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.time_zone_list_item, parent, false);
view.setOnClickListener(mOnClickListener);
final ViewHolder viewHolder = new ViewHolder(view);
if (viewType == VIEW_TYPE_SELECTED) {
viewHolder.mNameView.setTypeface(
viewHolder.mNameView.getTypeface(), Typeface.BOLD);
}
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
final TimeZoneInfo item = getItem(position);
final ViewHolder tzHolder = (ViewHolder) holder;
tzHolder.mNameView.setText(formatName(item));
tzHolder.mDetailsView.setText(formatDetails(item));
tzHolder.mTimeView.setText(formatTime(item));
String dstText = formatDstText(item);
tzHolder.mDstView.setText(dstText);
// Hide DST TextView when it has no content.
tzHolder.mDstView.setVisibility(dstText != null ? View.VISIBLE : View.GONE);
}
@Override
public int getItemCount() {
return getTimeZones().size();
}
@Override
public int getItemViewType(int position) {
final TimeZoneInfo tz = getItem(position);
if (tz.getId().equals(mCurrentTimeZone)) {
return VIEW_TYPE_SELECTED;
} else {
return VIEW_TYPE_NORMAL;
}
}
public TimeZoneInfo getItem(int position) {
return getTimeZones().get(position);
}
private CharSequence formatName(TimeZoneInfo item) {
CharSequence name = item.getExemplarLocation();
if (name == null) {
name = item.getGenericName();
}
if (name == null && item.getTimeZone().inDaylightTime(new Date())) {
name = item.getDaylightName();
}
if (name == null) {
name = item.getStandardName();
}
if (name == null) {
name = item.getGmtOffset();
}
return name;
}
private CharSequence formatDetails(TimeZoneInfo item) {
String name = item.getGenericName();
if (name == null) {
if (item.getTimeZone().inDaylightTime(new Date())) {
name = item.getDaylightName();
} else {
name = item.getStandardName();
}
}
if (name == null) {
return item.getGmtOffset();
} else {
return TextUtils.concat(item.getGmtOffset(), " ", name);
}
}
private String formatDstText(TimeZoneInfo item) {
final TimeZone timeZone = item.getTimeZone();
if (!timeZone.observesDaylightTime()) {
return null;
}
final TimeZoneTransition nextDstTransition = findNextDstTransition(timeZone);
if (nextDstTransition == null) {
return null;
}
final boolean toDst = nextDstTransition.getTo().getDSTSavings() != 0;
String timeType = toDst ? item.getDaylightName() : item.getStandardName();
if (timeType == null) {
// Fall back to generic "summer time" and "standard time" if the time zone has no
// specific names.
timeType = toDst ?
mContext.getString(R.string.zone_time_type_dst) :
mContext.getString(R.string.zone_time_type_standard);
}
final Calendar transitionTime = Calendar.getInstance(timeZone);
transitionTime.setTimeInMillis(nextDstTransition.getTime());
final String date = mDateFormat.format(transitionTime);
return mContext.getString(R.string.zone_change_to_from_dst, timeType, date);
}
private TimeZoneTransition findNextDstTransition(TimeZone timeZone) {
if (!(timeZone instanceof OlsonTimeZone)) {
return null;
}
final OlsonTimeZone olsonTimeZone = (OlsonTimeZone) timeZone;
TimeZoneTransition transition = olsonTimeZone.getNextTransition(
System.currentTimeMillis(), /* inclusive */ false);
do {
if (transition.getTo().getDSTSavings() != transition.getFrom().getDSTSavings()) {
break;
}
transition = olsonTimeZone.getNextTransition(
transition.getTime(), /*inclusive */ false);
} while (transition != null);
return transition;
}
private String formatTime(TimeZoneInfo item) {
return mTimeFormat.format(Calendar.getInstance(item.getTimeZone()));
}
private List<TimeZoneInfo> getTimeZones() {
if (mTimeZoneInfos == null) {
return Collections.emptyList();
}
return mTimeZoneInfos;
}
void setTimeZoneInfos(List<TimeZoneInfo> timeZoneInfos) {
mTimeZoneInfos = timeZoneInfos;
notifyDataSetChanged();
}
}

View File

@@ -37,8 +37,6 @@ public class TimeZoneInfo {
private final String mDaylightName;
private final String mExemplarLocation;
private final CharSequence mGmtOffset;
// Arbitrary id that's unique within all TimeZoneInfo objects created by a given DataLoader instance.
private final long mItemId;
public TimeZoneInfo(Builder builder) {
mTimeZone = builder.mTimeZone;
@@ -48,7 +46,6 @@ public class TimeZoneInfo {
mDaylightName = builder.mDaylightName;
mExemplarLocation = builder.mExemplarLocation;
mGmtOffset = builder.mGmtOffset;
mItemId = builder.mItemId;
}
public String getId() {
@@ -79,10 +76,6 @@ public class TimeZoneInfo {
return mGmtOffset;
}
public long getItemId() {
return mItemId;
}
public static class Builder {
private final TimeZone mTimeZone;
private String mGenericName;
@@ -90,7 +83,6 @@ public class TimeZoneInfo {
private String mDaylightName;
private String mExemplarLocation;
private CharSequence mGmtOffset;
private long mItemId = -1;
public Builder(TimeZone timeZone) {
if (timeZone == null) {
@@ -124,18 +116,10 @@ public class TimeZoneInfo {
return this;
}
public Builder setItemId(long itemId) {
mItemId = itemId;
return this;
}
public TimeZoneInfo build() {
if (TextUtils.isEmpty(mGmtOffset)) {
throw new IllegalStateException("gmtOffset must not be empty!");
}
if (mItemId == -1) {
throw new IllegalStateException("ItemId not set!");
}
return new TimeZoneInfo(this);
}
}
@@ -179,8 +163,6 @@ public class TimeZoneInfo {
TimeZoneNames.NameType.LONG_DAYLIGHT, mNow.getTime()))
.setExemplarLocation(timeZoneNames.getExemplarLocationName(id))
.setGmtOffset(gmtOffset)
// TODO: move Item id to TimeZoneInfoAdapter
.setItemId(0)
.build();
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import com.android.settings.R;
/**
* View holder for a time zone list item.
*/
class ViewHolder extends RecyclerView.ViewHolder {
final TextView mNameView;
final TextView mDstView;
final TextView mDetailsView;
final TextView mTimeView;
public ViewHolder(View itemView) {
super(itemView);
mNameView = itemView.findViewById(R.id.tz_item_name);
mDstView = itemView.findViewById(R.id.tz_item_dst);
mDetailsView = itemView.findViewById(R.id.tz_item_details);
mTimeView = itemView.findViewById(R.id.tz_item_time);
}
}

View File

@@ -1,229 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import android.app.Activity;
import android.app.AlarmManager;
import android.content.Context;
import android.icu.util.TimeZone;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
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.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* The class displaying a region list and a list of time zones for the selected region.
* Choosing an item from the list will set the time zone. Pressing Back without choosing from the
* list will not result in a change in the time zone setting.
*/
public class ZonePicker extends InstrumentedFragment
implements AdapterView.OnItemSelectedListener, View.OnClickListener {
private static final int MENU_BY_REGION = Menu.FIRST;
private static final int MENU_BY_OFFSET = Menu.FIRST + 1;
private Locale mLocale;
private List<RegionInfo> mRegions;
private Map<String, List<TimeZoneInfo>> mZoneInfos;
private List<TimeZoneInfo> mFixedOffsetTimeZones;
private String mSelectedTimeZone;
private boolean mSelectByRegion;
private DataLoader mDataLoader;
private TimeZoneAdapter mTimeZoneAdapter;
private RecyclerView mRecyclerView;
private LinearLayout mRegionSpinnerLayout;
private Spinner mRegionSpinner;
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.ZONE_PICKER;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.time_zone_list, container, false);
mLocale = getContext().getResources().getConfiguration().locale;
mDataLoader = new DataLoader(mLocale);
// TOOD: move this off the UI thread.
mRegions = mDataLoader.loadRegionInfos();
mZoneInfos = new HashMap<>();
mSelectByRegion = true;
mSelectedTimeZone = TimeZone.getDefault().getID();
mTimeZoneAdapter = new TimeZoneAdapter(this, getContext());
mRecyclerView = view.findViewById(R.id.tz_list);
mRecyclerView.setAdapter(mTimeZoneAdapter);
mRecyclerView.setLayoutManager(
new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
final ArrayAdapter<RegionInfo> regionAdapter = new ArrayAdapter<>(getContext(),
R.layout.filter_spinner_item, mRegions);
regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mRegionSpinnerLayout = view.findViewById(R.id.tz_region_spinner_layout);
mRegionSpinner = view.findViewById(R.id.tz_region_spinner);
mRegionSpinner.setAdapter(regionAdapter);
mRegionSpinner.setOnItemSelectedListener(this);
setupForCurrentTimeZone();
setHasOptionsMenu(true);
return view;
}
private void setupForCurrentTimeZone() {
final String localeRegionId = mLocale.getCountry().toUpperCase(Locale.ROOT);
final String currentTimeZone = TimeZone.getDefault().getID();
boolean fixedOffset = currentTimeZone.startsWith("Etc/GMT") ||
currentTimeZone.equals("Etc/UTC");
for (int regionIndex = 0; regionIndex < mRegions.size(); regionIndex++) {
final RegionInfo region = mRegions.get(regionIndex);
if (localeRegionId.equals(region.getId())) {
mRegionSpinner.setSelection(regionIndex);
}
if (!fixedOffset) {
for (String timeZoneId: region.getTimeZoneIds()) {
if (TextUtils.equals(timeZoneId, mSelectedTimeZone)) {
mRegionSpinner.setSelection(regionIndex);
return;
}
}
}
}
if (fixedOffset) {
setSelectByRegion(false);
}
}
@Override
public void onClick(View view) {
// Ignore extra clicks
if (!isResumed()) {
return;
}
final int position = mRecyclerView.getChildAdapterPosition(view);
if (position == RecyclerView.NO_POSITION) {
return;
}
final TimeZoneInfo timeZoneInfo = mTimeZoneAdapter.getItem(position);
// Update the system timezone value
final Activity activity = getActivity();
final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
alarm.setTimeZone(timeZoneInfo.getId());
activity.onBackPressed();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(0, MENU_BY_REGION, 0, R.string.zone_menu_by_region);
menu.add(0, MENU_BY_OFFSET, 0, R.string.zone_menu_by_offset);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
if (mSelectByRegion) {
menu.findItem(MENU_BY_REGION).setVisible(false);
menu.findItem(MENU_BY_OFFSET).setVisible(true);
} else {
menu.findItem(MENU_BY_REGION).setVisible(true);
menu.findItem(MENU_BY_OFFSET).setVisible(false);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_BY_REGION:
setSelectByRegion(true);
return true;
case MENU_BY_OFFSET:
setSelectByRegion(false);
return true;
default:
return false;
}
}
private void setSelectByRegion(boolean selectByRegion) {
mSelectByRegion = selectByRegion;
mRegionSpinnerLayout.setVisibility(
mSelectByRegion ? View.VISIBLE : View.GONE);
List<TimeZoneInfo> tzInfos;
if (selectByRegion) {
int selectedRegion = mRegionSpinner.getSelectedItemPosition();
if (selectedRegion == -1) {
// Arbitrarily pick the first item if no region was selected above.
selectedRegion = 0;
mRegionSpinner.setSelection(selectedRegion);
}
tzInfos = getTimeZoneInfos(mRegions.get(selectedRegion));
} else {
if (mFixedOffsetTimeZones == null) {
mFixedOffsetTimeZones = mDataLoader.loadFixedOffsets();
}
tzInfos = mFixedOffsetTimeZones;
}
mTimeZoneAdapter.setTimeZoneInfos(tzInfos);
}
private List<TimeZoneInfo> getTimeZoneInfos(RegionInfo regionInfo) {
List<TimeZoneInfo> tzInfos = mZoneInfos.get(regionInfo.getId());
if (tzInfos == null) {
// TODO: move this off the UI thread.
Collection<String> tzIds = regionInfo.getTimeZoneIds();
tzInfos = mDataLoader.loadTimeZoneInfos(tzIds);
mZoneInfos.put(regionInfo.getId(), tzInfos);
}
return tzInfos;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mTimeZoneAdapter.setTimeZoneInfos(getTimeZoneInfos(mRegions.get(position)));
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
mTimeZoneAdapter.setTimeZoneInfos(null);
}
}

View File

@@ -89,7 +89,6 @@ public class BaseTimeZoneInfoPickerTest {
.setDaylightName("Pacific Daylight Time")
.setExemplarLocation("Los Angeles")
.setGmtOffset("GMT-08:00")
.setItemId(0)
.build();
TimeZoneInfo zone2 = new TimeZoneInfo.Builder(
TimeZone.getFrozenTimeZone("America/New_York"))
@@ -98,7 +97,6 @@ public class BaseTimeZoneInfoPickerTest {
.setDaylightName("Eastern Daylight Time")
.setExemplarLocation("New York")
.setGmtOffset("GMT-05:00")
.setItemId(1)
.build();
return Arrays.asList(zone1, zone2);

View File

@@ -1,91 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.Locale;
@RunWith(SettingsRobolectricTestRunner.class)
public class DataLoaderTest {
@Test
public void testHasData() {
List<RegionInfo> regions = new DataLoader(Locale.US).loadRegionInfos();
// Sanity check. Real size is closer to 200.
assertNotNull(regions);
assertTrue(regions.size() > 100);
assertEquals("Afghanistan", regions.get(0).getName());
assertEquals("Zimbabwe", regions.get(regions.size() - 1).getName());
}
@Test
public void testRegionsWithTimeZone() {
List<RegionInfo> regions = new DataLoader(Locale.US).loadRegionInfos();
checkRegionHasTimeZone(regions, "AT", "Europe/Vienna");
checkRegionHasTimeZone(regions, "US", "America/Los_Angeles");
checkRegionHasTimeZone(regions, "CN", "Asia/Shanghai");
checkRegionHasTimeZone(regions, "AU", "Australia/Sydney");
}
@Test
public void testFixedOffsetTimeZones() {
List<TimeZoneInfo> timeZones = new DataLoader(Locale.US).loadFixedOffsets();
// Etc/GMT would be equivalent to Etc/UTC, except for how it is labelled. Users have
// explicitly asked for UTC to be supported, so make sure we label it as such.
checkHasTimeZone(timeZones, "Etc/UTC");
checkHasTimeZone(timeZones, "Etc/GMT-1");
checkHasTimeZone(timeZones, "Etc/GMT-14");
checkHasTimeZone(timeZones, "Etc/GMT+1");
checkHasTimeZone(timeZones, "Etc/GMT+12");
}
private void checkRegionHasTimeZone(List<RegionInfo> regions, String regionId, String tzId) {
RegionInfo ri = findRegion(regions, regionId);
assertTrue("Region " + regionId + " does not have time zone " + tzId,
ri.getTimeZoneIds().contains(tzId));
}
private void checkHasTimeZone(List<TimeZoneInfo> timeZoneInfos, String tzId) {
for (TimeZoneInfo tz : timeZoneInfos) {
if (tz.getId().equals(tzId)) {
return;
}
}
fail("Fixed offset time zones do not contain " + tzId);
}
private RegionInfo findRegion(List<RegionInfo> regions, String regionId) {
for (RegionInfo region : regions) {
if (region.getId().equals(regionId)) {
assertNotNull(region.getName());
return region;
}
}
fail("No region with id " + regionId + " found.");
return null; // can't reach.
}
}

View File

@@ -44,7 +44,6 @@ public class FixedOffsetPreferenceControllerTest {
TimeZoneInfo fixedOffsetZone = new TimeZoneInfo.Builder(
TimeZone.getFrozenTimeZone("Etc/GMT-8"))
.setGmtOffset("GMT-08:00")
.setItemId(0)
.build();
Preference preference = new Preference(mActivity);
FixedOffsetPreferenceController controller = new FixedOffsetPreferenceController(mActivity);
@@ -59,7 +58,6 @@ public class FixedOffsetPreferenceControllerTest {
TimeZone.getFrozenTimeZone("Etc/UTC"))
.setStandardName("Coordinated Universal Time")
.setGmtOffset("GMT+00:00")
.setItemId(0)
.build();
Preference preference = new Preference(mActivity);
FixedOffsetPreferenceController controller = new FixedOffsetPreferenceController(mActivity);

View File

@@ -48,7 +48,6 @@ public class RegionZonePreferenceControllerTest {
.setDaylightName("Pacific Daylight Time")
.setExemplarLocation("Los Angeles")
.setGmtOffset("GMT-08:00")
.setItemId(0)
.build();
Preference preference = new Preference(mActivity);
RegionZonePreferenceController controller = new RegionZonePreferenceController(mActivity);

View File

@@ -1,170 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.datetime.timezone;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.icu.util.TimeZone;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import java.util.Collections;
import java.util.Locale;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = TimeZoneAdapterTest.ShadowDataFormat.class)
public class TimeZoneAdapterTest {
@Mock
private View.OnClickListener mOnClickListener;
private TimeZoneAdapter mTimeZoneAdapter;
private Context mContext;
private Locale mDefaultLocale;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mTimeZoneAdapter = new TimeZoneAdapter(mOnClickListener, mContext);
mDefaultLocale = Locale.getDefault();
}
@After
public void tearDown() {
Locale.setDefault(mDefaultLocale);
}
@Test
public void getItemViewType_onDefaultTimeZone_returnsTypeSelected() {
final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getDefault());
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
assertThat(mTimeZoneAdapter.getItemViewType(0))
.isEqualTo(TimeZoneAdapter.VIEW_TYPE_SELECTED);
}
@Test
public void getItemViewType_onNonDefaultTimeZone_returnsTypeNormal() {
final TimeZoneInfo tzi = dummyTimeZoneInfo(getNonDefaultTimeZone());
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
assertThat(mTimeZoneAdapter.getItemViewType(0)).isEqualTo(TimeZoneAdapter.VIEW_TYPE_NORMAL);
}
@Test
public void bindViewHolder_onDstTimeZone_showsDstLabel() {
final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("America/Los_Angeles"));
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application);
final ViewHolder viewHolder =
(ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL);
mTimeZoneAdapter.bindViewHolder(viewHolder, 0);
assertThat(viewHolder.mDstView).isNotNull();
assertThat(viewHolder.mDstView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void bindViewHolder_onNonDstTimeZone_hidesDstLabel() {
final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("Etc/UTC"));
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application);
final ViewHolder viewHolder =
(ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL);
mTimeZoneAdapter.bindViewHolder(viewHolder, 0);
assertThat(viewHolder.mDstView).isNotNull();
assertThat(viewHolder.mDstView.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void bindViewHolder_on24Hour() {
Locale.setDefault(Locale.US);
ShadowDataFormat.mTimeFormatString = "HH:mm";
mTimeZoneAdapter = new TimeZoneAdapter(mOnClickListener, mContext);
final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("Etc/UTC"));
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application);
final ViewHolder viewHolder =
(ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL);
mTimeZoneAdapter.bindViewHolder(viewHolder, 0);
assertThat(viewHolder.mTimeView).isNotNull();
assertThat(viewHolder.mTimeView.getText().toString()).hasLength(5);
}
@Test
public void bindViewHolder_on12Hour() {
Locale.setDefault(Locale.US);
ShadowDataFormat.mTimeFormatString = "hh:mm a";
mTimeZoneAdapter = new TimeZoneAdapter(mOnClickListener, mContext);
final TimeZoneInfo tzi = dummyTimeZoneInfo(TimeZone.getTimeZone("Etc/UTC"));
mTimeZoneAdapter.setTimeZoneInfos(Collections.singletonList(tzi));
final FrameLayout parent = new FrameLayout(RuntimeEnvironment.application);
final ViewHolder viewHolder =
(ViewHolder) mTimeZoneAdapter.createViewHolder(parent, TimeZoneAdapter.VIEW_TYPE_NORMAL);
mTimeZoneAdapter.bindViewHolder(viewHolder, 0);
assertThat(viewHolder.mTimeView).isNotNull();
assertThat(viewHolder.mTimeView.getText().toString()).hasLength(8);
}
// Pick an arbitrary time zone that's not the current default.
private static TimeZone getNonDefaultTimeZone() {
final String[] availableIDs = TimeZone.getAvailableIDs();
int index = 0;
if (TextUtils.equals(availableIDs[index], TimeZone.getDefault().getID())) {
index++;
}
return TimeZone.getTimeZone(availableIDs[index]);
}
private TimeZoneInfo dummyTimeZoneInfo(TimeZone timeZone) {
return new TimeZoneInfo.Builder(timeZone).setGmtOffset("GMT+0").setItemId(1).build();
}
@Implements(android.text.format.DateFormat.class)
public static class ShadowDataFormat {
private static String mTimeFormatString = "";
@Implementation
public static String getTimeFormatString(Context context) {
return mTimeFormatString;
}
}
}