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