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.car.settings.datetime;
18 
19 import android.app.timezonedetector.ManualTimeZoneSuggestion;
20 import android.app.timezonedetector.TimeZoneDetector;
21 import android.car.drivingstate.CarUxRestrictions;
22 import android.content.Context;
23 import android.content.Intent;
24 
25 import androidx.annotation.VisibleForTesting;
26 import androidx.preference.Preference;
27 import androidx.preference.PreferenceGroup;
28 
29 import com.android.car.settings.common.FragmentController;
30 import com.android.car.settings.common.PreferenceController;
31 import com.android.car.ui.preference.CarUiPreference;
32 import com.android.settingslib.datetime.ZoneGetter;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.List;
38 import java.util.Map;
39 
40 /**
41  * Business logic which will populate the timezone options.
42  */
43 public class TimeZonePickerScreenPreferenceController extends
44         PreferenceController<PreferenceGroup> {
45 
46     private List<Preference> mZonesList;
47     @VisibleForTesting
48     TimeZoneDetector mTimeZoneDetector;
49 
TimeZonePickerScreenPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)50     public TimeZonePickerScreenPreferenceController(Context context, String preferenceKey,
51             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
52         super(context, preferenceKey, fragmentController, uxRestrictions);
53         mTimeZoneDetector = getContext().getSystemService(TimeZoneDetector.class);
54     }
55 
56     @Override
getPreferenceType()57     protected Class<PreferenceGroup> getPreferenceType() {
58         return PreferenceGroup.class;
59     }
60 
61     @Override
updateState(PreferenceGroup preferenceGroup)62     protected void updateState(PreferenceGroup preferenceGroup) {
63         if (mZonesList == null) {
64             constructTimeZoneList();
65         }
66         for (Preference zonePreference : mZonesList) {
67             preferenceGroup.addPreference(zonePreference);
68         }
69     }
70 
constructTimeZoneList()71     private void constructTimeZoneList() {
72         // We load all of the time zones on the UI thread. However it shouldn't be very expensive
73         // and also shouldn't take a long time. We can revisit this to setup background work and
74         // paging, if it becomes an issue.
75         List<Map<String, Object>> zones = ZoneGetter.getZonesList(getContext());
76         setZonesList(zones);
77     }
78 
79     @VisibleForTesting
setZonesList(List<Map<String, Object>> zones)80     void setZonesList(List<Map<String, Object>> zones) {
81         Collections.sort(zones, new TimeZonesComparator());
82         mZonesList = new ArrayList<>();
83         for (Map<String, Object> zone : zones) {
84             mZonesList.add(createTimeZonePreference(zone));
85         }
86     }
87 
88     /** Construct a time zone preference based on the Map object given by {@link ZoneGetter}. */
createTimeZonePreference(Map<String, Object> timeZone)89     private Preference createTimeZonePreference(Map<String, Object> timeZone) {
90         CarUiPreference preference = new CarUiPreference(getContext());
91         preference.setKey(timeZone.get(ZoneGetter.KEY_ID).toString());
92         preference.setTitle(timeZone.get(ZoneGetter.KEY_DISPLAY_LABEL).toString());
93         preference.setSummary(timeZone.get(ZoneGetter.KEY_OFFSET_LABEL).toString());
94         preference.setOnPreferenceClickListener(pref -> {
95             String tzId = timeZone.get(ZoneGetter.KEY_ID).toString();
96             ManualTimeZoneSuggestion suggestion = TimeZoneDetector.createManualTimeZoneSuggestion(
97                     tzId, "Settings: Set time zone");
98             mTimeZoneDetector.suggestManualTimeZone(suggestion);
99             getFragmentController().goBack();
100 
101             // Note: This is intentionally ACTION_TIME_CHANGED, not ACTION_TIMEZONE_CHANGED.
102             // Timezone change is handled by the alarm manager. This broadcast message is used
103             // to update the clock and other time related displays that the time has changed due
104             // to a change in the timezone.
105             getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
106             return true;
107         });
108         return preference;
109     }
110 
111     /** Compares the timezone objects returned by {@link ZoneGetter}. */
112     private static final class TimeZonesComparator implements Comparator<Map<String, Object>> {
113 
114         /** Compares timezones based on 1. offset, 2. display label/name. */
TimeZonesComparator()115         TimeZonesComparator() {
116         }
117 
118         @Override
compare(Map<String, Object> map1, Map<String, Object> map2)119         public int compare(Map<String, Object> map1, Map<String, Object> map2) {
120             int timeZoneOffsetCompare = compareWithKey(map1, map2, ZoneGetter.KEY_OFFSET);
121 
122             // If equivalent timezone offset, compare based on display label.
123             if (timeZoneOffsetCompare == 0) {
124                 return compareWithKey(map1, map2, ZoneGetter.KEY_DISPLAY_LABEL);
125             }
126 
127             return timeZoneOffsetCompare;
128         }
129 
compareWithKey(Map<String, Object> map1, Map<String, Object> map2, String comparisonKey)130         private int compareWithKey(Map<String, Object> map1, Map<String, Object> map2,
131                 String comparisonKey) {
132             Object value1 = map1.get(comparisonKey);
133             Object value2 = map2.get(comparisonKey);
134             if (!isComparable(value1) || !isComparable(value2)) {
135                 throw new IllegalArgumentException(
136                         "Cannot use Map which has values that are not Comparable");
137             }
138             return ((Comparable) value1).compareTo(value2);
139         }
140 
isComparable(Object value)141         private boolean isComparable(Object value) {
142             return (value != null) && (value instanceof Comparable);
143         }
144     }
145 }
146