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