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 }