1/*
2 * Copyright (C) 2012 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
17package com.android.deskclock.worldclock;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.preference.PreferenceManager;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.BaseAdapter;
26import android.widget.TextClock;
27import android.widget.TextView;
28
29import com.android.deskclock.AnalogClock;
30import com.android.deskclock.R;
31import com.android.deskclock.SettingsActivity;
32import com.android.deskclock.Utils;
33
34import java.text.Collator;
35import java.util.Arrays;
36import java.util.Calendar;
37import java.util.Comparator;
38import java.util.Date;
39import java.util.HashMap;
40import java.util.Locale;
41import java.util.TimeZone;
42
43public class WorldClockAdapter extends BaseAdapter {
44    protected Object [] mCitiesList;
45    private final LayoutInflater mInflater;
46    private final Context mContext;
47    private String mClockStyle;
48    private final Collator mCollator = Collator.getInstance();
49    protected HashMap<String, CityObj> mCitiesDb = new HashMap<String, CityObj>();
50    private int mClocksPerRow;
51
52    public WorldClockAdapter(Context context) {
53        super();
54        mContext = context;
55        loadData(context);
56        loadCitiesDb(context);
57        mInflater = LayoutInflater.from(context);
58        mClocksPerRow = context.getResources().getInteger(R.integer.world_clocks_per_row);
59    }
60
61    public void reloadData(Context context) {
62        loadData(context);
63        notifyDataSetChanged();
64    }
65
66    public void loadData(Context context) {
67        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
68        mClockStyle = prefs.getString(SettingsActivity.KEY_CLOCK_STYLE,
69                mContext.getResources().getString(R.string.default_clock_style));
70        mCitiesList = Cities.readCitiesFromSharedPrefs(prefs).values().toArray();
71        sortList();
72        mCitiesList = addHomeCity();
73    }
74
75    public void loadCitiesDb(Context context) {
76        mCitiesDb.clear();
77        // Read the cities DB so that the names and timezones will be taken from the DB
78        // and not from the selected list so that change of locale or changes in the DB will
79        // be reflected.
80        CityObj[] cities = Utils.loadCitiesFromXml(context);
81        if (cities != null) {
82            for (int i = 0; i < cities.length; i ++) {
83                mCitiesDb.put(cities[i].mCityId, cities [i]);
84            }
85        }
86    }
87
88    /***
89     * Adds the home city as the first item of the adapter if the feature is on and the device time
90     * zone is different from the home time zone that was set by the user.
91     * return the list of cities.
92     */
93    private Object[] addHomeCity() {
94        if (needHomeCity()) {
95            SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
96            String homeTZ = sharedPref.getString(SettingsActivity.KEY_HOME_TZ, "");
97            CityObj c = new CityObj(
98                    mContext.getResources().getString(R.string.home_label), homeTZ, null);
99            Object[] temp = new Object[mCitiesList.length + 1];
100            temp[0] = c;
101            for (int i = 0; i < mCitiesList.length; i++) {
102                temp[i + 1] = mCitiesList[i];
103            }
104            return temp;
105        } else {
106            return mCitiesList;
107        }
108    }
109
110    public void updateHomeLabel(Context context) {
111        // Update the "home" label if the home time zone clock is shown
112        if (needHomeCity() && mCitiesList.length > 0) {
113            ((CityObj) mCitiesList[0]).mCityName =
114                    context.getResources().getString(R.string.home_label);
115        }
116    }
117
118    public boolean needHomeCity() {
119        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
120        if (sharedPref.getBoolean(SettingsActivity.KEY_AUTO_HOME_CLOCK, false)) {
121            String homeTZ = sharedPref.getString(
122                    SettingsActivity.KEY_HOME_TZ, TimeZone.getDefault().getID());
123            final Date now = new Date();
124            return TimeZone.getTimeZone(homeTZ).getOffset(now.getTime())
125                    != TimeZone.getDefault().getOffset(now.getTime());
126        } else {
127            return false;
128        }
129    }
130
131    public boolean hasHomeCity() {
132        return (mCitiesList != null) && mCitiesList.length > 0
133                && ((CityObj) mCitiesList[0]).mCityId == null;
134    }
135
136    private void sortList() {
137        final Date now = new Date();
138
139        // Sort by the Offset from GMT taking DST into account
140        // and if the same sort by City Name
141        Arrays.sort(mCitiesList, new Comparator<Object>() {
142            private int safeCityNameCompare(CityObj city1, CityObj city2) {
143                if (city1.mCityName == null && city2.mCityName == null) {
144                    return 0;
145                } else if (city1.mCityName == null) {
146                    return -1;
147                } else if (city2.mCityName == null) {
148                    return 1;
149                } else {
150                    return mCollator.compare(city1.mCityName, city2.mCityName);
151                }
152            }
153
154            @Override
155            public int compare(Object object1, Object object2) {
156                CityObj city1 = (CityObj) object1;
157                CityObj city2 = (CityObj) object2;
158                if (city1.mTimeZone == null && city2.mTimeZone == null) {
159                    return safeCityNameCompare(city1, city2);
160                } else if (city1.mTimeZone == null) {
161                    return -1;
162                } else if (city2.mTimeZone == null) {
163                    return 1;
164                }
165
166                int gmOffset1 = TimeZone.getTimeZone(city1.mTimeZone).getOffset(now.getTime());
167                int gmOffset2 = TimeZone.getTimeZone(city2.mTimeZone).getOffset(now.getTime());
168                if (gmOffset1 == gmOffset2) {
169                    return safeCityNameCompare(city1, city2);
170                } else {
171                    return gmOffset1 - gmOffset2;
172                }
173            }
174        });
175    }
176
177    @Override
178    public int getCount() {
179        if (mClocksPerRow == 1) {
180            // In the special case where we have only 1 clock per view.
181            return mCitiesList.length;
182        }
183
184        // Otherwise, each item in the list holds 1 or 2 clocks
185        return (mCitiesList.length  + 1)/2;
186    }
187
188    @Override
189    public Object getItem(int p) {
190        return null;
191    }
192
193    @Override
194    public long getItemId(int p) {
195        return p;
196    }
197
198    @Override
199    public boolean isEnabled(int p) {
200        return false;
201    }
202
203    @Override
204    public View getView(int position, View view, ViewGroup parent) {
205        // Index in cities list
206        int index = position * mClocksPerRow;
207        if (index < 0 || index >= mCitiesList.length) {
208            return null;
209        }
210
211        if (view == null) {
212            view = mInflater.inflate(R.layout.world_clock_list_item, parent, false);
213        }
214        // The world clock list item can hold two world clocks
215        View rightClock = view.findViewById(R.id.city_right);
216        updateView(view.findViewById(R.id.city_left), (CityObj)mCitiesList[index]);
217        if (rightClock != null) {
218            // rightClock may be null (landscape phone layout has only one clock per row) so only
219            // process it if it exists.
220            if (index + 1 < mCitiesList.length) {
221                rightClock.setVisibility(View.VISIBLE);
222                updateView(rightClock, (CityObj)mCitiesList[index + 1]);
223            } else {
224                // To make sure the spacing is right , make sure that the right clock style is
225                // selected even if the clock is invisible.
226                View dclock = rightClock.findViewById(R.id.digital_clock);
227                View aclock = rightClock.findViewById(R.id.analog_clock);
228                if (mClockStyle.equals("analog")) {
229                    dclock.setVisibility(View.GONE);
230                    aclock.setVisibility(View.INVISIBLE);
231                } else {
232                    dclock.setVisibility(View.INVISIBLE);
233                    aclock.setVisibility(View.GONE);
234                }
235                // If there's only the one item, center it. If there are other items in the list,
236                // keep it side-aligned.
237                if (getCount() == 1) {
238                    rightClock.setVisibility(View.GONE);
239                } else {
240                    rightClock.setVisibility(View.INVISIBLE);
241                }
242            }
243        }
244
245        return view;
246    }
247
248    private void updateView(View clock, CityObj cityObj) {
249        View nameLayout= clock.findViewById(R.id.city_name_layout);
250        TextView name = (TextView)(nameLayout.findViewById(R.id.city_name));
251        TextView dayOfWeek = (TextView)(nameLayout.findViewById(R.id.city_day));
252        TextClock dclock = (TextClock)(clock.findViewById(R.id.digital_clock));
253        AnalogClock aclock = (AnalogClock)(clock.findViewById(R.id.analog_clock));
254        View separator = clock.findViewById(R.id.separator);
255
256        if (mClockStyle.equals("analog")) {
257            dclock.setVisibility(View.GONE);
258            separator.setVisibility(View.GONE);
259            aclock.setVisibility(View.VISIBLE);
260            aclock.setTimeZone(cityObj.mTimeZone);
261            aclock.enableSeconds(false);
262        } else {
263            dclock.setVisibility(View.VISIBLE);
264            separator.setVisibility(View.VISIBLE);
265            aclock.setVisibility(View.GONE);
266            dclock.setTimeZone(cityObj.mTimeZone);
267            Utils.setTimeFormat(dclock,
268                    (int)mContext.getResources().getDimension(R.dimen.label_font_size));
269        }
270        CityObj cityInDb = mCitiesDb.get(cityObj.mCityId);
271        // Home city or city not in DB , use data from the save selected cities list
272        name.setText(Utils.getCityName(cityObj, cityInDb));
273
274        final Calendar now = Calendar.getInstance();
275        now.setTimeZone(TimeZone.getDefault());
276        int myDayOfWeek = now.get(Calendar.DAY_OF_WEEK);
277        // Get timezone from cities DB if available
278        String cityTZ = (cityInDb != null) ? cityInDb.mTimeZone : cityObj.mTimeZone;
279        now.setTimeZone(TimeZone.getTimeZone(cityTZ));
280        int cityDayOfWeek = now.get(Calendar.DAY_OF_WEEK);
281        if (myDayOfWeek != cityDayOfWeek) {
282            dayOfWeek.setText(mContext.getString(R.string.world_day_of_week_label,
283                    now.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault())));
284            dayOfWeek.setVisibility(View.VISIBLE);
285        } else {
286            dayOfWeek.setVisibility(View.GONE);
287        }
288    }
289}
290