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 17 package com.android.deskclock.worldclock; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.preference.PreferenceManager; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.BaseAdapter; 26 import android.widget.TextClock; 27 import android.widget.TextView; 28 29 import com.android.deskclock.AnalogClock; 30 import com.android.deskclock.R; 31 import com.android.deskclock.SettingsActivity; 32 import com.android.deskclock.Utils; 33 34 import java.text.Collator; 35 import java.util.Arrays; 36 import java.util.Calendar; 37 import java.util.Comparator; 38 import java.util.Date; 39 import java.util.HashMap; 40 import java.util.Locale; 41 import java.util.TimeZone; 42 43 public 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