• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.deskclock.data;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.SharedPreferences;
24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
25 import android.preference.PreferenceManager;
26 
27 import com.android.deskclock.R;
28 import com.android.deskclock.Utils;
29 import com.android.deskclock.data.DataModel.CitySort;
30 import com.android.deskclock.settings.SettingsActivity;
31 
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.Comparator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TimeZone;
40 
41 /**
42  * All {@link City} data is accessed via this model.
43  */
44 final class CityModel {
45 
46     private final Context mContext;
47 
48     /** The model from which settings are fetched. */
49     private final SettingsModel mSettingsModel;
50 
51     /**
52      * Retain a hard reference to the shared preference observer to prevent it from being garbage
53      * collected. See {@link SharedPreferences#registerOnSharedPreferenceChangeListener} for detail.
54      */
55     private final OnSharedPreferenceChangeListener mPreferenceListener = new PreferenceListener();
56 
57     /** Clears data structures containing data that is locale-sensitive. */
58     private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
59 
60     /** Maps city ID to city instance. */
61     private Map<String, City> mCityMap;
62 
63     /** List of city instances in display order. */
64     private List<City> mAllCities;
65 
66     /** List of selected city instances in display order. */
67     private List<City> mSelectedCities;
68 
69     /** List of unselected city instances in display order. */
70     private List<City> mUnselectedCities;
71 
72     /** A city instance representing the home timezone of the user. */
73     private City mHomeCity;
74 
CityModel(Context context, SettingsModel settingsModel)75     CityModel(Context context, SettingsModel settingsModel) {
76         mContext = context;
77         mSettingsModel = settingsModel;
78 
79         // Clear caches affected by locale when locale changes.
80         final IntentFilter localeBroadcastFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
81         mContext.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter);
82 
83         // Clear caches affected by preferences when preferences change.
84         final SharedPreferences prefs = Utils.getDefaultSharedPreferences(mContext);
85         prefs.registerOnSharedPreferenceChangeListener(mPreferenceListener);
86     }
87 
88     /**
89      * @return a list of all cities in their display order
90      */
getAllCities()91     List<City> getAllCities() {
92         if (mAllCities == null) {
93             // Create a set of selections to identify the unselected cities.
94             final List<City> selected = new ArrayList<>(getSelectedCities());
95 
96             // Sort the selected cities alphabetically by name.
97             Collections.sort(selected, new City.NameComparator());
98 
99             // Combine selected and unselected cities into a single list.
100             final List<City> allCities = new ArrayList<>(getCityMap().size());
101             allCities.addAll(selected);
102             allCities.addAll(getUnselectedCities());
103             mAllCities = Collections.unmodifiableList(allCities);
104         }
105 
106         return mAllCities;
107     }
108 
109     /**
110      * @param cityName the case-insensitive city name to search for
111      * @return the city with the given {@code cityName}; {@code null} if no such city exists
112      */
getCity(String cityName)113     City getCity(String cityName) {
114         cityName = cityName.toUpperCase();
115 
116         for (City city : getAllCities()) {
117             if (cityName.equals(city.getNameUpperCase())) {
118                 return city;
119             }
120         }
121 
122         return null;
123     }
124 
125     /**
126      * @return a city representing the user's home timezone
127      */
getHomeCity()128     City getHomeCity() {
129         if (mHomeCity == null) {
130             final String name = mContext.getString(R.string.home_label);
131             final TimeZone timeZone = mSettingsModel.getHomeTimeZone();
132             mHomeCity = new City(null, -1, null, name, name, timeZone.getID());
133         }
134 
135         return mHomeCity;
136     }
137 
138     /**
139      * @return a list of cities not selected for display
140      */
getUnselectedCities()141     List<City> getUnselectedCities() {
142         if (mUnselectedCities == null) {
143             // Create a set of selections to identify the unselected cities.
144             final List<City> selected = new ArrayList<>(getSelectedCities());
145             final Set<City> selectedSet = Utils.newArraySet(selected);
146 
147             final Collection<City> all = getCityMap().values();
148             final List<City> unselected = new ArrayList<>(all.size() - selectedSet.size());
149             for (City city : all) {
150                 if (!selectedSet.contains(city)) {
151                     unselected.add(city);
152                 }
153             }
154 
155             // Sort the unselected cities according by the user's preferred sort.
156             Collections.sort(unselected, getCitySortComparator());
157             mUnselectedCities = Collections.unmodifiableList(unselected);
158         }
159 
160         return mUnselectedCities;
161     }
162 
163     /**
164      * @return a list of cities selected for display
165      */
getSelectedCities()166     List<City> getSelectedCities() {
167         if (mSelectedCities == null) {
168             final List<City> selectedCities = CityDAO.getSelectedCities(mContext, getCityMap());
169             Collections.sort(selectedCities, new City.UtcOffsetComparator());
170             mSelectedCities = Collections.unmodifiableList(selectedCities);
171         }
172 
173         return mSelectedCities;
174     }
175 
176     /**
177      * @param cities the new collection of cities selected for display by the user
178      */
setSelectedCities(Collection<City> cities)179     void setSelectedCities(Collection<City> cities) {
180         CityDAO.setSelectedCities(mContext, cities);
181 
182         // Clear caches affected by this update.
183         mAllCities = null;
184         mSelectedCities = null;
185         mUnselectedCities = null;
186 
187         // Broadcast the change to the selected cities for the benefit of widgets.
188         sendCitiesChangedBroadcast();
189     }
190 
191     /**
192      * @return a comparator used to locate index positions
193      */
getCityIndexComparator()194     Comparator<City> getCityIndexComparator() {
195         final CitySort citySort = mSettingsModel.getCitySort();
196         switch (citySort) {
197             case NAME: return new City.NameIndexComparator();
198             case UTC_OFFSET: return new City.UtcOffsetIndexComparator();
199         }
200         throw new IllegalStateException("unexpected city sort: " + citySort);
201     }
202 
203     /**
204      * @return the order in which cities are sorted
205      */
getCitySort()206     CitySort getCitySort() {
207         return mSettingsModel.getCitySort();
208     }
209 
210     /**
211      * Adjust the order in which cities are sorted.
212      */
toggleCitySort()213     void toggleCitySort() {
214         mSettingsModel.toggleCitySort();
215 
216         // Clear caches affected by this update.
217         mAllCities = null;
218         mUnselectedCities = null;
219     }
220 
getCityMap()221     private Map<String, City> getCityMap() {
222         if (mCityMap == null) {
223             mCityMap = CityDAO.getCities(mContext);
224         }
225 
226         return mCityMap;
227     }
228 
getCitySortComparator()229     private Comparator<City> getCitySortComparator() {
230         final CitySort citySort = mSettingsModel.getCitySort();
231         switch (citySort) {
232             case NAME: return new City.NameComparator();
233             case UTC_OFFSET: return new City.UtcOffsetComparator();
234         }
235         throw new IllegalStateException("unexpected city sort: " + citySort);
236     }
237 
sendCitiesChangedBroadcast()238     private void sendCitiesChangedBroadcast() {
239         mContext.sendBroadcast(new Intent(DataModel.ACTION_DIGITAL_WIDGET_CHANGED));
240     }
241 
242     /**
243      * Cached information that is locale-sensitive must be cleared in response to locale changes.
244      */
245     private final class LocaleChangedReceiver extends BroadcastReceiver {
246         @Override
onReceive(Context context, Intent intent)247         public void onReceive(Context context, Intent intent) {
248             mCityMap = null;
249             mHomeCity = null;
250             mAllCities = null;
251             mSelectedCities = null;
252             mUnselectedCities = null;
253         }
254     }
255 
256     /**
257      * This receiver is notified when shared preferences change. Cached information built on
258      * preferences must be cleared.
259      */
260     private final class PreferenceListener implements OnSharedPreferenceChangeListener {
261         @Override
onSharedPreferenceChanged(SharedPreferences prefs, String key)262         public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
263             switch (key) {
264                 case SettingsActivity.KEY_HOME_TZ:
265                     mHomeCity = null;
266                 case SettingsActivity.KEY_AUTO_HOME_CLOCK:
267                     sendCitiesChangedBroadcast();
268                     break;
269             }
270         }
271     }
272 }