14c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang/*
24c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * Copyright (C) 2010 The Android Open Source Project
34c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang *
44c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * Licensed under the Apache License, Version 2.0 (the "License");
54c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * you may not use this file except in compliance with the License.
64c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * You may obtain a copy of the License at
74c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang *
84c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang *      http://www.apache.org/licenses/LICENSE-2.0
94c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang *
104c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * Unless required by applicable law or agreed to in writing, software
114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * distributed under the License is distributed on an "AS IS" BASIS,
124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * See the License for the specific language governing permissions and
144c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * limitations under the License.
154c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang */
164c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
174c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangpackage com.android.calendar;
184c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
194c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport android.content.Context;
204c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport android.content.SharedPreferences;
214c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport android.content.res.Resources;
2266a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErikimport android.text.TextUtils;
234c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport android.text.format.DateUtils;
24e274e47b7073a350dec4924ac7c09ba4211cb06dErikimport android.util.Log;
254c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport android.widget.ArrayAdapter;
264c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
27559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chanimport com.android.calendar.TimezoneAdapter.TimezoneRow;
28559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
294c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.ArrayList;
304c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.Arrays;
314c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.Collections;
32559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chanimport java.util.Date;
332adb0a5ee7e9ff4984e5e3198bc9f44710158418Erikimport java.util.LinkedHashMap;
344c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.LinkedHashSet;
354c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.List;
36559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chanimport java.util.Locale;
374c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangimport java.util.TimeZone;
384c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
394c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang/**
404c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * {@link TimezoneAdapter} is a custom adapter implementation that allows you to
414c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * easily display a list of timezones for users to choose from. In addition, it
424c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * provides a two-stage behavior that initially only loads a small set of
434c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * timezones (one user-provided, the device timezone, and two recent timezones),
444c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * which can later be expanded into the full list with a call to
454c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * {@link #showAllTimezones()}.
464c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang */
474c8871bf5dee3b3586b375aee98effde31b781a8Mason Tangpublic class TimezoneAdapter extends ArrayAdapter<TimezoneRow> {
48e274e47b7073a350dec4924ac7c09ba4211cb06dErik    private static final String TAG = "TimezoneAdapter";
494c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
504c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
514c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * {@link TimezoneRow} is an immutable class for representing a timezone. We
524c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * don't use {@link TimeZone} directly, in order to provide a reasonable
534c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * implementation of toString() and to control which display names we use.
544c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
55559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    public class TimezoneRow implements Comparable<TimezoneRow> {
564c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
574c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        /** The ID of this timezone, e.g. "America/Los_Angeles" */
584c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        public final String mId;
594c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
604c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        /** The display name of this timezone, e.g. "Pacific Time" */
61559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        private final String mDisplayName;
624c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
634c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        /** The actual offset of this timezone from GMT in milliseconds */
64559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        private final int mOffset;
65559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
66559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        /** Whether the TZ observes daylight saving time */
67559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        private final boolean mUseDaylightTime;
684c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
694c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        /**
704c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang         * A one-line representation of this timezone, including both GMT offset
714c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang         * and display name, e.g. "(GMT-7:00) Pacific Time"
724c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang         */
73559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        private String mGmtDisplayName;
744c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
754c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        public TimezoneRow(String id, String displayName) {
764c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            mId = id;
774c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            mDisplayName = displayName;
784c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            TimeZone tz = TimeZone.getTimeZone(id);
79559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            mUseDaylightTime = tz.useDaylightTime();
80559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            mOffset = tz.getOffset(TimezoneAdapter.this.mTime);
81559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        }
82559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
83559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        @Override
84559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        public String toString() {
85559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            if (mGmtDisplayName == null) {
86559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                buildGmtDisplayName();
87559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            }
88559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
89559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            return mGmtDisplayName;
90559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        }
91559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
92559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        /**
93559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan         *
94559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan         */
95559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        public void buildGmtDisplayName() {
96559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            if (mGmtDisplayName != null) {
97559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                return;
98559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            }
994c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
100559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            int p = Math.abs(mOffset);
1014c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            StringBuilder name = new StringBuilder();
1024c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.append("GMT");
1034c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
104559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            if (mOffset < 0) {
1054c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                name.append('-');
1064c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            } else {
1074c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                name.append('+');
1084c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1094c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1104c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.append(p / (DateUtils.HOUR_IN_MILLIS));
1114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.append(':');
1124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            int min = p / 60000;
1144c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            min %= 60;
1154c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1164c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (min < 10) {
1174c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                name.append('0');
1184c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1194c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.append(min);
1204c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.insert(0, "(");
1214c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            name.append(") ");
122559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            name.append(mDisplayName);
123559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            if (mUseDaylightTime) {
124559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                name.append(" \u2600"); // Sun symbol
125559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            }
1264c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            mGmtDisplayName = name.toString();
1274c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
1284c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1294c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        @Override
1304c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        public int hashCode() {
1314c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            final int prime = 31;
1324c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            int result = 1;
1334c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            result = prime * result + ((mDisplayName == null) ? 0 : mDisplayName.hashCode());
1344c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            result = prime * result + ((mId == null) ? 0 : mId.hashCode());
1354c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            result = prime * result + mOffset;
1364c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            return result;
1374c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
1384c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1394c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        @Override
1404c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        public boolean equals(Object obj) {
1414c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (this == obj) {
1424c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return true;
1434c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1444c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (obj == null) {
1454c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return false;
1464c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1474c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (getClass() != obj.getClass()) {
1484c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return false;
1494c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1504c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            TimezoneRow other = (TimezoneRow) obj;
1514c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (mDisplayName == null) {
1524c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                if (other.mDisplayName != null) {
1534c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                    return false;
1544c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                }
1554c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            } else if (!mDisplayName.equals(other.mDisplayName)) {
1564c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return false;
1574c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1584c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (mId == null) {
1594c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                if (other.mId != null) {
1604c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                    return false;
1614c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                }
1624c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            } else if (!mId.equals(other.mId)) {
1634c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return false;
1644c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1654c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (mOffset != other.mOffset) {
1664c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return false;
1674c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1684c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            return true;
1694c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
1704c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1714c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        @Override
1724c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        public int compareTo(TimezoneRow another) {
1734c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            if (mOffset == another.mOffset) {
1744c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return 0;
1754c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            } else {
1764c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                return mOffset < another.mOffset ? -1 : 1;
1774c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
1784c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
1794c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1804c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
1814c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1824c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private static final String KEY_RECENT_TIMEZONES = "preferences_recent_timezones";
1834c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1844c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /** The delimiter we use when serializing recent timezones to shared preferences */
1854c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private static final String RECENT_TIMEZONES_DELIMITER = ",";
1864c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1874c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /** The maximum number of recent timezones to save */
1884c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private static final int MAX_RECENT_TIMEZONES = 3;
1894c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
1904c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
1914c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Static cache of all known timezones, mapped to their string IDs. This is
192a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * lazily-loaded on the first call to {@link #loadFromResources(Resources)}.
193a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * Loading is called in a synchronized block during initialization of this
194a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * class and is based off the resources available to the calling context.
195a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * This class should not be used outside of the initial context.
196a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * LinkedHashMap is used to preserve ordering.
1974c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
198a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik    private static LinkedHashMap<String, TimezoneRow> sTimezones;
1994c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2004c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private Context mContext;
2014c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2024c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private String mCurrentTimezone;
2034c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2044c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    private boolean mShowingAll = false;
2054c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
206559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    private long mTime;
207559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
208559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    private Date mDateTime;
209559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
2104c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
2114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Constructs a timezone adapter that contains an initial set of entries
2124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * including the current timezone, the device timezone, and two recently
2134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * used timezones.
2144c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     *
2154c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param context
2164c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param currentTimezone
217559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     * @param time - needed to determine whether DLS is in effect
2184c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
219559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    public TimezoneAdapter(Context context, String currentTimezone, long time) {
2204c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        super(context, android.R.layout.simple_spinner_dropdown_item, android.R.id.text1);
2214c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        mContext = context;
2224c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        mCurrentTimezone = currentTimezone;
223559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        mTime = time;
224559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        mDateTime = new Date(mTime);
2254c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        mShowingAll = false;
2264c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        showInitialTimezones();
2274c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
2284c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2294c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
2304c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Given the ID of a timezone, returns the position of the timezone in this
2314c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * adapter, or -1 if not found.
2324c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     *
2334c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param id the ID of the timezone to find
2344c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @return the row position of the timezone, or -1 if not found
2354c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
2364c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    public int getRowById(String id) {
2374c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        TimezoneRow timezone = sTimezones.get(id);
2384c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        if (timezone == null) {
2394c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            return -1;
2404c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        } else {
2414c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            return getPosition(timezone);
2424c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
2434c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
2444c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2454c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
2464c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Populates the adapter with an initial list of timezones (one
2474c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * user-provided, the device timezone, and two recent timezones), which can
2484c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * later be expanded into the full list with a call to
2494c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * {@link #showAllTimezones()}.
2504c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     *
2514c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param currentTimezone
2524c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
2534c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    public void showInitialTimezones() {
2544c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2554c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        // we use a linked hash set to guarantee only unique IDs are added, and
2564c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        // also to maintain the insertion order of the timezones
2574c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        LinkedHashSet<String> ids = new LinkedHashSet<String>();
2584c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2594c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        // add in the provided (event) timezone
26066a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik        if (!TextUtils.isEmpty(mCurrentTimezone)) {
26166a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik            ids.add(mCurrentTimezone);
26266a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik        }
2634c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2644c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        // add in the device timezone if it is different
2654c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        ids.add(TimeZone.getDefault().getID());
2664c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2674c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        // add in recent timezone selections
2684b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mContext);
2694c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
2704c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        if (recentsString != null) {
2714c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            String[] recents = recentsString.split(RECENT_TIMEZONES_DELIMITER);
2724c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            for (String recent : recents) {
27366a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik                if (!TextUtils.isEmpty(recent)) {
27466a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik                    ids.add(recent);
27566a3c9ee58ae73a3a1ad3bdec79aa1566582465cRoboErik                }
2764c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
2774c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
2784c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
2794c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        clear();
280a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik
281a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        synchronized (TimezoneAdapter.class) {
282a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik            loadFromResources(mContext.getResources());
283a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik            TimeZone gmt = TimeZone.getTimeZone("GMT");
284a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik            for (String id : ids) {
285a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                if (!sTimezones.containsKey(id)) {
286a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    // a timezone we don't know about, so try to add it...
287a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    TimeZone newTz = TimeZone.getTimeZone(id);
288a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    // since TimeZone.getTimeZone actually returns a clone of GMT
289a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    // when it doesn't recognize the ID, this appears to be the only
290a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    // reliable way to check to see if the ID is a valid timezone
291a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    if (!newTz.equals(gmt)) {
292559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                        final String tzDisplayName = newTz.getDisplayName(
293559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                                newTz.inDaylightTime(mDateTime), TimeZone.LONG,
294559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                                Locale.getDefault());
295559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                        sTimezones.put(id, new TimezoneRow(id, tzDisplayName));
296a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    } else {
297a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                        continue;
298a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                    }
2994c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                }
300a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik                add(sTimezones.get(id));
3014c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
3024c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
3034c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        mShowingAll = false;
3044c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
3054c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
3064c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
3074c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Populates this adapter with all known timezones.
3084c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
3094c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    public void showAllTimezones() {
3104c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        List<TimezoneRow> timezones = new ArrayList<TimezoneRow>(sTimezones.values());
3114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        Collections.sort(timezones);
3124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        clear();
3134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        for (TimezoneRow timezone : timezones) {
314559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            timezone.buildGmtDisplayName();
3154c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            add(timezone);
3164c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
3174c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        mShowingAll = true;
3184c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
3194c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
3204c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
3214c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Sets the current timezone. If the adapter is currently displaying only a
3224c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * subset of views, reload that view since it may have changed.
3234c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     *
3244c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param currentTimezone the current timezone
3254c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
3264c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    public void setCurrentTimezone(String currentTimezone) {
327559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        if (currentTimezone != null && !currentTimezone.equals(mCurrentTimezone)) {
328559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            mCurrentTimezone = currentTimezone;
329559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            if (!mShowingAll) {
330559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan                showInitialTimezones();
331559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            }
332559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        }
333559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    }
334559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan
335559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    /**
336559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     * Set the time for the adapter and update the display string appropriate
337559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     * for the time of the year e.g. standard time vs daylight time
338559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     *
339559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     * @param time
340559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan     */
341559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan    public void setTime(long time) {
342559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan        if (time != mTime) {
343559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            mTime = time;
344559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            mDateTime.setTime(mTime);
345559cb86bf1aa43c0a9cba7a4c7297d8d6d3a33f8Michael Chan            sTimezones = null;
3464c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            showInitialTimezones();
3474c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
3484c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
3494c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
3504c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    /**
3514c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * Saves the given timezone ID as a recent timezone under shared
3524c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * preferences. If there are already the maximum number of recent timezones
3534c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * saved, it will remove the oldest and append this one.
3544c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     *
3554c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @param id the ID of the timezone to save
3564c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     * @see {@link #MAX_RECENT_TIMEZONES}
3574c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang     */
3584c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    public void saveRecentTimezone(String id) {
3594b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mContext);
3604c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
3614c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        List<String> recents;
3624c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        if (recentsString == null) {
3634c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            recents = new ArrayList<String>(MAX_RECENT_TIMEZONES);
3644c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        } else {
3654c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            recents = new ArrayList<String>(
3664c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang                Arrays.asList(recentsString.split(RECENT_TIMEZONES_DELIMITER)));
3674c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
3684c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
3694c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        while (recents.size() >= MAX_RECENT_TIMEZONES) {
3704c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            recents.remove(0);
3714c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
3724c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        recents.add(id);
3734c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        recentsString = Utils.join(recents, RECENT_TIMEZONES_DELIMITER);
374f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang        Utils.setSharedPreference(mContext, KEY_RECENT_TIMEZONES, recentsString);
3754c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
3764c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang
377a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik    /**
378a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * Returns an array of ids/time zones. This returns a double indexed array
379a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * of ids and time zones for Calendar. It is an inefficient method and
380a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * shouldn't be called often, but can be used for one time generation of
381a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * this list.
382a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     *
383a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     * @return double array of tz ids and tz names
384a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik     */
385a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik    public CharSequence[][] getAllTimezones() {
386a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        CharSequence[][] timeZones = new CharSequence[2][sTimezones.size()];
387a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        List<String> ids = new ArrayList<String>(sTimezones.keySet());
388a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        List<TimezoneRow> timezones = new ArrayList<TimezoneRow>(sTimezones.values());
389a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        int i = 0;
390a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        for (TimezoneRow row : timezones) {
391a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik            timeZones[0][i] = ids.get(i);
392a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik            timeZones[1][i++] = row.toString();
393a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        }
394a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik        return timeZones;
395a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik    }
396a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik
397a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik    private void loadFromResources(Resources resources) {
3984c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        if (sTimezones == null) {
399e274e47b7073a350dec4924ac7c09ba4211cb06dErik            String[] ids = resources.getStringArray(R.array.timezone_values);
400e274e47b7073a350dec4924ac7c09ba4211cb06dErik            String[] labels = resources.getStringArray(R.array.timezone_labels);
401e274e47b7073a350dec4924ac7c09ba4211cb06dErik
402e274e47b7073a350dec4924ac7c09ba4211cb06dErik            int length = ids.length;
403e274e47b7073a350dec4924ac7c09ba4211cb06dErik            sTimezones = new LinkedHashMap<String, TimezoneRow>(length);
404e274e47b7073a350dec4924ac7c09ba4211cb06dErik
405e274e47b7073a350dec4924ac7c09ba4211cb06dErik            if (ids.length != labels.length) {
406e274e47b7073a350dec4924ac7c09ba4211cb06dErik                Log.wtf(TAG, "ids length (" + ids.length + ") and labels length(" + labels.length +
407e274e47b7073a350dec4924ac7c09ba4211cb06dErik                        ") should be equal but aren't.");
408e274e47b7073a350dec4924ac7c09ba4211cb06dErik            }
409e274e47b7073a350dec4924ac7c09ba4211cb06dErik            for (int i = 0; i < length; i++) {
410e274e47b7073a350dec4924ac7c09ba4211cb06dErik                sTimezones.put(ids[i], new TimezoneRow(ids[i], labels[i]));
4114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang            }
4124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang        }
4134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang    }
414d7c7f2ab6fdb55451ead2d54819ba8f37af2d0a7Mason Tang}
415