1 /* 2 * Copyright (C) 2006 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.settings; 18 19 import android.app.Activity; 20 import android.app.AlarmManager; 21 import android.app.ListFragment; 22 import android.content.Context; 23 import android.content.res.XmlResourceParser; 24 import android.os.Bundle; 25 import android.util.Log; 26 import android.view.Menu; 27 import android.view.MenuInflater; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.widget.ListView; 31 import android.widget.SimpleAdapter; 32 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import java.util.ArrayList; 36 import java.util.Calendar; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.TimeZone; 43 44 /** 45 * The class displaying a list of time zones that match a filter string 46 * such as "Africa", "Europe", etc. Choosing an item from the list will set 47 * the time zone. Pressing Back without choosing from the list will not 48 * result in a change in the time zone setting. 49 */ 50 public class ZonePicker extends ListFragment { 51 private static final String TAG = "ZonePicker"; 52 53 public static interface ZoneSelectionListener { 54 // You can add any argument if you really need it... onZoneSelected(TimeZone tz)55 public void onZoneSelected(TimeZone tz); 56 } 57 58 private static final String KEY_ID = "id"; // value: String 59 private static final String KEY_DISPLAYNAME = "name"; // value: String 60 private static final String KEY_GMT = "gmt"; // value: String 61 private static final String KEY_OFFSET = "offset"; // value: int (Integer) 62 private static final String XMLTAG_TIMEZONE = "timezone"; 63 64 private static final int HOURS_1 = 60 * 60000; 65 66 private static final int MENU_TIMEZONE = Menu.FIRST+1; 67 private static final int MENU_ALPHABETICAL = Menu.FIRST; 68 69 private boolean mSortedByTimezone; 70 71 private SimpleAdapter mTimezoneSortedAdapter; 72 private SimpleAdapter mAlphabeticalAdapter; 73 74 private ZoneSelectionListener mListener; 75 76 /** 77 * Constructs an adapter with TimeZone list. Sorted by TimeZone in default. 78 * 79 * @param sortedByName use Name for sorting the list. 80 */ constructTimezoneAdapter(Context context, boolean sortedByName)81 public static SimpleAdapter constructTimezoneAdapter(Context context, 82 boolean sortedByName) { 83 return constructTimezoneAdapter(context, sortedByName, 84 android.R.layout.simple_list_item_2); 85 } 86 87 /** 88 * Constructs an adapter with TimeZone list. Sorted by TimeZone in default. 89 * 90 * @param sortedByName use Name for sorting the list. 91 */ constructTimezoneAdapter(Context context, boolean sortedByName, int layoutId)92 public static SimpleAdapter constructTimezoneAdapter(Context context, 93 boolean sortedByName, int layoutId) { 94 final String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT}; 95 final int[] to = new int[] {android.R.id.text1, android.R.id.text2}; 96 97 final String sortKey = (sortedByName ? KEY_DISPLAYNAME : KEY_OFFSET); 98 final MyComparator comparator = new MyComparator(sortKey); 99 final List<HashMap<String, Object>> sortedList = getZones(context); 100 Collections.sort(sortedList, comparator); 101 final SimpleAdapter adapter = new SimpleAdapter(context, 102 sortedList, 103 layoutId, 104 from, 105 to); 106 107 return adapter; 108 } 109 110 /** 111 * Searches {@link TimeZone} from the given {@link SimpleAdapter} object, and returns 112 * the index for the TimeZone. 113 * 114 * @param adapter SimpleAdapter constructed by 115 * {@link #constructTimezoneAdapter(Context, boolean)}. 116 * @param tz TimeZone to be searched. 117 * @return Index for the given TimeZone. -1 when there's no corresponding list item. 118 * returned. 119 */ getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz)120 public static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { 121 final String defaultId = tz.getID(); 122 final int listSize = adapter.getCount(); 123 for (int i = 0; i < listSize; i++) { 124 // Using HashMap<String, Object> induces unnecessary warning. 125 final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i); 126 final String id = (String)map.get(KEY_ID); 127 if (defaultId.equals(id)) { 128 // If current timezone is in this list, move focus to it 129 return i; 130 } 131 } 132 return -1; 133 } 134 135 /** 136 * @param item one of items in adapters. The adapter should be constructed by 137 * {@link #constructTimezoneAdapter(Context, boolean)}. 138 * @return TimeZone object corresponding to the item. 139 */ obtainTimeZoneFromItem(Object item)140 public static TimeZone obtainTimeZoneFromItem(Object item) { 141 return TimeZone.getTimeZone((String)((Map<?, ?>)item).get(KEY_ID)); 142 } 143 144 @Override onActivityCreated(Bundle savedInstanseState)145 public void onActivityCreated(Bundle savedInstanseState) { 146 super.onActivityCreated(savedInstanseState); 147 148 final Activity activity = getActivity(); 149 mTimezoneSortedAdapter = constructTimezoneAdapter(activity, false); 150 mAlphabeticalAdapter = constructTimezoneAdapter(activity, true); 151 152 // Sets the adapter 153 setSorting(true); 154 setHasOptionsMenu(true); 155 } 156 157 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)158 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 159 menu.add(0, MENU_ALPHABETICAL, 0, R.string.zone_list_menu_sort_alphabetically) 160 .setIcon(android.R.drawable.ic_menu_sort_alphabetically); 161 menu.add(0, MENU_TIMEZONE, 0, R.string.zone_list_menu_sort_by_timezone) 162 .setIcon(R.drawable.ic_menu_3d_globe); 163 super.onCreateOptionsMenu(menu, inflater); 164 } 165 166 @Override onPrepareOptionsMenu(Menu menu)167 public void onPrepareOptionsMenu(Menu menu) { 168 if (mSortedByTimezone) { 169 menu.findItem(MENU_TIMEZONE).setVisible(false); 170 menu.findItem(MENU_ALPHABETICAL).setVisible(true); 171 } else { 172 menu.findItem(MENU_TIMEZONE).setVisible(true); 173 menu.findItem(MENU_ALPHABETICAL).setVisible(false); 174 } 175 } 176 177 @Override onOptionsItemSelected(MenuItem item)178 public boolean onOptionsItemSelected(MenuItem item) { 179 switch (item.getItemId()) { 180 181 case MENU_TIMEZONE: 182 setSorting(true); 183 return true; 184 185 case MENU_ALPHABETICAL: 186 setSorting(false); 187 return true; 188 189 default: 190 return false; 191 } 192 } 193 setZoneSelectionListener(ZoneSelectionListener listener)194 public void setZoneSelectionListener(ZoneSelectionListener listener) { 195 mListener = listener; 196 } 197 setSorting(boolean sortByTimezone)198 private void setSorting(boolean sortByTimezone) { 199 final SimpleAdapter adapter = 200 sortByTimezone ? mTimezoneSortedAdapter : mAlphabeticalAdapter; 201 setListAdapter(adapter); 202 mSortedByTimezone = sortByTimezone; 203 final int defaultIndex = getTimeZoneIndex(adapter, TimeZone.getDefault()); 204 if (defaultIndex >= 0) { 205 setSelection(defaultIndex); 206 } 207 } 208 getZones(Context context)209 private static List<HashMap<String, Object>> getZones(Context context) { 210 final List<HashMap<String, Object>> myData = new ArrayList<HashMap<String, Object>>(); 211 final long date = Calendar.getInstance().getTimeInMillis(); 212 try { 213 XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones); 214 while (xrp.next() != XmlResourceParser.START_TAG) 215 continue; 216 xrp.next(); 217 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 218 while (xrp.getEventType() != XmlResourceParser.START_TAG) { 219 if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { 220 return myData; 221 } 222 xrp.next(); 223 } 224 if (xrp.getName().equals(XMLTAG_TIMEZONE)) { 225 String id = xrp.getAttributeValue(0); 226 String displayName = xrp.nextText(); 227 addItem(myData, id, displayName, date); 228 } 229 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 230 xrp.next(); 231 } 232 xrp.next(); 233 } 234 xrp.close(); 235 } catch (XmlPullParserException xppe) { 236 Log.e(TAG, "Ill-formatted timezones.xml file"); 237 } catch (java.io.IOException ioe) { 238 Log.e(TAG, "Unable to read timezones.xml file"); 239 } 240 241 return myData; 242 } 243 addItem( List<HashMap<String, Object>> myData, String id, String displayName, long date)244 private static void addItem( 245 List<HashMap<String, Object>> myData, String id, String displayName, long date) { 246 final HashMap<String, Object> map = new HashMap<String, Object>(); 247 map.put(KEY_ID, id); 248 map.put(KEY_DISPLAYNAME, displayName); 249 final TimeZone tz = TimeZone.getTimeZone(id); 250 final int offset = tz.getOffset(date); 251 final int p = Math.abs(offset); 252 final StringBuilder name = new StringBuilder(); 253 name.append("GMT"); 254 255 if (offset < 0) { 256 name.append('-'); 257 } else { 258 name.append('+'); 259 } 260 261 name.append(p / (HOURS_1)); 262 name.append(':'); 263 264 int min = p / 60000; 265 min %= 60; 266 267 if (min < 10) { 268 name.append('0'); 269 } 270 name.append(min); 271 272 map.put(KEY_GMT, name.toString()); 273 map.put(KEY_OFFSET, offset); 274 275 myData.add(map); 276 } 277 278 @Override onListItemClick(ListView listView, View v, int position, long id)279 public void onListItemClick(ListView listView, View v, int position, long id) { 280 final Map<?, ?> map = (Map<?, ?>)listView.getItemAtPosition(position); 281 final String tzId = (String) map.get(KEY_ID); 282 283 // Update the system timezone value 284 final Activity activity = getActivity(); 285 final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE); 286 alarm.setTimeZone(tzId); 287 final TimeZone tz = TimeZone.getTimeZone(tzId); 288 if (mListener != null) { 289 mListener.onZoneSelected(tz); 290 } else { 291 getActivity().onBackPressed(); 292 } 293 } 294 295 private static class MyComparator implements Comparator<HashMap<?, ?>> { 296 private String mSortingKey; 297 MyComparator(String sortingKey)298 public MyComparator(String sortingKey) { 299 mSortingKey = sortingKey; 300 } 301 setSortingKey(String sortingKey)302 public void setSortingKey(String sortingKey) { 303 mSortingKey = sortingKey; 304 } 305 compare(HashMap<?, ?> map1, HashMap<?, ?> map2)306 public int compare(HashMap<?, ?> map1, HashMap<?, ?> map2) { 307 Object value1 = map1.get(mSortingKey); 308 Object value2 = map2.get(mSortingKey); 309 310 /* 311 * This should never happen, but just in-case, put non-comparable 312 * items at the end. 313 */ 314 if (!isComparable(value1)) { 315 return isComparable(value2) ? 1 : 0; 316 } else if (!isComparable(value2)) { 317 return -1; 318 } 319 320 return ((Comparable) value1).compareTo(value2); 321 } 322 isComparable(Object value)323 private boolean isComparable(Object value) { 324 return (value != null) && (value instanceof Comparable); 325 } 326 } 327 } 328