1 /* 2 * Copyright (C) 2013 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.timezonepicker; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.text.TextUtils; 22 import android.util.Log; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.AdapterView; 27 import android.widget.AdapterView.OnItemClickListener; 28 import android.widget.BaseAdapter; 29 import android.widget.TextView; 30 31 import com.android.timezonepicker.TimeZoneFilterTypeAdapter.OnSetFilterListener; 32 import com.android.timezonepicker.TimeZonePickerView.OnTimeZoneSetListener; 33 34 import java.util.ArrayList; 35 import java.util.Iterator; 36 import java.util.LinkedHashSet; 37 38 public class TimeZoneResultAdapter extends BaseAdapter implements OnItemClickListener, 39 OnSetFilterListener { 40 private static final String TAG = "TimeZoneResultAdapter"; 41 private static final boolean DEBUG = false; 42 private static final int VIEW_TAG_TIME_ZONE = R.id.time_zone; 43 private static final int EMPTY_INDEX = -100; 44 45 /** SharedPref name and key for recent time zones */ 46 private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences"; 47 private static final String KEY_RECENT_TIMEZONES = "preferences_recent_timezones"; 48 49 private int mLastFilterType; 50 private String mLastFilterString; 51 private int mLastFilterTime; 52 53 private boolean mHasResults = false; 54 55 /** 56 * The delimiter we use when serializing recent timezones to shared 57 * preferences 58 */ 59 private static final String RECENT_TIMEZONES_DELIMITER = ","; 60 61 /** The maximum number of recent timezones to save */ 62 private static final int MAX_RECENT_TIMEZONES = 3; 63 64 static class ViewHolder { 65 TextView timeZone; 66 TextView timeOffset; 67 TextView location; 68 69 static void setupViewHolder(View v) { 70 ViewHolder vh = new ViewHolder(); 71 vh.timeZone = (TextView) v.findViewById(R.id.time_zone); 72 vh.timeOffset = (TextView) v.findViewById(R.id.time_offset); 73 vh.location = (TextView) v.findViewById(R.id.location); 74 v.setTag(vh); 75 } 76 } 77 78 private Context mContext; 79 private LayoutInflater mInflater; 80 81 private OnTimeZoneSetListener mTimeZoneSetListener; 82 private TimeZoneData mTimeZoneData; 83 84 private int[] mFilteredTimeZoneIndices; 85 private int mFilteredTimeZoneLength = 0; 86 87 public TimeZoneResultAdapter(Context context, TimeZoneData tzd, 88 com.android.timezonepicker.TimeZonePickerView.OnTimeZoneSetListener l) { 89 super(); 90 91 mContext = context; 92 mTimeZoneData = tzd; 93 mTimeZoneSetListener = l; 94 95 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 96 97 mFilteredTimeZoneIndices = new int[mTimeZoneData.size()]; 98 99 onSetFilter(TimeZoneFilterTypeAdapter.FILTER_TYPE_NONE, null, 0); 100 } 101 102 public boolean hasResults() { 103 return mHasResults; 104 } 105 106 public int getLastFilterType() { 107 return mLastFilterType; 108 } 109 110 public String getLastFilterString() { 111 return mLastFilterString; 112 } 113 114 public int getLastFilterTime() { 115 return mLastFilterTime; 116 } 117 118 // Implements OnSetFilterListener 119 @Override 120 public void onSetFilter(int filterType, String str, int time) { 121 if (DEBUG) { 122 Log.d(TAG, "onSetFilter: " + filterType + " [" + str + "] " + time); 123 } 124 125 mLastFilterType = filterType; 126 mLastFilterString = str; 127 mLastFilterTime = time; 128 129 mFilteredTimeZoneLength = 0; 130 int idx = 0; 131 132 switch (filterType) { 133 case TimeZoneFilterTypeAdapter.FILTER_TYPE_EMPTY: 134 mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = EMPTY_INDEX; 135 break; 136 case TimeZoneFilterTypeAdapter.FILTER_TYPE_NONE: 137 // Show the default/current value first 138 int defaultTzIndex = mTimeZoneData.getDefaultTimeZoneIndex(); 139 if (defaultTzIndex != -1) { 140 mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = defaultTzIndex; 141 } 142 143 // Show the recent selections 144 SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFS_NAME, 145 Context.MODE_PRIVATE); 146 String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null); 147 if (!TextUtils.isEmpty(recentsString)) { 148 String[] recents = recentsString.split(RECENT_TIMEZONES_DELIMITER); 149 for (int i = recents.length - 1; i >= 0; i--) { 150 if (!TextUtils.isEmpty(recents[i]) 151 && !recents[i].equals(mTimeZoneData.mDefaultTimeZoneId)) { 152 int index = mTimeZoneData.findIndexByTimeZoneIdSlow(recents[i]); 153 if (index != -1) { 154 mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = index; 155 } 156 } 157 } 158 } 159 160 break; 161 case TimeZoneFilterTypeAdapter.FILTER_TYPE_GMT: 162 ArrayList<Integer> indices = mTimeZoneData.getTimeZonesByOffset(time); 163 if (indices != null) { 164 for (Integer i : indices) { 165 mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = i; 166 } 167 } 168 break; 169 case TimeZoneFilterTypeAdapter.FILTER_TYPE_COUNTRY: 170 ArrayList<Integer> tzIds = mTimeZoneData.mTimeZonesByCountry.get(str); 171 if (tzIds != null) { 172 for (Integer tzi : tzIds) { 173 mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = tzi; 174 } 175 } 176 break; 177 case TimeZoneFilterTypeAdapter.FILTER_TYPE_STATE: 178 // TODO Filter by state 179 break; 180 default: 181 throw new IllegalArgumentException(); 182 } 183 mHasResults = mFilteredTimeZoneLength > 0; 184 185 notifyDataSetChanged(); 186 } 187 188 /** 189 * Saves the given timezone ID as a recent timezone under shared 190 * preferences. If there are already the maximum number of recent timezones 191 * saved, it will remove the oldest and append this one. 192 * 193 * @param id the ID of the timezone to save 194 * @see {@link #MAX_RECENT_TIMEZONES} 195 */ 196 public void saveRecentTimezone(String id) { 197 SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFS_NAME, 198 Context.MODE_PRIVATE); 199 String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null); 200 if (recentsString == null) { 201 recentsString = id; 202 } else { 203 // De-dup 204 LinkedHashSet<String> recents = new LinkedHashSet<String>(); 205 for(String tzId : recentsString.split(RECENT_TIMEZONES_DELIMITER)) { 206 if (!recents.contains(tzId) && !id.equals(tzId)) { 207 recents.add(tzId); 208 } 209 } 210 211 Iterator<String> it = recents.iterator(); 212 while (recents.size() >= MAX_RECENT_TIMEZONES) { 213 if (!it.hasNext()) { 214 break; 215 } 216 it.next(); 217 it.remove(); 218 } 219 recents.add(id); 220 221 StringBuilder builder = new StringBuilder(); 222 boolean first = true; 223 for (String recent : recents) { 224 if (first) { 225 first = false; 226 } else { 227 builder.append(RECENT_TIMEZONES_DELIMITER); 228 } 229 builder.append(recent); 230 } 231 recentsString = builder.toString(); 232 } 233 234 prefs.edit().putString(KEY_RECENT_TIMEZONES, recentsString).apply(); 235 } 236 237 @Override 238 public int getCount() { 239 return mFilteredTimeZoneLength; 240 } 241 242 @Override 243 public Object getItem(int position) { 244 if (position < 0 || position >= mFilteredTimeZoneLength) { 245 return null; 246 } 247 248 return mTimeZoneData.get(mFilteredTimeZoneIndices[position]); 249 } 250 251 @Override 252 public boolean areAllItemsEnabled() { 253 return false; 254 } 255 256 @Override 257 public boolean isEnabled(int position) { 258 return mFilteredTimeZoneIndices[position] >= 0; 259 } 260 261 @Override 262 public long getItemId(int position) { 263 return mFilteredTimeZoneIndices[position]; 264 } 265 266 @Override 267 public View getView(int position, View convertView, ViewGroup parent) { 268 View v = convertView; 269 270 if (mFilteredTimeZoneIndices[position] == EMPTY_INDEX) { 271 v = mInflater.inflate(R.layout.empty_time_zone_item, null); 272 return v; 273 } 274 275 // We'll need to re-inflate the view if it was null, or if it was used as an empty item. 276 if (v == null || v.findViewById(R.id.empty_item) != null) { 277 v = mInflater.inflate(R.layout.time_zone_item, null); 278 ViewHolder.setupViewHolder(v); 279 } 280 281 ViewHolder vh = (ViewHolder) v.getTag(); 282 283 TimeZoneInfo tzi = mTimeZoneData.get(mFilteredTimeZoneIndices[position]); 284 v.setTag(VIEW_TAG_TIME_ZONE, tzi); 285 286 vh.timeZone.setText(tzi.mDisplayName); 287 288 vh.timeOffset.setText(tzi.getGmtDisplayName(mContext)); 289 290 String location = tzi.mCountry; 291 if (location == null) { 292 vh.location.setVisibility(View.INVISIBLE); 293 } else { 294 vh.location.setText(location); 295 vh.location.setVisibility(View.VISIBLE); 296 } 297 298 return v; 299 } 300 301 @Override 302 public boolean hasStableIds() { 303 return true; 304 } 305 306 // Implements OnItemClickListener 307 @Override 308 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 309 if (mTimeZoneSetListener != null) { 310 TimeZoneInfo tzi = (TimeZoneInfo) v.getTag(VIEW_TAG_TIME_ZONE); 311 if (tzi != null) { 312 mTimeZoneSetListener.onTimeZoneSet(tzi); 313 saveRecentTimezone(tzi.mTzId); 314 } 315 } 316 } 317 } 318