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.AlarmManager; 20 import android.app.ListActivity; 21 import android.content.Context; 22 import android.content.res.XmlResourceParser; 23 import android.os.Bundle; 24 import android.text.TextUtils; 25 import android.util.Log; 26 import android.view.Menu; 27 import android.view.MenuItem; 28 import android.view.View; 29 import android.widget.ListAdapter; 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.Arrays; 37 import java.util.Calendar; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.TimeZone; 44 45 /** 46 * This activity displays a list of time zones that match a filter string 47 * such as "Africa", "Europe", etc. Choosing an item from the list will set 48 * the time zone. Pressing Back without choosing from the list will not 49 * result in a change in the time zone setting. 50 */ 51 public class ZoneList extends ListActivity { 52 53 private static final String TAG = "ZoneList"; 54 private static final String KEY_ID = "id"; 55 private static final String KEY_DISPLAYNAME = "name"; 56 private static final String KEY_GMT = "gmt"; 57 private static final String KEY_OFFSET = "offset"; 58 private static final String XMLTAG_TIMEZONE = "timezone"; 59 60 private static final int HOURS_1 = 60 * 60000; 61 private static final int HOURS_24 = 24 * HOURS_1; 62 private static final int HOURS_HALF = HOURS_1 / 2; 63 64 private static final int MENU_TIMEZONE = Menu.FIRST+1; 65 private static final int MENU_ALPHABETICAL = Menu.FIRST; 66 67 // Initial focus position 68 private int mDefault; 69 70 private boolean mSortedByTimezone; 71 72 private SimpleAdapter mTimezoneSortedAdapter; 73 private SimpleAdapter mAlphabeticalAdapter; 74 75 @Override 76 public void onCreate(Bundle icicle) { 77 super.onCreate(icicle); 78 79 String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT}; 80 int[] to = new int[] {android.R.id.text1, android.R.id.text2}; 81 82 MyComparator comparator = new MyComparator(KEY_OFFSET); 83 84 List<HashMap> timezoneSortedList = getZones(); 85 Collections.sort(timezoneSortedList, comparator); 86 mTimezoneSortedAdapter = new SimpleAdapter(this, 87 (List) timezoneSortedList, 88 android.R.layout.simple_list_item_2, 89 from, 90 to); 91 92 List<HashMap> alphabeticalList = new ArrayList<HashMap>(timezoneSortedList); 93 comparator.setSortingKey(KEY_DISPLAYNAME); 94 Collections.sort(alphabeticalList, comparator); 95 mAlphabeticalAdapter = new SimpleAdapter(this, 96 (List) alphabeticalList, 97 android.R.layout.simple_list_item_2, 98 from, 99 to); 100 101 // Sets the adapter 102 setSorting(true); 103 104 // If current timezone is in this list, move focus to it 105 setSelection(mDefault); 106 107 // Assume user may press Back 108 setResult(RESULT_CANCELED); 109 } 110 111 @Override 112 public boolean onCreateOptionsMenu(Menu menu) { 113 menu.add(0, MENU_ALPHABETICAL, 0, R.string.zone_list_menu_sort_alphabetically) 114 .setIcon(android.R.drawable.ic_menu_sort_alphabetically); 115 menu.add(0, MENU_TIMEZONE, 0, R.string.zone_list_menu_sort_by_timezone) 116 .setIcon(R.drawable.ic_menu_3d_globe); 117 118 return true; 119 } 120 121 @Override 122 public boolean onPrepareOptionsMenu(Menu menu) { 123 124 if (mSortedByTimezone) { 125 menu.findItem(MENU_TIMEZONE).setVisible(false); 126 menu.findItem(MENU_ALPHABETICAL).setVisible(true); 127 } else { 128 menu.findItem(MENU_TIMEZONE).setVisible(true); 129 menu.findItem(MENU_ALPHABETICAL).setVisible(false); 130 } 131 132 return true; 133 } 134 135 @Override 136 public boolean onOptionsItemSelected(MenuItem item) { 137 switch (item.getItemId()) { 138 139 case MENU_TIMEZONE: 140 setSorting(true); 141 return true; 142 143 case MENU_ALPHABETICAL: 144 setSorting(false); 145 return true; 146 147 default: 148 return false; 149 } 150 } 151 152 private void setSorting(boolean timezone) { 153 setListAdapter(timezone ? mTimezoneSortedAdapter : mAlphabeticalAdapter); 154 mSortedByTimezone = timezone; 155 } 156 157 private List<HashMap> getZones() { 158 List<HashMap> myData = new ArrayList<HashMap>(); 159 long date = Calendar.getInstance().getTimeInMillis(); 160 try { 161 XmlResourceParser xrp = getResources().getXml(R.xml.timezones); 162 while (xrp.next() != XmlResourceParser.START_TAG) 163 continue; 164 xrp.next(); 165 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 166 while (xrp.getEventType() != XmlResourceParser.START_TAG) { 167 if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { 168 return myData; 169 } 170 xrp.next(); 171 } 172 if (xrp.getName().equals(XMLTAG_TIMEZONE)) { 173 String id = xrp.getAttributeValue(0); 174 String displayName = xrp.nextText(); 175 addItem(myData, id, displayName, date); 176 } 177 while (xrp.getEventType() != XmlResourceParser.END_TAG) { 178 xrp.next(); 179 } 180 xrp.next(); 181 } 182 xrp.close(); 183 } catch (XmlPullParserException xppe) { 184 Log.e(TAG, "Ill-formatted timezones.xml file"); 185 } catch (java.io.IOException ioe) { 186 Log.e(TAG, "Unable to read timezones.xml file"); 187 } 188 189 return myData; 190 } 191 192 protected void addItem(List<HashMap> myData, String id, String displayName, 193 long date) { 194 HashMap map = new HashMap(); 195 map.put(KEY_ID, id); 196 map.put(KEY_DISPLAYNAME, displayName); 197 TimeZone tz = TimeZone.getTimeZone(id); 198 int offset = tz.getOffset(date); 199 int p = Math.abs(offset); 200 StringBuilder name = new StringBuilder(); 201 name.append("GMT"); 202 203 if (offset < 0) { 204 name.append('-'); 205 } else { 206 name.append('+'); 207 } 208 209 name.append(p / (HOURS_1)); 210 name.append(':'); 211 212 int min = p / 60000; 213 min %= 60; 214 215 if (min < 10) { 216 name.append('0'); 217 } 218 name.append(min); 219 220 map.put(KEY_GMT, name.toString()); 221 map.put(KEY_OFFSET, offset); 222 223 if (id.equals(TimeZone.getDefault().getID())) { 224 mDefault = myData.size(); 225 } 226 227 myData.add(map); 228 } 229 230 @Override 231 protected void onListItemClick(ListView l, View v, int position, long id) { 232 Map map = (Map) l.getItemAtPosition(position); 233 // Update the system timezone value 234 AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 235 alarm.setTimeZone((String) map.get(KEY_ID)); 236 setResult(RESULT_OK); 237 finish(); 238 } 239 240 private static class MyComparator implements Comparator<HashMap> { 241 private String mSortingKey; 242 243 public MyComparator(String sortingKey) { 244 mSortingKey = sortingKey; 245 } 246 247 public void setSortingKey(String sortingKey) { 248 mSortingKey = sortingKey; 249 } 250 251 public int compare(HashMap map1, HashMap map2) { 252 Object value1 = map1.get(mSortingKey); 253 Object value2 = map2.get(mSortingKey); 254 255 /* 256 * This should never happen, but just in-case, put non-comparable 257 * items at the end. 258 */ 259 if (!isComparable(value1)) { 260 return isComparable(value2) ? 1 : 0; 261 } else if (!isComparable(value2)) { 262 return -1; 263 } 264 265 return ((Comparable) value1).compareTo(value2); 266 } 267 268 private boolean isComparable(Object value) { 269 return (value != null) && (value instanceof Comparable); 270 } 271 } 272 273 } 274