Home | History | Annotate | Download | only in data
      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 
     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      */
     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      */
     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      */
     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
    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 
    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