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