1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.datetime.timezone; 18 19 import android.content.Context; 20 import android.icu.text.DateFormat; 21 import android.icu.text.DisplayContext; 22 import android.icu.text.SimpleDateFormat; 23 import android.icu.util.Calendar; 24 import android.icu.util.TimeZone; 25 26 import androidx.annotation.VisibleForTesting; 27 28 import com.android.settings.R; 29 import com.android.settings.core.BasePreferenceController; 30 31 import java.time.Instant; 32 import java.time.zone.ZoneOffsetTransition; 33 import java.time.zone.ZoneRules; 34 import java.util.Date; 35 36 public class TimeZoneInfoPreferenceController extends BasePreferenceController { 37 38 @VisibleForTesting 39 Date mDate; 40 private TimeZoneInfo mTimeZoneInfo; 41 private final DateFormat mDateFormat; 42 TimeZoneInfoPreferenceController(Context context, String key)43 public TimeZoneInfoPreferenceController(Context context, String key) { 44 super(context, key); 45 mDateFormat = DateFormat.getDateInstance(SimpleDateFormat.LONG); 46 mDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE); 47 mDate = new Date(); 48 } 49 50 @Override getAvailabilityStatus()51 public int getAvailabilityStatus() { 52 return mTimeZoneInfo != null ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE; 53 } 54 55 @Override getSummary()56 public CharSequence getSummary() { 57 return mTimeZoneInfo == null ? "" : formatInfo(mTimeZoneInfo); 58 } 59 setTimeZoneInfo(TimeZoneInfo timeZoneInfo)60 public void setTimeZoneInfo(TimeZoneInfo timeZoneInfo) { 61 mTimeZoneInfo = timeZoneInfo; 62 } 63 formatOffsetAndName(TimeZoneInfo item)64 private CharSequence formatOffsetAndName(TimeZoneInfo item) { 65 String name = item.getGenericName(); 66 if (name == null) { 67 if (item.getTimeZone().inDaylightTime(mDate)) { 68 name = item.getDaylightName(); 69 } else { 70 name = item.getStandardName(); 71 } 72 } 73 if (name == null) { 74 return item.getGmtOffset().toString(); 75 } else { 76 return SpannableUtil.getResourcesText(mContext.getResources(), 77 R.string.zone_info_offset_and_name, item.getGmtOffset(), 78 name); 79 } 80 } 81 formatInfo(TimeZoneInfo item)82 private CharSequence formatInfo(TimeZoneInfo item) { 83 final CharSequence offsetAndName = formatOffsetAndName(item); 84 final TimeZone timeZone = item.getTimeZone(); 85 if (!timeZone.observesDaylightTime()) { 86 return mContext.getString(R.string.zone_info_footer_no_dst, offsetAndName); 87 } 88 89 final ZoneOffsetTransition nextDstTransition = findNextDstTransition(item); 90 if (nextDstTransition == null) { // No future transition 91 return mContext.getString(R.string.zone_info_footer_no_dst, offsetAndName); 92 } 93 final boolean toDst = getDSTSavings(timeZone, nextDstTransition.getInstant()) != 0; 94 String timeType = toDst ? item.getDaylightName() : item.getStandardName(); 95 if (timeType == null) { 96 // Fall back to generic "summer time" and "standard time" if the time zone has no 97 // specific names. 98 timeType = toDst ? 99 mContext.getString(R.string.zone_time_type_dst) : 100 mContext.getString(R.string.zone_time_type_standard); 101 102 } 103 final Calendar transitionTime = Calendar.getInstance(timeZone); 104 transitionTime.setTimeInMillis(nextDstTransition.getInstant().toEpochMilli()); 105 final String date = mDateFormat.format(transitionTime); 106 return SpannableUtil.getResourcesText(mContext.getResources(), 107 R.string.zone_info_footer, offsetAndName, timeType, date); 108 } 109 findNextDstTransition(TimeZoneInfo timeZoneInfo)110 private ZoneOffsetTransition findNextDstTransition(TimeZoneInfo timeZoneInfo) { 111 TimeZone timeZone = timeZoneInfo.getTimeZone(); 112 ZoneRules zoneRules = timeZoneInfo.getJavaTimeZone().toZoneId().getRules(); 113 114 Instant from = mDate.toInstant(); 115 116 ZoneOffsetTransition transition; 117 while (true) { // Find next transition with different DST offsets 118 transition = zoneRules.nextTransition(from); 119 if (transition == null) { 120 break; 121 } 122 Instant to = transition.getInstant(); 123 if (getDSTSavings(timeZone, from) != getDSTSavings(timeZone, to)) { 124 break; 125 } 126 from = to; 127 } 128 129 return transition; 130 } 131 getDSTSavings(TimeZone timeZone, Instant instant)132 private static int getDSTSavings(TimeZone timeZone, Instant instant) { 133 int[] offsets = new int[2]; 134 timeZone.getOffset(instant.toEpochMilli(), false /* local time */, offsets); 135 return offsets[1]; 136 } 137 } 138