• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 
26 import com.android.deskclock.R
27 import com.android.deskclock.Utils
28 import com.android.deskclock.data.City.NameComparator
29 import com.android.deskclock.data.City.NameIndexComparator
30 import com.android.deskclock.data.City.UtcOffsetComparator
31 import com.android.deskclock.data.City.UtcOffsetIndexComparator
32 import com.android.deskclock.data.DataModel.CitySort
33 import com.android.deskclock.settings.SettingsActivity
34 
35 import java.util.Collections
36 
37 /**
38  * All [City] data is accessed via this model.
39  */
40 internal class CityModel(
41     private val context: Context,
42     private val prefs: SharedPreferences,
43     /** The model from which settings are fetched.  */
44     private val settingsModel: SettingsModel
45 ) {
46 
47     /**
48      * Retain a hard reference to the shared preference observer to prevent it from being garbage
49      * collected. See [SharedPreferences.registerOnSharedPreferenceChangeListener] for detail.
50      */
51     private val mPreferenceListener: OnSharedPreferenceChangeListener = PreferenceListener()
52 
53     /** Clears data structures containing data that is locale-sensitive.  */
54     private val mLocaleChangedReceiver: BroadcastReceiver = LocaleChangedReceiver()
55 
56     /** List of listeners to invoke upon world city list change  */
57     private val mCityListeners: MutableList<CityListener> = ArrayList()
58 
59     /** Maps city ID to city instance.  */
60     private var mCityMap: Map<String, City>? = null
61 
62     /** List of city instances in display order.  */
63     private var mAllCities: List<City>? = null
64 
65     /** List of selected city instances in display order.  */
66     private var mSelectedCities: List<City>? = null
67 
68     /** List of unselected city instances in display order.  */
69     private var mUnselectedCities: List<City>? = null
70 
71     /** A city instance representing the home timezone of the user.  */
72     private var mHomeCity: City? = null
73 
74     init {
75         // Clear caches affected by locale when locale changes.
76         val localeBroadcastFilter = IntentFilter(Intent.ACTION_LOCALE_CHANGED)
77         context.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter)
78 
79         // Clear caches affected by preferences when preferences change.
80         prefs.registerOnSharedPreferenceChangeListener(mPreferenceListener)
81     }
82 
addCityListenernull83     fun addCityListener(cityListener: CityListener) {
84         mCityListeners.add(cityListener)
85     }
86 
removeCityListenernull87     fun removeCityListener(cityListener: CityListener) {
88         mCityListeners.remove(cityListener)
89     }
90 
91     /**
92      * @return a list of all cities in their display order
93      */
94     val allCities: List<City>
95         get() {
96             if (mAllCities == null) {
97                 // Create a set of selections to identify the unselected cities.
98                 val selected: List<City> = selectedCities.toMutableList()
99 
100                 // Sort the selected cities alphabetically by name.
101                 Collections.sort(selected, NameComparator())
102 
103                 // Combine selected and unselected cities into a single list.
104                 val allCities: MutableList<City> = ArrayList(cityMap.size)
105                 allCities.addAll(selected)
106                 allCities.addAll(unselectedCities)
107                 mAllCities = allCities
108             }
109 
110             return mAllCities!!
111         }
112 
113     /**
114      * @return a city representing the user's home timezone
115      */
116     val homeCity: City
117         get() {
118             if (mHomeCity == null) {
119                 val name: String = context.getString(R.string.home_label)
120                 val timeZone = settingsModel.homeTimeZone
121                 mHomeCity = City(null, -1, null, name, name, timeZone)
122             }
123 
124             return mHomeCity!!
125         }
126 
127     /**
128      * @return a list of cities not selected for display
129      */
130     val unselectedCities: List<City>
131         get() {
132             if (mUnselectedCities == null) {
133                 // Create a set of selections to identify the unselected cities.
134                 val selected: List<City> = selectedCities.toMutableList()
135                 val selectedSet: Set<City> = Utils.newArraySet(selected)
136 
137                 val all = cityMap.values
138                 val unselected: MutableList<City> = ArrayList(all.size - selectedSet.size)
139                 for (city in all) {
140                     if (!selectedSet.contains(city)) {
141                         unselected.add(city)
142                     }
143                 }
144 
145                 // Sort the unselected cities according by the user's preferred sort.
146                 Collections.sort(unselected, citySortComparator)
147                 mUnselectedCities = unselected
148             }
149 
150             return mUnselectedCities!!
151         }
152 
153     /**
154      * @return a list of cities selected for display
155      */
156     val selectedCities: List<City>
157         get() {
158             if (mSelectedCities == null) {
159                 val selectedCities = CityDAO.getSelectedCities(prefs, cityMap)
160                 Collections.sort(selectedCities, UtcOffsetComparator())
161                 mSelectedCities = selectedCities
162             }
163 
164             return mSelectedCities!!
165         }
166 
167     /**
168      * @param cities the new collection of cities selected for display by the user
169      */
setSelectedCitiesnull170     fun setSelectedCities(cities: Collection<City>) {
171         val oldCities = allCities
172         CityDAO.setSelectedCities(prefs, cities)
173 
174         // Clear caches affected by this update.
175         mAllCities = null
176         mSelectedCities = null
177         mUnselectedCities = null
178 
179         // Broadcast the change to the selected cities for the benefit of widgets.
180         fireCitiesChanged(oldCities, allCities)
181     }
182 
183     /**
184      * @return a comparator used to locate index positions
185      */
186     val cityIndexComparator: Comparator<City>
187         get() = when (settingsModel.citySort) {
188             CitySort.NAME -> NameIndexComparator()
189             CitySort.UTC_OFFSET -> UtcOffsetIndexComparator()
190         }
191 
192     /**
193      * @return the order in which cities are sorted
194      */
195     val citySort: CitySort
196         get() = settingsModel.citySort
197 
198     /**
199      * Adjust the order in which cities are sorted.
200      */
toggleCitySortnull201     fun toggleCitySort() {
202         settingsModel.toggleCitySort()
203 
204         // Clear caches affected by this update.
205         mAllCities = null
206         mUnselectedCities = null
207     }
208 
209     private val cityMap: Map<String, City>
210         get() {
211             if (mCityMap == null) {
212                 mCityMap = CityDAO.getCities(context)
213             }
214 
215             return mCityMap!!
216         }
217 
218     private val citySortComparator: Comparator<City>
219         get() = when (settingsModel.citySort) {
220             CitySort.NAME -> NameComparator()
221             CitySort.UTC_OFFSET -> UtcOffsetComparator()
222         }
223 
fireCitiesChangednull224     private fun fireCitiesChanged(oldCities: List<City>, newCities: List<City>) {
225         context.sendBroadcast(Intent(DataModel.ACTION_WORLD_CITIES_CHANGED))
226         for (cityListener in mCityListeners) {
227             cityListener.citiesChanged(oldCities, newCities)
228         }
229     }
230 
231     /**
232      * Cached information that is locale-sensitive must be cleared in response to locale changes.
233      */
234     private inner class LocaleChangedReceiver : BroadcastReceiver() {
onReceivenull235         override fun onReceive(context: Context?, intent: Intent?) {
236             mCityMap = null
237             mHomeCity = null
238             mAllCities = null
239             mSelectedCities = null
240             mUnselectedCities = null
241         }
242     }
243 
244     /**
245      * This receiver is notified when shared preferences change. Cached information built on
246      * preferences must be cleared.
247      */
248     private inner class PreferenceListener : OnSharedPreferenceChangeListener {
onSharedPreferenceChangednull249         override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
250             when (key) {
251                 SettingsActivity.KEY_HOME_TZ -> {
252                     mHomeCity = null
253                     val cities = allCities
254                     fireCitiesChanged(cities, cities)
255                 }
256                 SettingsActivity.KEY_AUTO_HOME_CLOCK -> {
257                     val cities = allCities
258                     fireCitiesChanged(cities, cities)
259                 }
260             }
261         }
262     }
263 }