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.annotation.NonNull; 20 import android.app.Activity; 21 import android.app.AlarmManager; 22 import android.app.ListFragment; 23 import android.content.Context; 24 import android.os.Bundle; 25 import android.view.LayoutInflater; 26 import android.view.Menu; 27 import android.view.MenuInflater; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.widget.ListView; 32 import android.widget.SimpleAdapter; 33 34 import com.android.settingslib.datetime.ZoneGetter; 35 36 import java.util.Collections; 37 import java.util.Comparator; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.TimeZone; 42 43 /** 44 * The class displaying a list of time zones that match a filter string 45 * such as "Africa", "Europe", etc. Choosing an item from the list will set 46 * the time zone. Pressing Back without choosing from the list will not 47 * result in a change in the time zone setting. 48 */ 49 public class ZonePicker extends ListFragment { 50 private static final String TAG = "ZonePicker"; 51 52 public interface ZoneSelectionListener { 53 // You can add any argument if you really need it... onZoneSelected(TimeZone tz)54 void onZoneSelected(TimeZone tz); 55 } 56 57 private static final int MENU_TIMEZONE = Menu.FIRST+1; 58 private static final int MENU_ALPHABETICAL = Menu.FIRST; 59 60 private boolean mSortedByTimezone; 61 62 private SimpleAdapter mTimezoneSortedAdapter; 63 private SimpleAdapter mAlphabeticalAdapter; 64 65 private ZoneSelectionListener mListener; 66 67 /** 68 * Constructs an adapter with TimeZone list. Sorted by TimeZone in default. 69 * 70 * @param sortedByName use Name for sorting the list. 71 */ constructTimezoneAdapter(Context context, boolean sortedByName)72 public static SimpleAdapter constructTimezoneAdapter(Context context, 73 boolean sortedByName) { 74 return constructTimezoneAdapter(context, sortedByName, 75 R.layout.date_time_setup_custom_list_item_2); 76 } 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, int layoutId)83 public static SimpleAdapter constructTimezoneAdapter(Context context, 84 boolean sortedByName, int layoutId) { 85 final String[] from = new String[] {ZoneGetter.KEY_DISPLAYNAME, ZoneGetter.KEY_GMT}; 86 final int[] to = new int[] {android.R.id.text1, android.R.id.text2}; 87 88 final String sortKey = (sortedByName ? ZoneGetter.KEY_DISPLAYNAME : ZoneGetter.KEY_OFFSET); 89 final MyComparator comparator = new MyComparator(sortKey); 90 final List<Map<String, Object>> sortedList = ZoneGetter.getZonesList(context); 91 Collections.sort(sortedList, comparator); 92 final SimpleAdapter adapter = new SimpleAdapter(context, 93 sortedList, 94 layoutId, 95 from, 96 to); 97 98 return adapter; 99 } 100 101 /** 102 * Searches {@link TimeZone} from the given {@link SimpleAdapter} object, and returns 103 * the index for the TimeZone. 104 * 105 * @param adapter SimpleAdapter constructed by 106 * {@link #constructTimezoneAdapter(Context, boolean)}. 107 * @param tz TimeZone to be searched. 108 * @return Index for the given TimeZone. -1 when there's no corresponding list item. 109 * returned. 110 */ getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz)111 public static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { 112 final String defaultId = tz.getID(); 113 final int listSize = adapter.getCount(); 114 for (int i = 0; i < listSize; i++) { 115 // Using HashMap<String, Object> induces unnecessary warning. 116 final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i); 117 final String id = (String)map.get(ZoneGetter.KEY_ID); 118 if (defaultId.equals(id)) { 119 // If current timezone is in this list, move focus to it 120 return i; 121 } 122 } 123 return -1; 124 } 125 126 /** 127 * @param item one of items in adapters. The adapter should be constructed by 128 * {@link #constructTimezoneAdapter(Context, boolean)}. 129 * @return TimeZone object corresponding to the item. 130 */ obtainTimeZoneFromItem(Object item)131 public static TimeZone obtainTimeZoneFromItem(Object item) { 132 return TimeZone.getTimeZone((String)((Map<?, ?>)item).get(ZoneGetter.KEY_ID)); 133 } 134 135 @Override onActivityCreated(Bundle savedInstanceState)136 public void onActivityCreated(Bundle savedInstanceState) { 137 super.onActivityCreated(savedInstanceState); 138 139 final Activity activity = getActivity(); 140 mTimezoneSortedAdapter = constructTimezoneAdapter(activity, false); 141 mAlphabeticalAdapter = constructTimezoneAdapter(activity, true); 142 143 // Sets the adapter 144 setSorting(true); 145 setHasOptionsMenu(true); 146 } 147 148 @Override onCreateView(@onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)149 public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 150 Bundle savedInstanceState) { 151 final View view = super.onCreateView(inflater, container, savedInstanceState); 152 final ListView list = (ListView) view.findViewById(android.R.id.list); 153 Utils.forcePrepareCustomPreferencesList(container, view, list, false); 154 return view; 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 209 @Override onListItemClick(ListView listView, View v, int position, long id)210 public void onListItemClick(ListView listView, View v, int position, long id) { 211 // Ignore extra clicks 212 if (!isResumed()) return; 213 final Map<?, ?> map = (Map<?, ?>)listView.getItemAtPosition(position); 214 final String tzId = (String) map.get(ZoneGetter.KEY_ID); 215 216 // Update the system timezone value 217 final Activity activity = getActivity(); 218 final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE); 219 alarm.setTimeZone(tzId); 220 final TimeZone tz = TimeZone.getTimeZone(tzId); 221 if (mListener != null) { 222 mListener.onZoneSelected(tz); 223 } else { 224 getActivity().onBackPressed(); 225 } 226 } 227 228 private static class MyComparator implements Comparator<Map<?, ?>> { 229 private String mSortingKey; 230 MyComparator(String sortingKey)231 public MyComparator(String sortingKey) { 232 mSortingKey = sortingKey; 233 } 234 setSortingKey(String sortingKey)235 public void setSortingKey(String sortingKey) { 236 mSortingKey = sortingKey; 237 } 238 compare(Map<?, ?> map1, Map<?, ?> map2)239 public int compare(Map<?, ?> map1, Map<?, ?> map2) { 240 Object value1 = map1.get(mSortingKey); 241 Object value2 = map2.get(mSortingKey); 242 243 /* 244 * This should never happen, but just in-case, put non-comparable 245 * items at the end. 246 */ 247 if (!isComparable(value1)) { 248 return isComparable(value2) ? 1 : 0; 249 } else if (!isComparable(value2)) { 250 return -1; 251 } 252 253 return ((Comparable) value1).compareTo(value2); 254 } 255 isComparable(Object value)256 private boolean isComparable(Object value) { 257 return (value != null) && (value instanceof Comparable); 258 } 259 } 260 } 261