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