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.Context;
20 import android.content.SharedPreferences;
21 import android.content.res.Resources;
22 import android.preference.PreferenceManager;
23 import android.support.annotation.VisibleForTesting;
24 import android.text.TextUtils;
25 import android.util.ArrayMap;
26 
27 import com.android.deskclock.R;
28 import com.android.deskclock.Utils;
29 
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38 
39 /**
40  * This class encapsulates the transfer of data between {@link City} domain objects and their
41  * permanent storage in {@link Resources} and {@link SharedPreferences}.
42  */
43 final class CityDAO {
44 
45     // Regex to match natural index values when parsing city names.
46     private static final Pattern INDEX_REGEX = Pattern.compile("\\d+");
47 
48     // Key to a preference that stores the number of selected cities.
49     private static final String NUMBER_OF_CITIES = "number_of_cities";
50 
51     // Prefix for a key to a preference that stores the id of a selected city.
52     private static final String CITY_ID = "city_id_";
53 
54     // Lazily instantiated and cached for the life of the application.
55     private static SharedPreferences sPrefs;
56 
CityDAO()57     private CityDAO() {}
58 
59     /**
60      * @param cityMap maps city ids to city instances
61      * @return the list of city ids selected for display by the user
62      */
getSelectedCities(Context context, Map<String, City> cityMap)63     public static List<City> getSelectedCities(Context context, Map<String, City> cityMap) {
64         final SharedPreferences prefs = getSharedPreferences(context);
65         final int size = prefs.getInt(NUMBER_OF_CITIES, 0);
66         final List<City> selectedCities = new ArrayList<>(size);
67 
68         for (int i = 0; i < size; i++) {
69             final String id = prefs.getString(CITY_ID + i, null);
70             final City city = cityMap.get(id);
71             if (city != null) {
72                 selectedCities.add(city);
73             }
74         }
75 
76         return selectedCities;
77     }
78 
79     /**
80      * @param cities the collection of cities selected for display by the user
81      */
setSelectedCities(Context context, Collection<City> cities)82     public static void setSelectedCities(Context context, Collection<City> cities) {
83         final SharedPreferences prefs = getSharedPreferences(context);
84         final SharedPreferences.Editor editor = prefs.edit();
85         editor.putInt(NUMBER_OF_CITIES, cities.size());
86 
87         int count = 0;
88         for (City city : cities) {
89             editor.putString(CITY_ID + count, city.getId());
90             count++;
91         }
92 
93         editor.apply();
94     }
95 
96     /**
97      * @return the domain of cities from which the user may choose a world clock
98      */
getCities(Context context)99     public static Map<String, City> getCities(Context context) {
100         final Resources resources = context.getResources();
101         final String[] ids = resources.getStringArray(R.array.cities_id);
102         final String[] names = resources.getStringArray(R.array.cities_names);
103         final String[] timezones = resources.getStringArray(R.array.cities_tz);
104 
105         if (ids.length != names.length) {
106             final String locale = Locale.getDefault().toString();
107             final String format = "id count (%d) != name count (%d) for locale %s";
108             final String message = String.format(format, ids.length, names.length, locale);
109             throw new IllegalStateException(message);
110         }
111 
112         if (ids.length != timezones.length) {
113             final String locale = Locale.getDefault().toString();
114             final String format = "id count (%d) != timezone count (%d) for locale %s";
115             final String message = String.format(format, ids.length, timezones.length, locale);
116             throw new IllegalStateException(message);
117         }
118 
119         final Map<String, City> cities = new ArrayMap<>(ids.length);
120         for (int i = 0; i < ids.length; i++) {
121             final String id = ids[i];
122             if ("C0".equals(id)) {
123                 continue;
124             }
125             cities.put(id, createCity(id, names[i], timezones[i]));
126         }
127         return Collections.unmodifiableMap(cities);
128     }
129 
130     /**
131      * @param id unique identifier for city
132      * @param formattedName "[index string]=[name]" or "[index string]=[name]:[phonetic name]",
133      *                      If [index string] is empty, we use the first character of name as index,
134      *                      If phonetic name is empty, we use the name itself as phonetic.
135      * @param timeZoneId identifies the timezone in which the city is located
136      */
137     @VisibleForTesting
createCity(String id, String formattedName, String timeZoneId)138     static City createCity(String id, String formattedName, String timeZoneId) {
139         final String[] parts = formattedName.split("[=:]");
140         final String name = parts[1];
141         // Extract index string from input, use the first character of city name as index string
142         // if it's not explicitly provided.
143         final String indexString = TextUtils.isEmpty(parts[0])
144                 ? String.valueOf(name.charAt(0)) : parts[0];
145         final String phoneticName = parts.length == 3 ? parts[2] : name;
146 
147         final Matcher matcher = INDEX_REGEX.matcher(indexString);
148         final int index = matcher.find() ? Integer.parseInt(matcher.group()) : -1;
149 
150         return new City(id, index, indexString, name, phoneticName, timeZoneId);
151     }
152 
getSharedPreferences(Context context)153     private static SharedPreferences getSharedPreferences(Context context) {
154         if (sPrefs == null) {
155             sPrefs = Utils.getDefaultSharedPreferences(context.getApplicationContext());
156         }
157 
158         return sPrefs;
159     }
160 }
161