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 static com.android.settingslib.datetime.ZoneGetter.capitalizeForStandaloneDisplay; 20 21 import android.app.settings.SettingsEnums; 22 import android.content.Intent; 23 import android.icu.text.Collator; 24 import android.icu.text.LocaleDisplayNames; 25 import android.icu.util.TimeZone; 26 import android.os.Bundle; 27 import android.util.Log; 28 29 import androidx.annotation.Nullable; 30 import androidx.annotation.VisibleForTesting; 31 32 import com.android.settings.R; 33 import com.android.settings.datetime.timezone.model.FilteredCountryTimeZones; 34 import com.android.settings.datetime.timezone.model.TimeZoneData; 35 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.Date; 41 import java.util.List; 42 import java.util.TreeSet; 43 44 /** 45 * Given a region, render a list of time zone {@class TimeZoneInfo} into a list view. 46 */ 47 public class RegionZonePicker extends BaseTimeZoneInfoPicker { 48 49 public static final String EXTRA_REGION_ID = 50 "com.android.settings.datetime.timezone.region_id"; 51 52 private @Nullable String mRegionName; 53 RegionZonePicker()54 public RegionZonePicker() { 55 super(R.string.date_time_set_timezone_title, R.string.search_settings, true, false); 56 } 57 58 @Override getMetricsCategory()59 public int getMetricsCategory() { 60 return SettingsEnums.SETTINGS_ZONE_PICKER_TIME_ZONE; 61 } 62 63 @Override onCreate(Bundle savedInstanceState)64 public void onCreate(Bundle savedInstanceState) { 65 super.onCreate(savedInstanceState); 66 67 final LocaleDisplayNames localeDisplayNames = LocaleDisplayNames.getInstance(getLocale()); 68 final String regionId = 69 getArguments() == null ? null : getArguments().getString(EXTRA_REGION_ID); 70 mRegionName = regionId == null ? null : capitalizeForStandaloneDisplay( 71 mLocale, localeDisplayNames.regionDisplayName(regionId)); 72 } 73 74 @Override getHeaderText()75 protected @Nullable CharSequence getHeaderText() { 76 return mRegionName; 77 } 78 79 /** 80 * Add the extra region id into the result. 81 */ 82 @Override prepareResultData(TimeZoneInfo selectedTimeZoneInfo)83 protected Intent prepareResultData(TimeZoneInfo selectedTimeZoneInfo) { 84 final Intent intent = super.prepareResultData(selectedTimeZoneInfo); 85 intent.putExtra(EXTRA_RESULT_REGION_ID, getArguments().getString(EXTRA_REGION_ID)); 86 return intent; 87 } 88 89 @Override getAllTimeZoneInfos(TimeZoneData timeZoneData)90 public List<TimeZoneInfo> getAllTimeZoneInfos(TimeZoneData timeZoneData) { 91 if (getArguments() == null) { 92 Log.e(TAG, "getArguments() == null"); 93 getActivity().finish(); 94 return Collections.emptyList(); 95 } 96 String regionId = getArguments().getString(EXTRA_REGION_ID); 97 98 FilteredCountryTimeZones filteredCountryTimeZones = timeZoneData.lookupCountryTimeZones( 99 regionId); 100 if (filteredCountryTimeZones == null) { 101 Log.e(TAG, "region id is not valid: " + regionId); 102 getActivity().finish(); 103 return Collections.emptyList(); 104 } 105 106 // It could be a timely operations if there are many time zones. A region in time zone data 107 // contains a maximum of 29 time zones currently. It may change in the future, but it's 108 // unlikely to be changed drastically. 109 return getRegionTimeZoneInfo(filteredCountryTimeZones.getPreferredTimeZoneIds()); 110 } 111 112 /** 113 * Returns a list of {@link TimeZoneInfo} objects. The returned list will be sorted properly for 114 * display in the locale.It may be smaller than the input collection, if equivalent IDs are 115 * passed in. 116 * 117 * @param timeZoneIds a list of Olson IDs. 118 */ getRegionTimeZoneInfo(Collection<String> timeZoneIds)119 public List<TimeZoneInfo> getRegionTimeZoneInfo(Collection<String> timeZoneIds) { 120 final TimeZoneInfo.Formatter formatter = new TimeZoneInfo.Formatter(getLocale(), 121 new Date()); 122 final TreeSet<TimeZoneInfo> timeZoneInfos = 123 new TreeSet<>(new TimeZoneInfoComparator(Collator.getInstance(getLocale()), 124 new Date())); 125 126 for (final String timeZoneId : timeZoneIds) { 127 final TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId); 128 // Skip time zone ICU isn't aware. 129 if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) { 130 continue; 131 } 132 timeZoneInfos.add(formatter.format(timeZone)); 133 } 134 return Collections.unmodifiableList(new ArrayList<>(timeZoneInfos)); 135 } 136 137 @VisibleForTesting 138 static class TimeZoneInfoComparator implements Comparator<TimeZoneInfo> { 139 private Collator mCollator; 140 private final Date mNow; 141 142 @VisibleForTesting TimeZoneInfoComparator(Collator collator, Date now)143 TimeZoneInfoComparator(Collator collator, Date now) { 144 mCollator = collator; 145 mNow = now; 146 } 147 148 @Override compare(TimeZoneInfo tzi1, TimeZoneInfo tzi2)149 public int compare(TimeZoneInfo tzi1, TimeZoneInfo tzi2) { 150 int result = Integer.compare(tzi1.getTimeZone().getOffset(mNow.getTime()), 151 tzi2.getTimeZone().getOffset(mNow.getTime())); 152 if (result == 0) { 153 result = Integer.compare(tzi1.getTimeZone().getRawOffset(), 154 tzi2.getTimeZone().getRawOffset()); 155 } 156 if (result == 0) { 157 result = mCollator.compare(tzi1.getExemplarLocation(), tzi2.getExemplarLocation()); 158 } 159 if (result == 0 && tzi1.getGenericName() != null && tzi2.getGenericName() != null) { 160 result = mCollator.compare(tzi1.getGenericName(), tzi2.getGenericName()); 161 } 162 return result; 163 } 164 } 165 } 166