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
onCreateInternal()62     protected void onCreateInternal() {
63         super.onCreateInternal();
64         setClickableWhileDisabled(getPreference(), /* clickable= */ true, p ->
65                 DatetimeUtils.runClickableWhileDisabled(getContext(), getFragmentController()));
66     }
67 
68     @Override
updateState(PreferenceGroup preferenceGroup)69     protected void updateState(PreferenceGroup preferenceGroup) {
70         if (mZonesList == null) {
71             constructTimeZoneList();
72         }
73         for (Preference zonePreference : mZonesList) {
74             preferenceGroup.addPreference(zonePreference);
75         }
76     }
77 
78     @Override
getDefaultAvailabilityStatus()79     public int getDefaultAvailabilityStatus() {
80         return DatetimeUtils.getAvailabilityStatus(getContext());
81     }
82 
constructTimeZoneList()83     private void constructTimeZoneList() {
84         // We load all of the time zones on the UI thread. However it shouldn't be very expensive
85         // and also shouldn't take a long time. We can revisit this to setup background work and
86         // paging, if it becomes an issue.
87         List<Map<String, Object>> zones = ZoneGetter.getZonesList(getContext());
88         setZonesList(zones);
89     }
90 
91     @VisibleForTesting
setZonesList(List<Map<String, Object>> zones)92     void setZonesList(List<Map<String, Object>> zones) {
93         Collections.sort(zones, new TimeZonesComparator());
94         mZonesList = new ArrayList<>();
95         for (Map<String, Object> zone : zones) {
96             mZonesList.add(createTimeZonePreference(zone));
97         }
98     }
99 
100     /** Construct a time zone preference based on the Map object given by {@link ZoneGetter}. */
createTimeZonePreference(Map<String, Object> timeZone)101     private Preference createTimeZonePreference(Map<String, Object> timeZone) {
102         CarUiPreference preference = new CarUiPreference(getContext());
103         preference.setKey(timeZone.get(ZoneGetter.KEY_ID).toString());
104         preference.setTitle(timeZone.get(ZoneGetter.KEY_DISPLAY_LABEL).toString());
105         preference.setSummary(timeZone.get(ZoneGetter.KEY_OFFSET_LABEL).toString());
106         preference.setOnPreferenceClickListener(pref -> {
107             String tzId = timeZone.get(ZoneGetter.KEY_ID).toString();
108             ManualTimeZoneSuggestion suggestion = TimeZoneDetector.createManualTimeZoneSuggestion(
109                     tzId, "Settings: Set time zone");
110             mTimeZoneDetector.suggestManualTimeZone(suggestion);
111             getFragmentController().goBack();
112 
113             // Note: This is intentionally ACTION_TIME_CHANGED, not ACTION_TIMEZONE_CHANGED.
114             // Timezone change is handled by the alarm manager. This broadcast message is used
115             // to update the clock and other time related displays that the time has changed due
116             // to a change in the timezone.
117             getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
118             return true;
119         });
120         return preference;
121     }
122 
123     /** Compares the timezone objects returned by {@link ZoneGetter}. */
124     private static final class TimeZonesComparator implements Comparator<Map<String, Object>> {
125 
126         /** Compares timezones based on 1. offset, 2. display label/name. */
TimeZonesComparator()127         TimeZonesComparator() {
128         }
129 
130         @Override
compare(Map<String, Object> map1, Map<String, Object> map2)131         public int compare(Map<String, Object> map1, Map<String, Object> map2) {
132             int timeZoneOffsetCompare = compareWithKey(map1, map2, ZoneGetter.KEY_OFFSET);
133 
134             // If equivalent timezone offset, compare based on display label.
135             if (timeZoneOffsetCompare == 0) {
136                 return compareWithKey(map1, map2, ZoneGetter.KEY_DISPLAY_LABEL);
137             }
138 
139             return timeZoneOffsetCompare;
140         }
141 
compareWithKey(Map<String, Object> map1, Map<String, Object> map2, String comparisonKey)142         private int compareWithKey(Map<String, Object> map1, Map<String, Object> map2,
143                 String comparisonKey) {
144             Object value1 = map1.get(comparisonKey);
145             Object value2 = map2.get(comparisonKey);
146             if (!isComparable(value1) || !isComparable(value2)) {
147                 throw new IllegalArgumentException(
148                         "Cannot use Map which has values that are not Comparable");
149             }
150             return ((Comparable) value1).compareTo(value2);
151         }
152 
isComparable(Object value)153         private boolean isComparable(Object value) {
154             return (value != null) && (value instanceof Comparable);
155         }
156     }
157 }
158