/* * 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]; } }