Additional fix: 1. Fixed the SpannableUtil.getResourcesText to actually preserve Spannable (TtsSpan in this use case) during formatting. Bug: 185453652 Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.datetime.timezone Change-Id: Iae5e1d4261ec0a34222ae1d042c7f3f027f2e512
157 lines
5.8 KiB
Java
157 lines
5.8 KiB
Java
/*
|
|
* 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.content.res.Resources;
|
|
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 androidx.annotation.VisibleForTesting;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.core.BasePreferenceController;
|
|
|
|
import java.time.Instant;
|
|
import java.time.zone.ZoneOffsetTransition;
|
|
import java.time.zone.ZoneRules;
|
|
import java.util.Date;
|
|
import java.util.Locale;
|
|
|
|
public class TimeZoneInfoPreferenceController extends BasePreferenceController {
|
|
|
|
@VisibleForTesting
|
|
Date mDate;
|
|
private TimeZoneInfo mTimeZoneInfo;
|
|
private final DateFormat mDateFormat;
|
|
|
|
public TimeZoneInfoPreferenceController(Context context, String key) {
|
|
super(context, key);
|
|
mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.LONG);
|
|
mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
|
|
mDate = new Date();
|
|
}
|
|
|
|
@Override
|
|
public int getAvailabilityStatus() {
|
|
return mTimeZoneInfo != null ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
|
|
}
|
|
|
|
@Override
|
|
public CharSequence getSummary() {
|
|
return mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo);
|
|
}
|
|
|
|
public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) {
|
|
mTimeZoneInfo = timeZoneInfo;
|
|
}
|
|
|
|
private CharSequence formatOffsetAndName(TimeZoneInfo item) {
|
|
String name = item.getGenericName();
|
|
if (name == null) {
|
|
if (item.getTimeZone().inDaylightTime(mDate)) {
|
|
name = item.getDaylightName();
|
|
} else {
|
|
name = item.getStandardName();
|
|
}
|
|
}
|
|
if (name == null) {
|
|
return item.getGmtOffset().toString();
|
|
} else {
|
|
return SpannableUtil.getResourcesText(mContext.getResources(),
|
|
R.string.zone_info_offset_and_name, item.getGmtOffset(),
|
|
name);
|
|
}
|
|
}
|
|
|
|
private CharSequence formatInfo(TimeZoneInfo item) {
|
|
final CharSequence offsetAndName = formatOffsetAndName(item);
|
|
final TimeZone timeZone = item.getTimeZone();
|
|
ZoneOffsetTransition nextDstTransition = null;
|
|
if (timeZone.observesDaylightTime()) {
|
|
nextDstTransition = findNextDstTransition(item);
|
|
}
|
|
if (nextDstTransition == null || !timeZone.observesDaylightTime()) {
|
|
return SpannableUtil.getResourcesText(mContext.getResources(),
|
|
R.string.zone_info_footer_no_dst, offsetAndName);
|
|
}
|
|
|
|
final boolean toDst = getDSTSavings(timeZone, nextDstTransition.getInstant()) != 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.getInstant().toEpochMilli());
|
|
final String date = mDateFormat.format(transitionTime);
|
|
return createFooterString(offsetAndName, timeType, date);
|
|
}
|
|
|
|
/**
|
|
* @param offsetAndName {@Spannable} styled text information should be preserved. See
|
|
* {@link #formatInfo} and {@link com.android.settingslib.datetime.ZoneGetter#getGmtOffsetText}.
|
|
*
|
|
*/
|
|
private CharSequence createFooterString(CharSequence offsetAndName, String timeType,
|
|
String date) {
|
|
Resources res = mContext.getResources();
|
|
Locale locale = res.getConfiguration().getLocales().get(0);
|
|
CharSequence secondSentence = SpannableUtil.titleCaseSentences(locale,
|
|
SpannableUtil.getResourcesText(res, R.string.zone_info_footer_second_sentence,
|
|
timeType, date));
|
|
|
|
return SpannableUtil.titleCaseSentences(locale, SpannableUtil.getResourcesText(res,
|
|
R.string.zone_info_footer_first_sentence, offsetAndName, secondSentence));
|
|
}
|
|
|
|
private ZoneOffsetTransition findNextDstTransition(TimeZoneInfo timeZoneInfo) {
|
|
TimeZone timeZone = timeZoneInfo.getTimeZone();
|
|
ZoneRules zoneRules = timeZoneInfo.getJavaTimeZone().toZoneId().getRules();
|
|
|
|
Instant from = mDate.toInstant();
|
|
|
|
ZoneOffsetTransition transition;
|
|
while (true) { // Find next transition with different DST offsets
|
|
transition = zoneRules.nextTransition(from);
|
|
if (transition == null) {
|
|
break;
|
|
}
|
|
Instant to = transition.getInstant();
|
|
if (getDSTSavings(timeZone, from) != getDSTSavings(timeZone, to)) {
|
|
break;
|
|
}
|
|
from = to;
|
|
}
|
|
|
|
return transition;
|
|
}
|
|
|
|
private static int getDSTSavings(TimeZone timeZone, Instant instant) {
|
|
int[] offsets = new int[2];
|
|
timeZone.getOffset(instant.toEpochMilli(), false /* local time */, offsets);
|
|
return offsets[1];
|
|
}
|
|
}
|