blob: 458a7a3c6ddebfa2b57115517d1916255a001de0 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.deskclock.data;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import com.android.deskclock.R;
import com.android.deskclock.Utils;
import com.android.deskclock.data.DataModel.CitySort;
import com.android.deskclock.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
/**
* All {@link City} data is accessed via this model.
*/
final class CityModel {
private final Context mContext;
private final SharedPreferences mPrefs;
/** The model from which settings are fetched. */
private final SettingsModel mSettingsModel;
/**
* Retain a hard reference to the shared preference observer to prevent it from being garbage
* collected. See {@link SharedPreferences#registerOnSharedPreferenceChangeListener} for detail.
*/
@SuppressWarnings("FieldCanBeLocal")
private final OnSharedPreferenceChangeListener mPreferenceListener = new PreferenceListener();
/** Clears data structures containing data that is locale-sensitive. */
@SuppressWarnings("FieldCanBeLocal")
private final BroadcastReceiver mLocaleChangedReceiver = new LocaleChangedReceiver();
/** List of listeners to invoke upon world city list change */
private final List<CityListener> mCityListeners = new ArrayList<>();
/** Maps city ID to city instance. */
private Map<String, City> mCityMap;
/** List of city instances in display order. */
private List<City> mAllCities;
/** List of selected city instances in display order. */
private List<City> mSelectedCities;
/** List of unselected city instances in display order. */
private List<City> mUnselectedCities;
/** A city instance representing the home timezone of the user. */
private City mHomeCity;
CityModel(Context context, SharedPreferences prefs, SettingsModel settingsModel) {
mContext = context;
mPrefs = prefs;
mSettingsModel = settingsModel;
// Clear caches affected by locale when locale changes.
final IntentFilter localeBroadcastFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
mContext.registerReceiver(mLocaleChangedReceiver, localeBroadcastFilter,
Context.RECEIVER_NOT_EXPORTED);
// Clear caches affected by preferences when preferences change.
prefs.registerOnSharedPreferenceChangeListener(mPreferenceListener);
}
void addCityListener(CityListener cityListener) {
mCityListeners.add(cityListener);
}
void removeCityListener(CityListener cityListener) {
mCityListeners.remove(cityListener);
}
/**
* @return a list of all cities in their display order
*/
List<City> getAllCities() {
if (mAllCities == null) {
// Create a set of selections to identify the unselected cities.
final List<City> selected = new ArrayList<>(getSelectedCities());
// Sort the selected cities alphabetically by name.
selected.sort(new City.NameComparator());
// Combine selected and unselected cities into a single list.
final List<City> allCities = new ArrayList<>(getCityMap().size());
allCities.addAll(selected);
allCities.addAll(getUnselectedCities());
mAllCities = Collections.unmodifiableList(allCities);
}
return mAllCities;
}
/**
* @return a city representing the user's home timezone
*/
City getHomeCity() {
if (mHomeCity == null) {
final String name = mContext.getString(R.string.home_label);
final TimeZone timeZone = mSettingsModel.getHomeTimeZone();
mHomeCity = new City(null, -1, null, name, name, timeZone);
}
return mHomeCity;
}
/**
* @return a list of cities not selected for display
*/
List<City> getUnselectedCities() {
if (mUnselectedCities == null) {
// Create a set of selections to identify the unselected cities.
final List<City> selected = new ArrayList<>(getSelectedCities());
final Set<City> selectedSet = Utils.newArraySet(selected);
final Collection<City> all = getCityMap().values();
final List<City> unselected = new ArrayList<>(all.size() - selectedSet.size());
for (City city : all) {
if (!selectedSet.contains(city)) {
unselected.add(city);
}
}
// Sort the unselected cities according by the user's preferred sort.
unselected.sort(getCitySortComparator());
mUnselectedCities = Collections.unmodifiableList(unselected);
}
return mUnselectedCities;
}
/**
* @return a list of cities selected for display
*/
List<City> getSelectedCities() {
if (mSelectedCities == null) {
final List<City> selectedCities = CityDAO.getSelectedCities(mPrefs, getCityMap());
selectedCities.sort(new City.UtcOffsetComparator());
mSelectedCities = Collections.unmodifiableList(selectedCities);
}
return mSelectedCities;
}
/**
* @param cities the new collection of cities selected for display by the user
*/
void setSelectedCities(Collection<City> cities) {
CityDAO.setSelectedCities(mPrefs, cities);
// Clear caches affected by this update.
mAllCities = null;
mSelectedCities = null;
mUnselectedCities = null;
// Broadcast the change to the selected cities for the benefit of widgets.
fireCitiesChanged();
}
/**
* @return a comparator used to locate index positions
*/
Comparator<City> getCityIndexComparator() {
final CitySort citySort = mSettingsModel.getCitySort();
switch (citySort) {
case NAME: return new City.NameIndexComparator();
case UTC_OFFSET: return new City.UtcOffsetIndexComparator();
}
throw new IllegalStateException("unexpected city sort: " + citySort);
}
/**
* @return the order in which cities are sorted
*/
CitySort getCitySort() {
return mSettingsModel.getCitySort();
}
/**
* Adjust the order in which cities are sorted.
*/
void toggleCitySort() {
mSettingsModel.toggleCitySort();
// Clear caches affected by this update.
mAllCities = null;
mUnselectedCities = null;
}
private Map<String, City> getCityMap() {
if (mCityMap == null) {
mCityMap = CityDAO.getCities(mContext);
}
return mCityMap;
}
private Comparator<City> getCitySortComparator() {
final CitySort citySort = mSettingsModel.getCitySort();
switch (citySort) {
case NAME: return new City.NameComparator();
case UTC_OFFSET: return new City.UtcOffsetComparator();
}
throw new IllegalStateException("unexpected city sort: " + citySort);
}
private void fireCitiesChanged() {
mContext.sendBroadcast(new Intent(DataModel.ACTION_WORLD_CITIES_CHANGED));
for (CityListener cityListener : mCityListeners) {
cityListener.citiesChanged();
}
}
/**
* Cached information that is locale-sensitive must be cleared in response to locale changes.
*/
private final class LocaleChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
mCityMap = null;
mHomeCity = null;
mAllCities = null;
mSelectedCities = null;
mUnselectedCities = null;
}
}
/**
* This receiver is notified when shared preferences change. Cached information built on
* preferences must be cleared.
*/
private final class PreferenceListener implements OnSharedPreferenceChangeListener {
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
switch (key) {
case SettingsActivity.KEY_HOME_TZ:
mHomeCity = null;
case SettingsActivity.KEY_AUTO_HOME_CLOCK:
fireCitiesChanged();
break;
}
}
}
}