blob: e9956c66af858deb312e27b9761327c9306d8ccd [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.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.ArrayMap;
import androidx.annotation.VisibleForTesting;
import com.android.deskclock.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class encapsulates the transfer of data between {@link City} domain objects and their
* permanent storage in {@link Resources} and {@link SharedPreferences}.
*/
final class CityDAO {
/** Regex to match numeric index values when parsing city names. */
private static final Pattern NUMERIC_INDEX_REGEX = Pattern.compile("\\d+");
/** Key to a preference that stores the number of selected cities. */
private static final String NUMBER_OF_CITIES = "number_of_cities";
/** Prefix for a key to a preference that stores the id of a selected city. */
private static final String CITY_ID = "city_id_";
private CityDAO() {}
/**
* @param cityMap maps city ids to city instances
* @return the list of city ids selected for display by the user
*/
static List<City> getSelectedCities(SharedPreferences prefs, Map<String, City> cityMap) {
final int size = prefs.getInt(NUMBER_OF_CITIES, 0);
final List<City> selectedCities = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final String id = prefs.getString(CITY_ID + i, null);
final City city = cityMap.get(id);
if (city != null) {
selectedCities.add(city);
}
}
return selectedCities;
}
/**
* @param cities the collection of cities selected for display by the user
*/
static void setSelectedCities(SharedPreferences prefs, Collection<City> cities) {
final SharedPreferences.Editor editor = prefs.edit();
editor.putInt(NUMBER_OF_CITIES, cities.size());
int count = 0;
for (City city : cities) {
editor.putString(CITY_ID + count, city.getId());
count++;
}
editor.apply();
}
/**
* @return the domain of cities from which the user may choose a world clock
*/
static Map<String, City> getCities(Context context) {
final Resources resources = context.getResources();
final TypedArray cityStrings = resources.obtainTypedArray(R.array.city_ids);
final int citiesCount = cityStrings.length();
final Map<String, City> cities = new ArrayMap<>(citiesCount);
try {
for (int i = 0; i < citiesCount; ++i) {
// Attempt to locate the resource id defining the city as a string.
final int cityResourceId = cityStrings.getResourceId(i, 0);
if (cityResourceId == 0) {
final String message = String.format(Locale.ENGLISH,
"Unable to locate city resource id for index %d", i);
throw new IllegalStateException(message);
}
final String id = resources.getResourceEntryName(cityResourceId);
final String cityString = cityStrings.getString(i);
if (cityString == null) {
final String message = String.format("Unable to locate city with id %s", id);
throw new IllegalStateException(message);
}
// Attempt to parse the time zone from the city entry.
final String[] cityParts = cityString.split("[|]");
if (cityParts.length != 2) {
final String message = String.format(
"Error parsing malformed city %s", cityString);
throw new IllegalStateException(message);
}
final City city = createCity(id, cityParts[0], cityParts[1]);
// Skip cities whose timezone cannot be resolved.
if (city != null) {
cities.put(id, city);
}
}
} finally {
cityStrings.recycle();
}
return Collections.unmodifiableMap(cities);
}
/**
* @param id unique identifier for city
* @param formattedName "[index string]=[name]" or "[index string]=[name]:[phonetic name]",
* If [index string] is empty, use the first character of name as index,
* If phonetic name is empty, use the name itself as phonetic name.
* @param tzId the string id of the timezone a given city is located in
*/
@VisibleForTesting
static City createCity(String id, String formattedName, String tzId) {
final TimeZone tz = TimeZone.getTimeZone(tzId);
// If the time zone lookup fails, GMT is returned. No cities actually map to GMT.
if ("GMT".equals(tz.getID())) {
return null;
}
final String[] parts = formattedName.split("[=:]");
final String name = parts[1];
// Extract index string from input, use the first character of city name as the index string
// if one is not explicitly provided.
final String indexString = TextUtils.isEmpty(parts[0])
? name.substring(0, 1) : parts[0];
final String phoneticName = parts.length == 3 ? parts[2] : name;
final Matcher matcher = NUMERIC_INDEX_REGEX.matcher(indexString);
final int index = matcher.find() ? Integer.parseInt(matcher.group()) : -1;
return new City(id, index, indexString, name, phoneticName, tz);
}
}