New manual time zone picker.

This implements a new manual time zone picker that allows selection of a
time zone for a selected country. It also allows selecting a fixed
offset time zone (most importantly Etc/UTC, which is a frequently
requested feature).

The new time zone picker is currently behind a feature flag
(settings_zone_picker_v2), which is disabled by default.

Test: manual
Test: SettingsFunctionalTests
Test: SettingsRobotTests
Bug: 62255208
Change-Id: I89c5a04bcb562b6facf5f31a8aa4ad1cdd51ab10
This commit is contained in:
Joachim Sauer
2017-11-07 13:56:16 +00:00
parent 0bed37a086
commit 0cdbe1897c
9 changed files with 703 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
/*
* 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;
/**
* 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;
mTimeFormat = DateFormat.getTimeInstance(SimpleDateFormat.SHORT);
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();
}
}