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
17package com.android.timezonepicker;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.text.TextUtils;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.AdapterView;
27import android.widget.AdapterView.OnItemClickListener;
28import android.widget.BaseAdapter;
29import android.widget.TextView;
30
31import com.android.timezonepicker.TimeZoneFilterTypeAdapter.OnSetFilterListener;
32import com.android.timezonepicker.TimeZonePickerView.OnTimeZoneSetListener;
33
34import java.util.ArrayList;
35import java.util.Iterator;
36import java.util.LinkedHashSet;
37
38public 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