summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Android.mk27
-rw-r--r--AndroidManifest.xml26
-rw-r--r--assets/backward117
-rw-r--r--assets/zone.tab441
l---------libs/android-support-v4.jar1
-rw-r--r--project.properties15
-rw-r--r--res/layout/time_zone_filter_item.xml49
-rw-r--r--res/layout/time_zone_item.xml56
-rw-r--r--res/layout/timezonepickerview.xml70
-rw-r--r--res/values-v11/styles.xml26
-rw-r--r--res/values-v14/styles.xml27
-rw-r--r--res/values-v16/styles.xml22
-rw-r--r--res/values-v17/styles.xml22
-rw-r--r--res/values/strings.xml35
-rw-r--r--res/values/styles.xml34
-rw-r--r--src/com/android/timezonepicker/TimeZoneData.java442
-rw-r--r--src/com/android/timezonepicker/TimeZoneFilterTypeAdapter.java470
-rw-r--r--src/com/android/timezonepicker/TimeZoneInfo.java352
-rw-r--r--src/com/android/timezonepicker/TimeZonePickerDialog.java85
-rw-r--r--src/com/android/timezonepicker/TimeZonePickerView.java86
-rw-r--r--src/com/android/timezonepicker/TimeZoneResultAdapter.java283
22 files changed, 2688 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7792e06
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+bin/
+gen/
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..a35f0ec
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-opt-timezonepicker
+
+LOCAL_SDK_VERSION := 17
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-logtags-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..c72a839
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.timezonepicker"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ android:targetSdkVersion="17" />
+
+</manifest>
diff --git a/assets/backward b/assets/backward
new file mode 100644
index 0000000..dc7769f
--- /dev/null
+++ b/assets/backward
@@ -0,0 +1,117 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This file provides links between current names for time zones
+# and their old names. Many names changed in late 1993.
+
+Link Africa/Asmara Africa/Asmera
+Link Africa/Bamako Africa/Timbuktu
+Link America/Argentina/Catamarca America/Argentina/ComodRivadavia
+Link America/Adak America/Atka
+Link America/Argentina/Buenos_Aires America/Buenos_Aires
+Link America/Argentina/Catamarca America/Catamarca
+Link America/Atikokan America/Coral_Harbour
+Link America/Argentina/Cordoba America/Cordoba
+Link America/Tijuana America/Ensenada
+Link America/Indiana/Indianapolis America/Fort_Wayne
+Link America/Indiana/Indianapolis America/Indianapolis
+Link America/Argentina/Jujuy America/Jujuy
+Link America/Indiana/Knox America/Knox_IN
+Link America/Kentucky/Louisville America/Louisville
+Link America/Argentina/Mendoza America/Mendoza
+Link America/Rio_Branco America/Porto_Acre
+Link America/Argentina/Cordoba America/Rosario
+Link America/St_Thomas America/Virgin
+Link Asia/Ashgabat Asia/Ashkhabad
+Link Asia/Chongqing Asia/Chungking
+Link Asia/Dhaka Asia/Dacca
+Link Asia/Kathmandu Asia/Katmandu
+Link Asia/Kolkata Asia/Calcutta
+Link Asia/Macau Asia/Macao
+Link Asia/Jerusalem Asia/Tel_Aviv
+Link Asia/Ho_Chi_Minh Asia/Saigon
+Link Asia/Thimphu Asia/Thimbu
+Link Asia/Makassar Asia/Ujung_Pandang
+Link Asia/Ulaanbaatar Asia/Ulan_Bator
+Link Atlantic/Faroe Atlantic/Faeroe
+Link Europe/Oslo Atlantic/Jan_Mayen
+Link Australia/Sydney Australia/ACT
+Link Australia/Sydney Australia/Canberra
+Link Australia/Lord_Howe Australia/LHI
+Link Australia/Sydney Australia/NSW
+Link Australia/Darwin Australia/North
+Link Australia/Brisbane Australia/Queensland
+Link Australia/Adelaide Australia/South
+Link Australia/Hobart Australia/Tasmania
+Link Australia/Melbourne Australia/Victoria
+Link Australia/Perth Australia/West
+Link Australia/Broken_Hill Australia/Yancowinna
+Link America/Rio_Branco Brazil/Acre
+Link America/Noronha Brazil/DeNoronha
+Link America/Sao_Paulo Brazil/East
+Link America/Manaus Brazil/West
+Link America/Halifax Canada/Atlantic
+Link America/Winnipeg Canada/Central
+Link America/Regina Canada/East-Saskatchewan
+Link America/Toronto Canada/Eastern
+Link America/Edmonton Canada/Mountain
+Link America/St_Johns Canada/Newfoundland
+Link America/Vancouver Canada/Pacific
+Link America/Regina Canada/Saskatchewan
+Link America/Whitehorse Canada/Yukon
+Link America/Santiago Chile/Continental
+Link Pacific/Easter Chile/EasterIsland
+Link America/Havana Cuba
+Link Africa/Cairo Egypt
+Link Europe/Dublin Eire
+Link Europe/London Europe/Belfast
+Link Europe/Chisinau Europe/Tiraspol
+Link Europe/London GB
+Link Europe/London GB-Eire
+Link Etc/GMT GMT+0
+Link Etc/GMT GMT-0
+Link Etc/GMT GMT0
+Link Etc/GMT Greenwich
+Link Asia/Hong_Kong Hongkong
+Link Atlantic/Reykjavik Iceland
+Link Asia/Tehran Iran
+Link Asia/Jerusalem Israel
+Link America/Jamaica Jamaica
+Link Asia/Tokyo Japan
+Link Pacific/Kwajalein Kwajalein
+Link Africa/Tripoli Libya
+Link America/Tijuana Mexico/BajaNorte
+Link America/Mazatlan Mexico/BajaSur
+Link America/Mexico_City Mexico/General
+Link Pacific/Auckland NZ
+Link Pacific/Chatham NZ-CHAT
+Link America/Denver Navajo
+Link Asia/Shanghai PRC
+Link Pacific/Pago_Pago Pacific/Samoa
+Link Pacific/Chuuk Pacific/Yap
+Link Pacific/Chuuk Pacific/Truk
+Link Pacific/Pohnpei Pacific/Ponape
+Link Europe/Warsaw Poland
+Link Europe/Lisbon Portugal
+Link Asia/Taipei ROC
+Link Asia/Seoul ROK
+Link Asia/Singapore Singapore
+Link Europe/Istanbul Turkey
+Link Etc/UCT UCT
+Link America/Anchorage US/Alaska
+Link America/Adak US/Aleutian
+Link America/Phoenix US/Arizona
+Link America/Chicago US/Central
+Link America/Indiana/Indianapolis US/East-Indiana
+Link America/New_York US/Eastern
+Link Pacific/Honolulu US/Hawaii
+Link America/Indiana/Knox US/Indiana-Starke
+Link America/Detroit US/Michigan
+Link America/Denver US/Mountain
+Link America/Los_Angeles US/Pacific
+Link Pacific/Pago_Pago US/Samoa
+Link Etc/UTC UTC
+Link Etc/UTC Universal
+Link Europe/Moscow W-SU
+Link Etc/UTC Zulu
diff --git a/assets/zone.tab b/assets/zone.tab
new file mode 100644
index 0000000..6bda826
--- /dev/null
+++ b/assets/zone.tab
@@ -0,0 +1,441 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+#
+# TZ zone descriptions
+#
+# From Paul Eggert (1996-08-05):
+#
+# This file contains a table with the following columns:
+# 1. ISO 3166 2-character country code. See the file `iso3166.tab'.
+# 2. Latitude and longitude of the zone's principal location
+# in ISO 6709 sign-degrees-minutes-seconds format,
+# either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS,
+# first latitude (+ is north), then longitude (+ is east).
+# 3. Zone name used in value of TZ environment variable.
+# 4. Comments; present if and only if the country has multiple rows.
+#
+# Columns are separated by a single tab.
+# The table is sorted first by country, then an order within the country that
+# (1) makes some geographical sense, and
+# (2) puts the most populous zones first, where that does not contradict (1).
+#
+# Lines beginning with `#' are comments.
+#
+#country-
+#code coordinates TZ comments
+AD +4230+00131 Europe/Andorra
+AE +2518+05518 Asia/Dubai
+AF +3431+06912 Asia/Kabul
+AG +1703-06148 America/Antigua
+AI +1812-06304 America/Anguilla
+AL +4120+01950 Europe/Tirane
+AM +4011+04430 Asia/Yerevan
+AO -0848+01314 Africa/Luanda
+AQ -7750+16636 Antarctica/McMurdo McMurdo Station, Ross Island
+AQ -9000+00000 Antarctica/South_Pole Amundsen-Scott Station, South Pole
+AQ -6734-06808 Antarctica/Rothera Rothera Station, Adelaide Island
+AQ -6448-06406 Antarctica/Palmer Palmer Station, Anvers Island
+AQ -6736+06253 Antarctica/Mawson Mawson Station, Holme Bay
+AQ -6835+07758 Antarctica/Davis Davis Station, Vestfold Hills
+AQ -6617+11031 Antarctica/Casey Casey Station, Bailey Peninsula
+AQ -7824+10654 Antarctica/Vostok Vostok Station, Lake Vostok
+AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville Station, Terre Adelie
+AQ -690022+0393524 Antarctica/Syowa Syowa Station, E Ongul I
+AQ -5430+15857 Antarctica/Macquarie Macquarie Island Station, Macquarie Island
+AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF)
+AR -3124-06411 America/Argentina/Cordoba most locations (CB, CC, CN, ER, FM, MN, SE, SF)
+AR -2447-06525 America/Argentina/Salta (SA, LP, NQ, RN)
+AR -2411-06518 America/Argentina/Jujuy Jujuy (JY)
+AR -2649-06513 America/Argentina/Tucuman Tucuman (TM)
+AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH)
+AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR)
+AR -3132-06831 America/Argentina/San_Juan San Juan (SJ)
+AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ)
+AR -3319-06621 America/Argentina/San_Luis San Luis (SL)
+AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC)
+AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF)
+AS -1416-17042 Pacific/Pago_Pago
+AT +4813+01620 Europe/Vienna
+AU -3133+15905 Australia/Lord_Howe Lord Howe Island
+AU -4253+14719 Australia/Hobart Tasmania - most locations
+AU -3956+14352 Australia/Currie Tasmania - King Island
+AU -3749+14458 Australia/Melbourne Victoria
+AU -3352+15113 Australia/Sydney New South Wales - most locations
+AU -3157+14127 Australia/Broken_Hill New South Wales - Yancowinna
+AU -2728+15302 Australia/Brisbane Queensland - most locations
+AU -2016+14900 Australia/Lindeman Queensland - Holiday Islands
+AU -3455+13835 Australia/Adelaide South Australia
+AU -1228+13050 Australia/Darwin Northern Territory
+AU -3157+11551 Australia/Perth Western Australia - most locations
+AU -3143+12852 Australia/Eucla Western Australia - Eucla area
+AW +1230-06958 America/Aruba
+AX +6006+01957 Europe/Mariehamn
+AZ +4023+04951 Asia/Baku
+BA +4352+01825 Europe/Sarajevo
+BB +1306-05937 America/Barbados
+BD +2343+09025 Asia/Dhaka
+BE +5050+00420 Europe/Brussels
+BF +1222-00131 Africa/Ouagadougou
+BG +4241+02319 Europe/Sofia
+BH +2623+05035 Asia/Bahrain
+BI -0323+02922 Africa/Bujumbura
+BJ +0629+00237 Africa/Porto-Novo
+BL +1753-06251 America/St_Barthelemy
+BM +3217-06446 Atlantic/Bermuda
+BN +0456+11455 Asia/Brunei
+BO -1630-06809 America/La_Paz
+BQ +120903-0681636 America/Kralendijk
+BR -0351-03225 America/Noronha Atlantic islands
+BR -0127-04829 America/Belem Amapa, E Para
+BR -0343-03830 America/Fortaleza NE Brazil (MA, PI, CE, RN, PB)
+BR -0803-03454 America/Recife Pernambuco
+BR -0712-04812 America/Araguaina Tocantins
+BR -0940-03543 America/Maceio Alagoas, Sergipe
+BR -1259-03831 America/Bahia Bahia
+BR -2332-04637 America/Sao_Paulo S & SE Brazil (GO, DF, MG, ES, RJ, SP, PR, SC, RS)
+BR -2027-05437 America/Campo_Grande Mato Grosso do Sul
+BR -1535-05605 America/Cuiaba Mato Grosso
+BR -0226-05452 America/Santarem W Para
+BR -0846-06354 America/Porto_Velho Rondonia
+BR +0249-06040 America/Boa_Vista Roraima
+BR -0308-06001 America/Manaus E Amazonas
+BR -0640-06952 America/Eirunepe W Amazonas
+BR -0958-06748 America/Rio_Branco Acre
+BS +2505-07721 America/Nassau
+BT +2728+08939 Asia/Thimphu
+BW -2439+02555 Africa/Gaborone
+BY +5354+02734 Europe/Minsk
+BZ +1730-08812 America/Belize
+CA +4734-05243 America/St_Johns Newfoundland Time, including SE Labrador
+CA +4439-06336 America/Halifax Atlantic Time - Nova Scotia (most places), PEI
+CA +4612-05957 America/Glace_Bay Atlantic Time - Nova Scotia - places that did not observe DST 1966-1971
+CA +4606-06447 America/Moncton Atlantic Time - New Brunswick
+CA +5320-06025 America/Goose_Bay Atlantic Time - Labrador - most locations
+CA +5125-05707 America/Blanc-Sablon Atlantic Standard Time - Quebec - Lower North Shore
+CA +4531-07334 America/Montreal Eastern Time - Quebec - most locations
+CA +4339-07923 America/Toronto Eastern Time - Ontario - most locations
+CA +4901-08816 America/Nipigon Eastern Time - Ontario & Quebec - places that did not observe DST 1967-1973
+CA +4823-08915 America/Thunder_Bay Eastern Time - Thunder Bay, Ontario
+CA +6344-06828 America/Iqaluit Eastern Time - east Nunavut - most locations
+CA +6608-06544 America/Pangnirtung Eastern Time - Pangnirtung, Nunavut
+CA +744144-0944945 America/Resolute Central Standard Time - Resolute, Nunavut
+CA +484531-0913718 America/Atikokan Eastern Standard Time - Atikokan, Ontario and Southampton I, Nunavut
+CA +624900-0920459 America/Rankin_Inlet Central Time - central Nunavut
+CA +4953-09709 America/Winnipeg Central Time - Manitoba & west Ontario
+CA +4843-09434 America/Rainy_River Central Time - Rainy River & Fort Frances, Ontario
+CA +5024-10439 America/Regina Central Standard Time - Saskatchewan - most locations
+CA +5017-10750 America/Swift_Current Central Standard Time - Saskatchewan - midwest
+CA +5333-11328 America/Edmonton Mountain Time - Alberta, east British Columbia & west Saskatchewan
+CA +690650-1050310 America/Cambridge_Bay Mountain Time - west Nunavut
+CA +6227-11421 America/Yellowknife Mountain Time - central Northwest Territories
+CA +682059-1334300 America/Inuvik Mountain Time - west Northwest Territories
+CA +4906-11631 America/Creston Mountain Standard Time - Creston, British Columbia
+CA +5946-12014 America/Dawson_Creek Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia
+CA +4916-12307 America/Vancouver Pacific Time - west British Columbia
+CA +6043-13503 America/Whitehorse Pacific Time - south Yukon
+CA +6404-13925 America/Dawson Pacific Time - north Yukon
+CC -1210+09655 Indian/Cocos
+CD -0418+01518 Africa/Kinshasa west Dem. Rep. of Congo
+CD -1140+02728 Africa/Lubumbashi east Dem. Rep. of Congo
+CF +0422+01835 Africa/Bangui
+CG -0416+01517 Africa/Brazzaville
+CH +4723+00832 Europe/Zurich
+CI +0519-00402 Africa/Abidjan
+CK -2114-15946 Pacific/Rarotonga
+CL -3327-07040 America/Santiago most locations
+CL -2709-10926 Pacific/Easter Easter Island & Sala y Gomez
+CM +0403+00942 Africa/Douala
+CN +3114+12128 Asia/Shanghai east China - Beijing, Guangdong, Shanghai, etc.
+CN +4545+12641 Asia/Harbin Heilongjiang (except Mohe), Jilin
+CN +2934+10635 Asia/Chongqing central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.
+CN +4348+08735 Asia/Urumqi most of Tibet & Xinjiang
+CN +3929+07559 Asia/Kashgar west Tibet & Xinjiang
+CO +0436-07405 America/Bogota
+CR +0956-08405 America/Costa_Rica
+CU +2308-08222 America/Havana
+CV +1455-02331 Atlantic/Cape_Verde
+CW +1211-06900 America/Curacao
+CX -1025+10543 Indian/Christmas
+CY +3510+03322 Asia/Nicosia
+CZ +5005+01426 Europe/Prague
+DE +5230+01322 Europe/Berlin
+DJ +1136+04309 Africa/Djibouti
+DK +5540+01235 Europe/Copenhagen
+DM +1518-06124 America/Dominica
+DO +1828-06954 America/Santo_Domingo
+DZ +3647+00303 Africa/Algiers
+EC -0210-07950 America/Guayaquil mainland
+EC -0054-08936 Pacific/Galapagos Galapagos Islands
+EE +5925+02445 Europe/Tallinn
+EG +3003+03115 Africa/Cairo
+EH +2709-01312 Africa/El_Aaiun
+ER +1520+03853 Africa/Asmara
+ES +4024-00341 Europe/Madrid mainland
+ES +3553-00519 Africa/Ceuta Ceuta & Melilla
+ES +2806-01524 Atlantic/Canary Canary Islands
+ET +0902+03842 Africa/Addis_Ababa
+FI +6010+02458 Europe/Helsinki
+FJ -1808+17825 Pacific/Fiji
+FK -5142-05751 Atlantic/Stanley
+FM +0725+15147 Pacific/Chuuk Chuuk (Truk) and Yap
+FM +0658+15813 Pacific/Pohnpei Pohnpei (Ponape)
+FM +0519+16259 Pacific/Kosrae Kosrae
+FO +6201-00646 Atlantic/Faroe
+FR +4852+00220 Europe/Paris
+GA +0023+00927 Africa/Libreville
+GB +513030-0000731 Europe/London
+GD +1203-06145 America/Grenada
+GE +4143+04449 Asia/Tbilisi
+GF +0456-05220 America/Cayenne
+GG +4927-00232 Europe/Guernsey
+GH +0533-00013 Africa/Accra
+GI +3608-00521 Europe/Gibraltar
+GL +6411-05144 America/Godthab most locations
+GL +7646-01840 America/Danmarkshavn east coast, north of Scoresbysund
+GL +7029-02158 America/Scoresbysund Scoresbysund / Ittoqqortoormiit
+GL +7634-06847 America/Thule Thule / Pituffik
+GM +1328-01639 Africa/Banjul
+GN +0931-01343 Africa/Conakry
+GP +1614-06132 America/Guadeloupe
+GQ +0345+00847 Africa/Malabo
+GR +3758+02343 Europe/Athens
+GS -5416-03632 Atlantic/South_Georgia
+GT +1438-09031 America/Guatemala
+GU +1328+14445 Pacific/Guam
+GW +1151-01535 Africa/Bissau
+GY +0648-05810 America/Guyana
+HK +2217+11409 Asia/Hong_Kong
+HN +1406-08713 America/Tegucigalpa
+HR +4548+01558 Europe/Zagreb
+HT +1832-07220 America/Port-au-Prince
+HU +4730+01905 Europe/Budapest
+ID -0610+10648 Asia/Jakarta Java & Sumatra
+ID -0002+10920 Asia/Pontianak west & central Borneo
+ID -0507+11924 Asia/Makassar east & south Borneo, Sulawesi (Celebes), Bali, Nusa Tengarra, west Timor
+ID -0232+14042 Asia/Jayapura west New Guinea (Irian Jaya) & Malukus (Moluccas)
+IE +5320-00615 Europe/Dublin
+IL +3146+03514 Asia/Jerusalem
+IM +5409-00428 Europe/Isle_of_Man
+IN +2232+08822 Asia/Kolkata
+IO -0720+07225 Indian/Chagos
+IQ +3321+04425 Asia/Baghdad
+IR +3540+05126 Asia/Tehran
+IS +6409-02151 Atlantic/Reykjavik
+IT +4154+01229 Europe/Rome
+JE +4912-00207 Europe/Jersey
+JM +1800-07648 America/Jamaica
+JO +3157+03556 Asia/Amman
+JP +353916+1394441 Asia/Tokyo
+KE -0117+03649 Africa/Nairobi
+KG +4254+07436 Asia/Bishkek
+KH +1133+10455 Asia/Phnom_Penh
+KI +0125+17300 Pacific/Tarawa Gilbert Islands
+KI -0308-17105 Pacific/Enderbury Phoenix Islands
+KI +0152-15720 Pacific/Kiritimati Line Islands
+KM -1141+04316 Indian/Comoro
+KN +1718-06243 America/St_Kitts
+KP +3901+12545 Asia/Pyongyang
+KR +3733+12658 Asia/Seoul
+KW +2920+04759 Asia/Kuwait
+KY +1918-08123 America/Cayman
+KZ +4315+07657 Asia/Almaty most locations
+KZ +4448+06528 Asia/Qyzylorda Qyzylorda (Kyzylorda, Kzyl-Orda)
+KZ +5017+05710 Asia/Aqtobe Aqtobe (Aktobe)
+KZ +4431+05016 Asia/Aqtau Atyrau (Atirau, Gur'yev), Mangghystau (Mankistau)
+KZ +5113+05121 Asia/Oral West Kazakhstan
+LA +1758+10236 Asia/Vientiane
+LB +3353+03530 Asia/Beirut
+LC +1401-06100 America/St_Lucia
+LI +4709+00931 Europe/Vaduz
+LK +0656+07951 Asia/Colombo
+LR +0618-01047 Africa/Monrovia
+LS -2928+02730 Africa/Maseru
+LT +5441+02519 Europe/Vilnius
+LU +4936+00609 Europe/Luxembourg
+LV +5657+02406 Europe/Riga
+LY +3254+01311 Africa/Tripoli
+MA +3339-00735 Africa/Casablanca
+MC +4342+00723 Europe/Monaco
+MD +4700+02850 Europe/Chisinau
+ME +4226+01916 Europe/Podgorica
+MF +1804-06305 America/Marigot
+MG -1855+04731 Indian/Antananarivo
+MH +0709+17112 Pacific/Majuro most locations
+MH +0905+16720 Pacific/Kwajalein Kwajalein
+MK +4159+02126 Europe/Skopje
+ML +1239-00800 Africa/Bamako
+MM +1647+09610 Asia/Rangoon
+MN +4755+10653 Asia/Ulaanbaatar most locations
+MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan
+MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar
+MO +2214+11335 Asia/Macau
+MP +1512+14545 Pacific/Saipan
+MQ +1436-06105 America/Martinique
+MR +1806-01557 Africa/Nouakchott
+MS +1643-06213 America/Montserrat
+MT +3554+01431 Europe/Malta
+MU -2010+05730 Indian/Mauritius
+MV +0410+07330 Indian/Maldives
+MW -1547+03500 Africa/Blantyre
+MX +1924-09909 America/Mexico_City Central Time - most locations
+MX +2105-08646 America/Cancun Central Time - Quintana Roo
+MX +2058-08937 America/Merida Central Time - Campeche, Yucatan
+MX +2540-10019 America/Monterrey Mexican Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas away from US border
+MX +2550-09730 America/Matamoros US Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas near US border
+MX +2313-10625 America/Mazatlan Mountain Time - S Baja, Nayarit, Sinaloa
+MX +2838-10605 America/Chihuahua Mexican Mountain Time - Chihuahua away from US border
+MX +2934-10425 America/Ojinaga US Mountain Time - Chihuahua near US border
+MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora
+MX +3232-11701 America/Tijuana US Pacific Time - Baja California near US border
+MX +3018-11452 America/Santa_Isabel Mexican Pacific Time - Baja California away from US border
+MX +2048-10515 America/Bahia_Banderas Mexican Central Time - Bahia de Banderas
+MY +0310+10142 Asia/Kuala_Lumpur peninsular Malaysia
+MY +0133+11020 Asia/Kuching Sabah & Sarawak
+MZ -2558+03235 Africa/Maputo
+NA -2234+01706 Africa/Windhoek
+NC -2216+16627 Pacific/Noumea
+NE +1331+00207 Africa/Niamey
+NF -2903+16758 Pacific/Norfolk
+NG +0627+00324 Africa/Lagos
+NI +1209-08617 America/Managua
+NL +5222+00454 Europe/Amsterdam
+NO +5955+01045 Europe/Oslo
+NP +2743+08519 Asia/Kathmandu
+NR -0031+16655 Pacific/Nauru
+NU -1901-16955 Pacific/Niue
+NZ -3652+17446 Pacific/Auckland most locations
+NZ -4357-17633 Pacific/Chatham Chatham Islands
+OM +2336+05835 Asia/Muscat
+PA +0858-07932 America/Panama
+PE -1203-07703 America/Lima
+PF -1732-14934 Pacific/Tahiti Society Islands
+PF -0900-13930 Pacific/Marquesas Marquesas Islands
+PF -2308-13457 Pacific/Gambier Gambier Islands
+PG -0930+14710 Pacific/Port_Moresby
+PH +1435+12100 Asia/Manila
+PK +2452+06703 Asia/Karachi
+PL +5215+02100 Europe/Warsaw
+PM +4703-05620 America/Miquelon
+PN -2504-13005 Pacific/Pitcairn
+PR +182806-0660622 America/Puerto_Rico
+PS +3130+03428 Asia/Gaza Gaza Strip
+PS +313200+0350542 Asia/Hebron West Bank
+PT +3843-00908 Europe/Lisbon mainland
+PT +3238-01654 Atlantic/Madeira Madeira Islands
+PT +3744-02540 Atlantic/Azores Azores
+PW +0720+13429 Pacific/Palau
+PY -2516-05740 America/Asuncion
+QA +2517+05132 Asia/Qatar
+RE -2052+05528 Indian/Reunion
+RO +4426+02606 Europe/Bucharest
+RS +4450+02030 Europe/Belgrade
+RU +5443+02030 Europe/Kaliningrad Moscow-01 - Kaliningrad
+RU +5545+03735 Europe/Moscow Moscow+00 - west Russia
+RU +4844+04425 Europe/Volgograd Moscow+00 - Caspian Sea
+RU +5312+05009 Europe/Samara Moscow+00 - Samara, Udmurtia
+RU +5651+06036 Asia/Yekaterinburg Moscow+02 - Urals
+RU +5500+07324 Asia/Omsk Moscow+03 - west Siberia
+RU +5502+08255 Asia/Novosibirsk Moscow+03 - Novosibirsk
+RU +5345+08707 Asia/Novokuznetsk Moscow+03 - Novokuznetsk
+RU +5601+09250 Asia/Krasnoyarsk Moscow+04 - Yenisei River
+RU +5216+10420 Asia/Irkutsk Moscow+05 - Lake Baikal
+RU +6200+12940 Asia/Yakutsk Moscow+06 - Lena River
+RU +4310+13156 Asia/Vladivostok Moscow+07 - Amur River
+RU +4658+14242 Asia/Sakhalin Moscow+07 - Sakhalin Island
+RU +5934+15048 Asia/Magadan Moscow+08 - Magadan
+RU +5301+15839 Asia/Kamchatka Moscow+08 - Kamchatka
+RU +6445+17729 Asia/Anadyr Moscow+08 - Bering Sea
+RW -0157+03004 Africa/Kigali
+SA +2438+04643 Asia/Riyadh
+SB -0932+16012 Pacific/Guadalcanal
+SC -0440+05528 Indian/Mahe
+SD +1536+03232 Africa/Khartoum
+SE +5920+01803 Europe/Stockholm
+SG +0117+10351 Asia/Singapore
+SH -1555-00542 Atlantic/St_Helena
+SI +4603+01431 Europe/Ljubljana
+SJ +7800+01600 Arctic/Longyearbyen
+SK +4809+01707 Europe/Bratislava
+SL +0830-01315 Africa/Freetown
+SM +4355+01228 Europe/San_Marino
+SN +1440-01726 Africa/Dakar
+SO +0204+04522 Africa/Mogadishu
+SR +0550-05510 America/Paramaribo
+SS +0451+03136 Africa/Juba
+ST +0020+00644 Africa/Sao_Tome
+SV +1342-08912 America/El_Salvador
+SX +180305-0630250 America/Lower_Princes
+SY +3330+03618 Asia/Damascus
+SZ -2618+03106 Africa/Mbabane
+TC +2128-07108 America/Grand_Turk
+TD +1207+01503 Africa/Ndjamena
+TF -492110+0701303 Indian/Kerguelen
+TG +0608+00113 Africa/Lome
+TH +1345+10031 Asia/Bangkok
+TJ +3835+06848 Asia/Dushanbe
+TK -0922-17114 Pacific/Fakaofo
+TL -0833+12535 Asia/Dili
+TM +3757+05823 Asia/Ashgabat
+TN +3648+01011 Africa/Tunis
+TO -2110-17510 Pacific/Tongatapu
+TR +4101+02858 Europe/Istanbul
+TT +1039-06131 America/Port_of_Spain
+TV -0831+17913 Pacific/Funafuti
+TW +2503+12130 Asia/Taipei
+TZ -0648+03917 Africa/Dar_es_Salaam
+UA +5026+03031 Europe/Kiev most locations
+UA +4837+02218 Europe/Uzhgorod Ruthenia
+UA +4750+03510 Europe/Zaporozhye Zaporozh'ye, E Lugansk / Zaporizhia, E Luhansk
+UA +4457+03406 Europe/Simferopol central Crimea
+UG +0019+03225 Africa/Kampala
+UM +1645-16931 Pacific/Johnston Johnston Atoll
+UM +2813-17722 Pacific/Midway Midway Islands
+UM +1917+16637 Pacific/Wake Wake Island
+US +404251-0740023 America/New_York Eastern Time
+US +421953-0830245 America/Detroit Eastern Time - Michigan - most locations
+US +381515-0854534 America/Kentucky/Louisville Eastern Time - Kentucky - Louisville area
+US +364947-0845057 America/Kentucky/Monticello Eastern Time - Kentucky - Wayne County
+US +394606-0860929 America/Indiana/Indianapolis Eastern Time - Indiana - most locations
+US +384038-0873143 America/Indiana/Vincennes Eastern Time - Indiana - Daviess, Dubois, Knox & Martin Counties
+US +410305-0863611 America/Indiana/Winamac Eastern Time - Indiana - Pulaski County
+US +382232-0862041 America/Indiana/Marengo Eastern Time - Indiana - Crawford County
+US +382931-0871643 America/Indiana/Petersburg Eastern Time - Indiana - Pike County
+US +384452-0850402 America/Indiana/Vevay Eastern Time - Indiana - Switzerland County
+US +415100-0873900 America/Chicago Central Time
+US +375711-0864541 America/Indiana/Tell_City Central Time - Indiana - Perry County
+US +411745-0863730 America/Indiana/Knox Central Time - Indiana - Starke County
+US +450628-0873651 America/Menominee Central Time - Michigan - Dickinson, Gogebic, Iron & Menominee Counties
+US +470659-1011757 America/North_Dakota/Center Central Time - North Dakota - Oliver County
+US +465042-1012439 America/North_Dakota/New_Salem Central Time - North Dakota - Morton County (except Mandan area)
+US +471551-1014640 America/North_Dakota/Beulah Central Time - North Dakota - Mercer County
+US +394421-1045903 America/Denver Mountain Time
+US +433649-1161209 America/Boise Mountain Time - south Idaho & east Oregon
+US +364708-1084111 America/Shiprock Mountain Time - Navajo
+US +332654-1120424 America/Phoenix Mountain Standard Time - Arizona
+US +340308-1181434 America/Los_Angeles Pacific Time
+US +611305-1495401 America/Anchorage Alaska Time
+US +581807-1342511 America/Juneau Alaska Time - Alaska panhandle
+US +571035-1351807 America/Sitka Alaska Time - southeast Alaska panhandle
+US +593249-1394338 America/Yakutat Alaska Time - Alaska panhandle neck
+US +643004-1652423 America/Nome Alaska Time - west Alaska
+US +515248-1763929 America/Adak Aleutian Islands
+US +550737-1313435 America/Metlakatla Metlakatla Time - Annette Island
+US +211825-1575130 Pacific/Honolulu Hawaii
+UY -3453-05611 America/Montevideo
+UZ +3940+06648 Asia/Samarkand west Uzbekistan
+UZ +4120+06918 Asia/Tashkent east Uzbekistan
+VA +415408+0122711 Europe/Vatican
+VC +1309-06114 America/St_Vincent
+VE +1030-06656 America/Caracas
+VG +1827-06437 America/Tortola
+VI +1821-06456 America/St_Thomas
+VN +1045+10640 Asia/Ho_Chi_Minh
+VU -1740+16825 Pacific/Efate
+WF -1318-17610 Pacific/Wallis
+WS -1350-17144 Pacific/Apia
+YE +1245+04512 Asia/Aden
+YT -1247+04514 Indian/Mayotte
+ZA -2615+02800 Africa/Johannesburg
+ZM -1525+02817 Africa/Lusaka
+ZW -1750+03103 Africa/Harare
diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar
new file mode 120000
index 0000000..d36be8f
--- /dev/null
+++ b/libs/android-support-v4.jar
@@ -0,0 +1 @@
+../../../../out/target/common/obj/JAVA_LIBRARIES/android-support-v4_intermediates/javalib.jar \ No newline at end of file
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..484dab0
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17
+android.library=true
diff --git a/res/layout/time_zone_filter_item.xml b/res/layout/time_zone_filter_item.xml
new file mode 100644
index 0000000..b647830
--- /dev/null
+++ b/res/layout/time_zone_filter_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:padding="8dp" >
+
+ <TextView
+ android:id="@+id/type"
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical"
+ android:minHeight="24dp"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:id="@+id/value"
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical"
+ android:minHeight="32dp"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/time_zone_item.xml b/res/layout/time_zone_item.xml
new file mode 100644
index 0000000..94a192b
--- /dev/null
+++ b/res/layout/time_zone_item.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:orientation="vertical"
+ android:paddingBottom="8dp"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="8dp" >
+
+ <TextView
+ android:id="@+id/time_zone"
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/time_offset"
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:singleLine="true" />
+
+ <TextView
+ android:id="@+id/location"
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/timezonepickerview.xml b/res/layout/timezonepickerview.xml
new file mode 100644
index 0000000..f794bb0
--- /dev/null
+++ b/res/layout/timezonepickerview.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="300dp"
+ android:descendantFocusability="beforeDescendants"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical" >
+
+ <AutoCompleteTextView
+ android:id="@+id/searchBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp"
+ android:layout_marginLeft="8dp"
+ android:layout_marginRight="8dp"
+ android:completionThreshold="1"
+ android:dropDownHeight="250dp"
+ android:hint="@string/hint_time_zone_search"
+ android:imeOptions="actionDone"
+ android:minHeight="48dp"
+ android:singleLine="true" >
+ </AutoCompleteTextView>
+
+ <ListView
+ android:id="@+id/timezonelist"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:background="#FFECECEC"
+ android:choiceMode="singleChoice" >
+ </ListView>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"
+ tools:ignore="PxUsage" />
+
+ <TextView
+ style="@style/font_family_thin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#FFECECEC"
+ android:ellipsize="marquee"
+ android:paddingBottom="8dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="8dp"
+ android:text="@string/dst_note"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000..b54bd83
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,26 @@
+<resources>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+ <!--
+ Base application theme for API 11+. This theme completely replaces
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+ <!-- API 11 theme customizations can go here. -->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644
index 0000000..fc98e49
--- /dev/null
+++ b/res/values-v14/styles.xml
@@ -0,0 +1,27 @@
+<resources>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+ <!--
+ Base application theme for API 14+. This theme completely replaces
+ AppBaseTheme from BOTH res/values/styles.xml and
+ res/values-v11/styles.xml on API 14+ devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- API 14 theme customizations can go here. -->
+ </style>
+
+</resources> \ No newline at end of file
diff --git a/res/values-v16/styles.xml b/res/values-v16/styles.xml
new file mode 100644
index 0000000..997163a
--- /dev/null
+++ b/res/values-v16/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Style for dialog labels. -->
+ <style name="font_family_thin">
+ <item name="android:fontFamily">sans-serif-light</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml
new file mode 100644
index 0000000..043fe71
--- /dev/null
+++ b/res/values-v17/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Style for dialog labels. -->
+ <style name="time_label_thin">
+ <item name="android:fontFamily">sans-serif-thin</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..2be02a3
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="hint_time_zone_search">Country, time zone, or time</string>
+
+ <string name="currenttime_zone">CURRENT TIME ZONE</string>
+ <string name="recent_time_zone">RECENT TIME ZONES</string>
+
+ <string name="country">Countries</string>
+ <string name="time_zone">Time zones</string>
+ <string name="gmt_offset">GMT offset</string>
+ <string name="local_time">Local time</string>
+
+ <string name="results_country">"TIME ZONES IN <xliff:g id="country">%s</xliff:g>"</string>
+ <string name="results_gmt">"TIME ZONES IN GMT <xliff:g id="offset">%s</xliff:g>"</string>
+ <string name="results_local_type">"TIME ZONES AT <xliff:g id="local_time">%s</xliff:g>"</string>
+
+ <string name="dst_note">\u2600 Time zone observes daylight saving time</string>
+
+</resources> \ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..01d486e
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,34 @@
+<resources>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="AppBaseTheme" parent="android:Theme.Light">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="AppBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ </style>
+</resources> \ No newline at end of file
diff --git a/src/com/android/timezonepicker/TimeZoneData.java b/src/com/android/timezonepicker/TimeZoneData.java
new file mode 100644
index 0000000..115a8b8
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZoneData.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class TimeZoneData {
+ private static final String TAG = "TimeZoneData";
+ private static final boolean DEBUG = false;
+ private static final int OFFSET_ARRAY_OFFSET = 20;
+
+ ArrayList<TimeZoneInfo> mTimeZones;
+ LinkedHashMap<String, ArrayList<Integer>> mTimeZonesByCountry;
+ HashSet<String> mTimeZoneNames = new HashSet<String>();
+
+ private long mTimeMillis;
+ private HashMap<String, String> mCountryCodeToNameMap = new HashMap<String, String>();
+
+ public String mDefaultTimeZoneId;
+ public static boolean is24HourFormat;
+ private TimeZoneInfo mDefaultTimeZoneInfo;
+ private String mAlternateDefaultTimeZoneId;
+ private String mDefaultTimeZoneCountry;
+
+ public TimeZoneData(Context context, String defaultTimeZoneId, long timeMillis) {
+ is24HourFormat = TimeZoneInfo.is24HourFormat = DateFormat.is24HourFormat(context);
+ mDefaultTimeZoneId = mAlternateDefaultTimeZoneId = defaultTimeZoneId;
+ long now = System.currentTimeMillis();
+
+ if (timeMillis == 0) {
+ mTimeMillis = now;
+ } else {
+ mTimeMillis = timeMillis;
+ }
+ loadTzs(context);
+ Log.i(TAG, "Time to load time zones (ms): " + (System.currentTimeMillis() - now));
+
+ // now = System.currentTimeMillis();
+ // printTz();
+ // Log.i(TAG, "Time to print time zones (ms): " +
+ // (System.currentTimeMillis() - now));
+ }
+
+ public void setTime(long timeMillis) {
+ mTimeMillis = timeMillis;
+ }
+
+ public TimeZoneInfo get(int position) {
+ return mTimeZones.get(position);
+ }
+
+ public int size() {
+ return mTimeZones.size();
+ }
+
+ public int getDefaultTimeZoneIndex() {
+ return mTimeZones.indexOf(mDefaultTimeZoneInfo);
+ }
+
+ // TODO speed this up
+ public int findIndexByTimeZoneIdSlow(String timeZoneId) {
+ int idx = 0;
+ for (TimeZoneInfo tzi : mTimeZones) {
+ if (timeZoneId.equals(tzi.mTzId)) {
+ return idx;
+ }
+ idx++;
+ }
+ return -1;
+ }
+
+ void loadTzs(Context context) {
+ mTimeZones = new ArrayList<TimeZoneInfo>();
+ HashSet<String> processedTimeZones = loadTzsInZoneTab(context);
+ String[] tzIds = TimeZone.getAvailableIDs();
+
+ if (DEBUG) {
+ Log.e(TAG, "Available time zones: " + tzIds.length);
+ }
+
+ for (String tzId : tzIds) {
+ if (processedTimeZones.contains(tzId)) {
+ continue;
+ }
+
+ final TimeZone tz = TimeZone.getTimeZone(tzId);
+ if (tz == null) {
+ Log.e(TAG, "Timezone not found: " + tzId);
+ continue;
+ }
+
+ TimeZoneInfo tzInfo = new TimeZoneInfo(tz, null);
+
+ if (getIdenticalTimeZoneInTheCountry(tzInfo) == -1) {
+ if (DEBUG) {
+ Log.e(TAG, "# Adding time zone from getAvailId: " + tzInfo.toString());
+ }
+ mTimeZones.add(tzInfo);
+ } else {
+ if (DEBUG) {
+ Log.e(TAG,
+ "# Dropping identical time zone from getAvailId: " + tzInfo.toString());
+ }
+ continue;
+ }
+ //
+ // TODO check for dups
+ // checkForNameDups(tz, tzInfo.mCountry, false /* dls */,
+ // TimeZone.SHORT, groupIdx, !found);
+ // checkForNameDups(tz, tzInfo.mCountry, false /* dls */,
+ // TimeZone.LONG, groupIdx, !found);
+ // if (tz.useDaylightTime()) {
+ // checkForNameDups(tz, tzInfo.mCountry, true /* dls */,
+ // TimeZone.SHORT, groupIdx,
+ // !found);
+ // checkForNameDups(tz, tzInfo.mCountry, true /* dls */,
+ // TimeZone.LONG, groupIdx,
+ // !found);
+ // }
+ }
+
+ // Don't change the order of mTimeZones after this sort
+ Collections.sort(mTimeZones);
+
+ mTimeZonesByCountry = new LinkedHashMap<String, ArrayList<Integer>>();
+ mTimeZonesByOffsets = new SparseArray<ArrayList<Integer>>(mHasTimeZonesInHrOffset.length);
+
+ Date date = new Date(mTimeMillis);
+ Locale defaultLocal = Locale.getDefault();
+
+ int idx = 0;
+ for (TimeZoneInfo tz : mTimeZones) {
+ tz.mDisplayName = tz.mTz.getDisplayName(tz.mTz.inDaylightTime(date),
+ TimeZone.LONG, defaultLocal);
+
+ // /////////////////////
+ // Grouping tz's by country for search by country
+ ArrayList<Integer> group = mTimeZonesByCountry.get(tz.mCountry);
+ if (group == null) {
+ group = new ArrayList<Integer>();
+ mTimeZonesByCountry.put(tz.mCountry, group);
+ }
+
+ group.add(idx);
+
+ // /////////////////////
+ // Grouping tz's by GMT offsets
+ indexByOffsets(idx, tz);
+
+ // Skip all the GMT+xx:xx style display names from search
+ if (!tz.mDisplayName.endsWith(":00")) {
+ mTimeZoneNames.add(tz.mDisplayName);
+ } else if (DEBUG) {
+ Log.e(TAG, "# Hiding from pretty name search: " +
+ tz.mDisplayName);
+ }
+
+ idx++;
+ }
+ }
+
+ private boolean[] mHasTimeZonesInHrOffset = new boolean[40];
+ SparseArray<ArrayList<Integer>> mTimeZonesByOffsets;
+
+ public boolean hasTimeZonesInHrOffset(int offsetHr) {
+ int index = OFFSET_ARRAY_OFFSET + offsetHr;
+ if (index >= mHasTimeZonesInHrOffset.length || index < 0) {
+ return false;
+ }
+ return mHasTimeZonesInHrOffset[index];
+ }
+
+ private void indexByOffsets(int idx, TimeZoneInfo tzi) {
+ int offsetMillis = tzi.getNowOffsetMillis();
+ int index = OFFSET_ARRAY_OFFSET + (int) (offsetMillis / DateUtils.HOUR_IN_MILLIS);
+ mHasTimeZonesInHrOffset[index] = true;
+
+ ArrayList<Integer> group = mTimeZonesByOffsets.get(index);
+ if (group == null) {
+ group = new ArrayList<Integer>();
+ mTimeZonesByOffsets.put(index, group);
+ }
+ group.add(idx);
+ }
+
+ public ArrayList<Integer> getTimeZonesByOffset(int offsetHr) {
+ int index = OFFSET_ARRAY_OFFSET + offsetHr;
+ if (index >= mHasTimeZonesInHrOffset.length || index < 0) {
+ return null;
+ }
+ return mTimeZonesByOffsets.get(index);
+ }
+
+ private HashSet<String> loadTzsInZoneTab(Context context) {
+ HashSet<String> processedTimeZones = new HashSet<String>();
+ AssetManager am = context.getAssets();
+ InputStream is = null;
+
+ /*
+ * The 'backward' file contain mappings between new and old time zone
+ * ids. We will explicitly ignore the old ones.
+ */
+ try {
+ is = am.open("backward");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ // Skip comment lines
+ if (!line.startsWith("#") && line.length() > 0) {
+ // 0: "Link"
+ // 1: New tz id
+ // Last: Old tz id
+ String[] fields = line.split("\t+");
+ String newTzId = fields[1];
+ String oldTzId = fields[fields.length - 1];
+
+ final TimeZone tz = TimeZone.getTimeZone(newTzId);
+ if (tz == null) {
+ Log.e(TAG, "Timezone not found: " + newTzId);
+ continue;
+ }
+
+ processedTimeZones.add(oldTzId);
+
+ if (DEBUG) {
+ Log.e(TAG, "# Dropping identical time zone from backward: " + oldTzId);
+ }
+
+ // Remember the cooler/newer time zone id
+ if (mDefaultTimeZoneId != null && mDefaultTimeZoneId.equals(oldTzId)) {
+ mAlternateDefaultTimeZoneId = newTzId;
+ }
+ }
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to read 'backward' file.");
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ /*
+ * zone.tab contains a list of time zones and country code. They are
+ * "sorted first by country, then an order within the country that (1)
+ * makes some geographical sense, and (2) puts the most populous zones
+ * first, where that does not contradict (1)."
+ */
+ try {
+ String lang = Locale.getDefault().getLanguage();
+ is = am.open("zone.tab");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("#")) { // Skip comment lines
+ // 0: country code
+ // 1: coordinates
+ // 2: time zone id
+ // 3: comments
+ final String[] fields = line.split("\t");
+ final String timeZoneId = fields[2];
+ final String countryCode = fields[0];
+ final TimeZone tz = TimeZone.getTimeZone(timeZoneId);
+ if (tz == null) {
+ Log.e(TAG, "Timezone not found: " + timeZoneId);
+ continue;
+ }
+
+ // Remember the mapping between the country code and display
+ // name
+ String country = mCountryCodeToNameMap.get(fields[0]);
+ if (country == null) {
+ country = new Locale(lang, countryCode)
+ .getDisplayCountry(Locale.getDefault());
+ mCountryCodeToNameMap.put(countryCode, country);
+ }
+
+ // TODO Don't like this here but need to get the country of
+ // the default tz.
+
+ // Find the country of the default tz
+ if (mDefaultTimeZoneId != null && mDefaultTimeZoneCountry == null
+ && timeZoneId.equals(mAlternateDefaultTimeZoneId)) {
+ mDefaultTimeZoneCountry = country;
+ TimeZone defaultTz = TimeZone.getTimeZone(mDefaultTimeZoneId);
+ if (defaultTz != null) {
+ mDefaultTimeZoneInfo = new TimeZoneInfo(defaultTz, country);
+
+ int tzToOverride = getIdenticalTimeZoneInTheCountry(mDefaultTimeZoneInfo);
+ if (tzToOverride == -1) {
+ if (DEBUG) {
+ Log.e(TAG, "Adding default time zone: "
+ + mDefaultTimeZoneInfo.toString());
+ }
+ mTimeZones.add(mDefaultTimeZoneInfo);
+ } else {
+ mTimeZones.add(tzToOverride, mDefaultTimeZoneInfo);
+ if (DEBUG) {
+ TimeZoneInfo tzInfoToOverride = mTimeZones.get(tzToOverride);
+ String tzIdToOverride = tzInfoToOverride.mTzId;
+ Log.e(TAG, "Replaced by default tz: "
+ + tzInfoToOverride.toString());
+ Log.e(TAG, "Adding default time zone: "
+ + mDefaultTimeZoneInfo.toString());
+ }
+ }
+ }
+ }
+
+ // Add to the list of time zones if the time zone is unique
+ // in the given country.
+ TimeZoneInfo timeZoneInfo = new TimeZoneInfo(tz, country);
+ int identicalTzIdx = getIdenticalTimeZoneInTheCountry(timeZoneInfo);
+ if (identicalTzIdx == -1) {
+ if (DEBUG) {
+ Log.e(TAG, "# Adding time zone: " + timeZoneId + " ## " +
+ tz.getDisplayName());
+ }
+ mTimeZones.add(timeZoneInfo);
+ } else {
+ if (DEBUG) {
+ Log.e(TAG, "# Dropping identical time zone: " + timeZoneId + " ## " +
+ tz.getDisplayName());
+ }
+ }
+ processedTimeZones.add(timeZoneId);
+ }
+ }
+
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to read 'zone.tab'.");
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ return processedTimeZones;
+ }
+
+ private int getIdenticalTimeZoneInTheCountry(TimeZoneInfo timeZoneInfo) {
+ int idx = 0;
+ for (TimeZoneInfo tzi : mTimeZones) {
+ if (tzi.hasSameRules(timeZoneInfo)) {
+ if (tzi.mCountry == null) {
+ if (timeZoneInfo.mCountry == null) {
+ return idx;
+ }
+ } else if (tzi.mCountry.equals(timeZoneInfo.mCountry)) {
+ return idx;
+ }
+ }
+ ++idx;
+ }
+ return -1;
+ }
+
+ private void printTz() {
+ for (TimeZoneInfo tz : mTimeZones) {
+ Log.e(TAG, "" + tz.toString());
+ }
+
+ Log.e(TAG, "Total number of tz's = " + mTimeZones.size());
+ }
+
+ // void checkForNameDups(TimeZone tz, String country, boolean dls, int
+ // style, int idx,
+ // boolean print) {
+ // if (country == null) {
+ // return;
+ // }
+ // String displayName = tz.getDisplayName(dls, style);
+ //
+ // if (print) {
+ // Log.e(TAG, "" + idx + " " + tz.getID() + " " + country + " ## " +
+ // displayName);
+ // }
+ //
+ // if (tz.useDaylightTime()) {
+ // if (displayName.matches("GMT[+-][0-9][0-9]:[0-9][0-9]")) {
+ // return;
+ // }
+ //
+ // if (displayName.length() == 3 && displayName.charAt(2) == 'T' &&
+ // (displayName.charAt(1) == 'S' || displayName.charAt(1) == 'D')) {
+ // displayName = "" + displayName.charAt(0) + 'T';
+ // } else {
+ // displayName = displayName.replace(" Daylight ",
+ // " ").replace(" Standard ", " ");
+ // }
+ // }
+ //
+ // String tzNameWithCountry = country + " ## " + displayName;
+ // Integer groupId = mCountryPlusTzName2Tzs.get(tzNameWithCountry);
+ // if (groupId == null) {
+ // mCountryPlusTzName2Tzs.put(tzNameWithCountry, idx);
+ // } else if (groupId != idx) {
+ // Log.e(TAG, "Yikes: " + tzNameWithCountry + " matches " + groupId +
+ // " and " + idx);
+ // }
+ // }
+
+}
diff --git a/src/com/android/timezonepicker/TimeZoneFilterTypeAdapter.java b/src/com/android/timezonepicker/TimeZoneFilterTypeAdapter.java
new file mode 100644
index 0000000..82d65bf
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZoneFilterTypeAdapter.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class TimeZoneFilterTypeAdapter extends BaseAdapter implements Filterable, OnClickListener {
+ public static final String TAG = "TimeZoneFilterTypeAdapter";
+
+ public static final int FILTER_TYPE_EMPTY = -1;
+ public static final int FILTER_TYPE_NONE = 0;
+ public static final int FILTER_TYPE_TIME = 1;
+ public static final int FILTER_TYPE_TIME_ZONE = 2;
+ public static final int FILTER_TYPE_COUNTRY = 3;
+ public static final int FILTER_TYPE_STATE = 4;
+ public static final int FILTER_TYPE_GMT = 5;
+
+ public interface OnSetFilterListener {
+ void onSetFilter(int filterType, String str, int time);
+ }
+
+ static class ViewHolder {
+ int filterType;
+ String str;
+ int time;
+
+ TextView typeTextView;
+ TextView strTextView;
+
+ static void setupViewHolder(View v) {
+ ViewHolder vh = new ViewHolder();
+ vh.typeTextView = (TextView) v.findViewById(R.id.type);
+ vh.strTextView = (TextView) v.findViewById(R.id.value);
+ v.setTag(vh);
+ }
+ }
+
+ class FilterTypeResult {
+ boolean showLabel;
+ int type;
+ String constraint;
+ public int time;
+
+ @Override
+ public String toString() {
+ return constraint;
+ }
+ }
+
+ private ArrayList<FilterTypeResult> mLiveResults = new ArrayList<FilterTypeResult>();
+ private int mLiveResultsCount = 0;
+
+ private ArrayFilter mFilter;
+
+ private LayoutInflater mInflater;
+
+ private TimeZoneData mTimeZoneData;
+ private OnSetFilterListener mListener;
+
+ public TimeZoneFilterTypeAdapter(Context context, TimeZoneData tzd, OnSetFilterListener l) {
+ mTimeZoneData = tzd;
+ mListener = l;
+
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public int getCount() {
+ return mLiveResultsCount;
+ }
+
+ @Override
+ public FilterTypeResult getItem(int position) {
+ return mLiveResults.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v;
+
+ if (convertView != null) {
+ v = convertView;
+ } else {
+ v = mInflater.inflate(R.layout.time_zone_filter_item, null);
+ ViewHolder.setupViewHolder(v);
+ }
+
+ ViewHolder vh = (ViewHolder) v.getTag();
+
+ if (position >= mLiveResults.size()) {
+ Log.e(TAG, "getView: " + position + " of " + mLiveResults.size());
+ }
+
+ FilterTypeResult filter = mLiveResults.get(position);
+
+ vh.filterType = filter.type;
+ vh.str = filter.constraint;
+ vh.time = filter.time;
+
+ if (filter.showLabel) {
+ int resId;
+ switch (filter.type) {
+ case FILTER_TYPE_GMT:
+ resId = R.string.gmt_offset;
+ break;
+ case FILTER_TYPE_TIME:
+ resId = R.string.local_time;
+ break;
+ case FILTER_TYPE_TIME_ZONE:
+ resId = R.string.time_zone;
+ break;
+ case FILTER_TYPE_COUNTRY:
+ resId = R.string.country;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ vh.typeTextView.setText(resId);
+ vh.typeTextView.setVisibility(View.VISIBLE);
+ vh.strTextView.setVisibility(View.GONE);
+ } else {
+ vh.typeTextView.setVisibility(View.GONE);
+ vh.strTextView.setVisibility(View.VISIBLE);
+ }
+ vh.strTextView.setText(filter.constraint);
+ return v;
+ }
+
+ OnClickListener mDummyListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ }
+ };
+
+ // Implements OnClickListener
+
+ // This onClickListener is actually called from the AutoCompleteTextView's
+ // onItemClickListener. Trying to update the text in AutoCompleteTextView
+ // is causing an infinite loop.
+ @Override
+ public void onClick(View v) {
+ if (mListener != null && v != null) {
+ ViewHolder vh = (ViewHolder) v.getTag();
+ mListener.onSetFilter(vh.filterType, vh.str, vh.time);
+ }
+ notifyDataSetInvalidated();
+ }
+
+ // Implements Filterable
+ @Override
+ public Filter getFilter() {
+ if (mFilter == null) {
+ mFilter = new ArrayFilter();
+ }
+ return mFilter;
+ }
+
+ private class ArrayFilter extends Filter {
+ @Override
+ protected FilterResults performFiltering(CharSequence prefix) {
+ Log.e(TAG, "performFiltering >>>> [" + prefix + "]");
+
+ FilterResults results = new FilterResults();
+ String prefixString = null;
+ if (prefix != null) {
+ prefixString = prefix.toString().trim().toLowerCase();
+ }
+
+ if (TextUtils.isEmpty(prefixString)) {
+ results.values = null;
+ results.count = 0;
+ return results;
+ }
+
+ // TODO Perf - we can loop through the filtered list if the new
+ // search string starts with the old search string
+ ArrayList<FilterTypeResult> filtered = new ArrayList<FilterTypeResult>();
+
+ // ////////////////////////////////////////
+ // Search by local time and GMT offset
+ // ////////////////////////////////////////
+ boolean gmtOnly = false;
+ int startParsePosition = 0;
+ if (prefixString.charAt(0) == '+' || prefixString.charAt(0) == '-') {
+ gmtOnly = true;
+ }
+
+ if (prefixString.startsWith("gmt")) {
+ startParsePosition = 3;
+ gmtOnly = true;
+ }
+
+ int num = parseNum(prefixString, startParsePosition);
+ if (num != Integer.MIN_VALUE) {
+ boolean positiveOnly = prefixString.length() > startParsePosition
+ && prefixString.charAt(startParsePosition) == '+';
+ handleSearchByGmt(filtered, num, positiveOnly);
+
+ // Search by time
+// if (!gmtOnly) {
+// for(TimeZoneInfo tzi : mTimeZoneData.mTimeZones) {
+// tzi.getLocalHr(referenceTime)
+// }
+// }
+
+ }
+
+ // ////////////////////////////////////////
+ // Search by country
+ // ////////////////////////////////////////
+ boolean first = true;
+ for (String country : mTimeZoneData.mTimeZonesByCountry.keySet()) {
+ // TODO Perf - cache toLowerCase()?
+ if (country != null && country.toLowerCase().startsWith(prefixString)) {
+ FilterTypeResult r;
+ if (first) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_COUNTRY;
+ r.constraint = null;
+ r.showLabel = true;
+ first = false;
+ }
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_COUNTRY;
+ r.constraint = country;
+ r.showLabel = false;
+ }
+ }
+
+ // ////////////////////////////////////////
+ // Search by time zone name
+ // ////////////////////////////////////////
+ first = true;
+ for (String timeZoneName : mTimeZoneData.mTimeZoneNames) {
+ // TODO Perf - cache toLowerCase()?
+ if (timeZoneName.toLowerCase().startsWith(prefixString)) {
+ FilterTypeResult r;
+ if (first) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_TIME_ZONE;
+ r.constraint = null;
+ r.showLabel = true;
+ first = false;
+ }
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_TIME_ZONE;
+ r.constraint = timeZoneName;
+ r.showLabel = false;
+ }
+ }
+
+ // ////////////////////////////////////////
+ // TODO Search by state
+ // ////////////////////////////////////////
+ Log.e(TAG, "performFiltering <<<< " + filtered.size() + "[" + prefix + "]");
+
+ results.values = filtered;
+ results.count = filtered.size();
+ return results;
+ }
+
+ private void handleSearchByGmt(ArrayList<FilterTypeResult> filtered, int num,
+ boolean positiveOnly) {
+ FilterTypeResult r;
+ int originalResultCount = filtered.size();
+
+ // Separator
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_GMT;
+ r.showLabel = true;
+
+ if (num >= 0) {
+ if (num == 1) {
+ for (int i = 19; i >= 10; i--) {
+ if (mTimeZoneData.hasTimeZonesInHrOffset(i)) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_GMT;
+ r.time = i;
+ r.constraint = "GMT+" + r.time;
+ r.showLabel = false;
+ }
+ }
+ }
+
+ if (mTimeZoneData.hasTimeZonesInHrOffset(num)) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_GMT;
+ r.time = num;
+ r.constraint = "GMT+" + r.time;
+ r.showLabel = false;
+ }
+ num *= -1;
+ }
+
+ if (!positiveOnly && num != 0) {
+ if (mTimeZoneData.hasTimeZonesInHrOffset(num)) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_GMT;
+ r.time = num;
+ r.constraint = "GMT" + r.time;
+ r.showLabel = false;
+ }
+
+ if (num == -1) {
+ for (int i = -10; i >= -19; i--) {
+ if (mTimeZoneData.hasTimeZonesInHrOffset(i)) {
+ r = new FilterTypeResult();
+ filtered.add(r);
+ r.type = FILTER_TYPE_GMT;
+ r.time = i;
+ r.constraint = "GMT" + r.time;
+ r.showLabel = false;
+ }
+ }
+ }
+ }
+
+ // Nothing was added except for the separator. Let's remove it.
+ if (filtered.size() == originalResultCount + 1) {
+ filtered.remove(originalResultCount);
+ }
+ return;
+ }
+
+ //
+ // int start = Integer.MAX_VALUE;
+ // int end = Integer.MIN_VALUE;
+ // switch(num) {
+ // case 2:
+ // if (TimeZoneData.is24HourFormat) {
+ // start = 23;
+ // end = 20;
+ // }
+ // break;
+ // case 1:
+ // if (TimeZoneData.is24HourFormat) {
+ // start = 19;
+ // } else {
+ // start = 12;
+ // }
+ // end = 10;
+ // break;
+ // }
+
+ /**
+ * Acceptable strings are in the following format: [+-]?[0-9]?[0-9]
+ *
+ * @param str
+ * @param startIndex
+ * @return Integer.MIN_VALUE as invalid
+ */
+ public int parseNum(String str, int startIndex) {
+ int idx = startIndex;
+ int num = Integer.MIN_VALUE;
+ int negativeMultiplier = 1;
+
+ // First char - check for + and -
+ char ch = str.charAt(idx++);
+ switch (ch) {
+ case '-':
+ negativeMultiplier = -1;
+ // fall through
+ case '+':
+ if (idx >= str.length()) {
+ // No more digits
+ return Integer.MIN_VALUE;
+ }
+
+ ch = str.charAt(idx++);
+ break;
+ }
+
+ if (!Character.isDigit(ch)) {
+ // No digit
+ return Integer.MIN_VALUE;
+ }
+
+ // Got first digit
+ num = Character.digit(ch, 10);
+
+ // Check next char
+ if (idx < str.length()) {
+ ch = str.charAt(idx++);
+ if (Character.isDigit(ch)) {
+ // Got second digit
+ num = 10 * num + Character.digit(ch, 10);
+ } else {
+ return Integer.MIN_VALUE;
+ }
+ }
+
+ if (idx != str.length()) {
+ // Invalid
+ return Integer.MIN_VALUE;
+ }
+
+ Log.e(TAG, "Parsing " + str + " -> " + negativeMultiplier * num);
+ return negativeMultiplier * num;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults
+ results) {
+ if (results.values == null || results.count == 0) {
+ if (mListener != null) {
+ int filterType;
+ if (TextUtils.isEmpty(constraint)) {
+ filterType = FILTER_TYPE_NONE;
+ } else {
+ filterType = FILTER_TYPE_EMPTY;
+ }
+ mListener.onSetFilter(filterType, null, 0);
+ }
+ Log.e(TAG, "publishResults: " + results.count + " of null [" + constraint);
+ } else {
+ mLiveResults = (ArrayList<FilterTypeResult>) results.values;
+ Log.e(TAG, "publishResults: " + results.count + " of " + mLiveResults.size() + " ["
+ + constraint);
+ }
+ mLiveResultsCount = results.count;
+
+ if (results.count > 0) {
+ notifyDataSetChanged();
+ } else {
+ notifyDataSetInvalidated();
+ }
+ }
+ }
+}
diff --git a/src/com/android/timezonepicker/TimeZoneInfo.java b/src/com/android/timezonepicker/TimeZoneInfo.java
new file mode 100644
index 0000000..6c526fd
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZoneInfo.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class TimeZoneInfo implements Comparable<TimeZoneInfo> {
+ private static final char SEPARATOR = ',';
+ private static final String TAG = null;
+ public static int NUM_OF_TRANSITIONS = 6;
+ public static long time = System.currentTimeMillis() / 1000;
+ public static boolean is24HourFormat;
+
+ TimeZone mTz;
+ public String mTzId;
+ int mRawoffset;
+ public int[] mTransitions; // may have trailing 0's.
+ public String mCountry;
+ public int groupId;
+ private boolean hasDst;
+ public String mDisplayName;
+ private Time recycledTime = new Time();
+ private static StringBuilder mSB = new StringBuilder(50);
+ private static Formatter mFormatter = new Formatter(mSB, Locale.getDefault());
+
+ public TimeZoneInfo(TimeZone tz, String country) {
+ mTz = tz;
+ mTzId = tz.getID();
+ mCountry = country;
+ mRawoffset = tz.getRawOffset();
+ hasDst = tz.useDaylightTime();
+
+ try {
+ mTransitions = getTransitions(tz, time);
+ } catch (NoSuchFieldException ignored) {
+ } catch (IllegalAccessException ignored) {
+ ignored.printStackTrace();
+ }
+ }
+
+ SparseArray<String> mLocalTimeCache = new SparseArray<String>();
+ long mLocalTimeCacheReferenceTime = 0;
+ static private long mGmtDisplayNameUpdateTime;
+ static private SparseArray<String> mGmtDisplayNameCache = new SparseArray<String>();
+
+ public String getLocalTime(long referenceTime) {
+ recycledTime.timezone = TimeZone.getDefault().getID();
+ recycledTime.set(referenceTime);
+
+ int currYearDay = recycledTime.year * 366 + recycledTime.yearDay;
+
+ recycledTime.timezone = mTzId;
+ recycledTime.set(referenceTime);
+
+ String localTimeStr = null;
+
+ int hourMinute = recycledTime.hour * 60 +
+ recycledTime.minute;
+
+ if (mLocalTimeCacheReferenceTime != referenceTime) {
+ mLocalTimeCacheReferenceTime = referenceTime;
+ mLocalTimeCache.clear();
+ } else {
+ localTimeStr = mLocalTimeCache.get(hourMinute);
+ }
+
+ if (localTimeStr == null) {
+ String format = "%I:%M %p";
+ if (currYearDay != (recycledTime.year * 366 + recycledTime.yearDay)) {
+ if (is24HourFormat) {
+ format = "%b %d %H:%M";
+ } else {
+ format = "%b %d %I:%M %p";
+ }
+ } else if (is24HourFormat) {
+ format = "%H:%M";
+ }
+
+ // format = "%Y-%m-%d %H:%M";
+ localTimeStr = recycledTime.format(format);
+ mLocalTimeCache.put(hourMinute, localTimeStr);
+ }
+
+ return localTimeStr;
+ }
+
+ public int getLocalHr(long referenceTime) {
+ recycledTime.timezone = TimeZone.getDefault().getID();
+ recycledTime.set(referenceTime);
+ return recycledTime.hour;
+ }
+
+ public int getNowOffsetMillis() {
+ return mTz.getOffset(System.currentTimeMillis());
+ }
+
+ /*
+ * The method is synchronized because there's one mSB, which is used by
+ * mFormatter, per instance. If there are multiple callers for
+ * getGmtDisplayName, the output may be mangled.
+ */
+ public synchronized String getGmtDisplayName(Context context) {
+ // TODO Note: The local time is shown in current time (current GMT
+ // offset) which may be different from the time specified by
+ // mTimeMillis
+
+ final long nowMinute = System.currentTimeMillis() / DateUtils.MINUTE_IN_MILLIS;
+ final long now = nowMinute * DateUtils.MINUTE_IN_MILLIS;
+ final int gmtOffset = mTz.getOffset(now);
+ int cacheKey;
+
+ boolean hasFutureDST = mTz.useDaylightTime();
+ if (hasFutureDST) {
+ cacheKey = (int) (gmtOffset + 36 * DateUtils.HOUR_IN_MILLIS);
+ } else {
+ cacheKey = (int) (gmtOffset - 36 * DateUtils.HOUR_IN_MILLIS);
+ }
+
+ String displayName = null;
+ if (mGmtDisplayNameUpdateTime != nowMinute) {
+ mGmtDisplayNameUpdateTime = nowMinute;
+ mGmtDisplayNameCache.clear();
+ } else {
+ displayName = mGmtDisplayNameCache.get(cacheKey);
+ }
+
+ if (displayName == null) {
+ mSB.setLength(0);
+ int flags = DateUtils.FORMAT_ABBREV_ALL;
+ flags |= DateUtils.FORMAT_SHOW_TIME;
+ if (TimeZoneInfo.is24HourFormat) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+
+ // mFormatter writes to mSB
+ DateUtils.formatDateRange(context, mFormatter, now, now, flags, mTzId);
+ mSB.append(" (GMT");
+
+ if (gmtOffset < 0) {
+ mSB.append('-');
+ } else {
+ mSB.append('+');
+ }
+
+ final int p = Math.abs(gmtOffset);
+ mSB.append(p / DateUtils.HOUR_IN_MILLIS); // Hour
+
+ final int min = (p / 60000) % 60;
+ if (min != 0) { // Show minutes if non-zero
+ mSB.append(':');
+ if (min < 10) {
+ mSB.append('0');
+ }
+ mSB.append(min);
+ }
+ mSB.append(')');
+
+ if (hasFutureDST) {
+ mSB.append(" \u2600"); // Sun symbol
+ }
+
+ displayName = mSB.toString();
+ mGmtDisplayNameCache.put(cacheKey, displayName);
+ }
+ return displayName;
+ }
+
+ private static int[] getTransitions(TimeZone tz, long time)
+ throws IllegalAccessException, NoSuchFieldException {
+ Class<?> zoneInfoClass = tz.getClass();
+ Field mTransitionsField = zoneInfoClass.getDeclaredField("mTransitions");
+ mTransitionsField.setAccessible(true);
+ int[] objTransitions = (int[]) mTransitionsField.get(tz);
+ int[] transitions = null;
+ if (objTransitions.length != 0) {
+ transitions = new int[NUM_OF_TRANSITIONS];
+ int numOfTransitions = 0;
+ for (int i = 0; i < objTransitions.length; ++i) {
+ if (objTransitions[i] < time) {
+ continue;
+ }
+ transitions[numOfTransitions++] = objTransitions[i];
+ if (numOfTransitions == NUM_OF_TRANSITIONS) {
+ break;
+ }
+ }
+ }
+ return transitions;
+ }
+
+ public boolean hasSameRules(TimeZoneInfo tzi) {
+ // this.mTz.hasSameRules(tzi.mTz)
+
+ return this.mRawoffset == tzi.mRawoffset
+ && Arrays.equals(this.mTransitions, tzi.mTransitions);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ final String country = this.mCountry;
+ final TimeZone tz = this.mTz;
+
+ sb.append(mTzId);
+ sb.append(SEPARATOR);
+ sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.LONG));
+ sb.append(SEPARATOR);
+ sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.SHORT));
+ sb.append(SEPARATOR);
+ if (tz.useDaylightTime()) {
+ sb.append(tz.getDisplayName(true, TimeZone.LONG));
+ sb.append(SEPARATOR);
+ sb.append(tz.getDisplayName(true, TimeZone.SHORT));
+ } else {
+ sb.append(SEPARATOR);
+ }
+ sb.append(SEPARATOR);
+ sb.append(tz.getRawOffset() / 3600000f);
+ sb.append(SEPARATOR);
+ sb.append(tz.getDSTSavings() / 3600000f);
+ sb.append(SEPARATOR);
+ sb.append(country);
+ sb.append(SEPARATOR);
+
+ // 1-1-2013 noon GMT
+ sb.append(getLocalTime(1357041600000L));
+ sb.append(SEPARATOR);
+
+ // 3-15-2013 noon GMT
+ sb.append(getLocalTime(1363348800000L));
+ sb.append(SEPARATOR);
+
+ // 7-1-2013 noon GMT
+ sb.append(getLocalTime(1372680000000L));
+ sb.append(SEPARATOR);
+
+ // 11-01-2013 noon GMT
+ sb.append(getLocalTime(1383307200000L));
+ sb.append(SEPARATOR);
+
+ // if (this.mTransitions != null && this.mTransitions.length != 0) {
+ // sb.append('"');
+ // DateFormat df = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss Z",
+ // Locale.US);
+ // df.setTimeZone(tz);
+ // DateFormat weekdayFormat = new SimpleDateFormat("EEEE", Locale.US);
+ // weekdayFormat.setTimeZone(tz);
+ // Formatter f = new Formatter(sb);
+ // for (int i = 0; i < this.mTransitions.length; ++i) {
+ // if (this.mTransitions[i] < time) {
+ // continue;
+ // }
+ //
+ // String fromTime = formatTime(df, this.mTransitions[i] - 1);
+ // String toTime = formatTime(df, this.mTransitions[i]);
+ // f.format("%s -> %s (%d)", fromTime, toTime, this.mTransitions[i]);
+ //
+ // String weekday = weekdayFormat.format(new Date(1000L *
+ // this.mTransitions[i]));
+ // if (!weekday.equals("Sunday")) {
+ // f.format(" -- %s", weekday);
+ // }
+ // sb.append("##");
+ // }
+ // sb.append('"');
+ // }
+ // sb.append(SEPARATOR);
+ sb.append('\n');
+ return sb.toString();
+ }
+
+ private static String formatTime(DateFormat df, int s) {
+ long ms = s * 1000L;
+ return df.format(new Date(ms));
+ }
+
+ /*
+ * Returns a negative integer if this instance is less than the other; a
+ * positive integer if this instance is greater than the other; 0 if this
+ * instance has the same order as the other.
+ */
+ @Override
+ public int compareTo(TimeZoneInfo other) {
+
+ // TODO !!! Should compare the clock time instead of raw offset
+
+ // Higher raw offset comes before i.e. if the offset is bigger, return
+ // positive number.
+ if (this.mRawoffset != other.mRawoffset) {
+ return other.mRawoffset - this.mRawoffset;
+ }
+
+ // TZ with DST comes first because the offset is bigger during DST
+ // compared to a tz without DST
+ if (this.hasDst != other.hasDst) {
+ return this.hasDst ? -1 : 1;
+ }
+
+ // By country
+ if (this.mCountry == null) {
+ if (other.mCountry != null) {
+ return 1;
+ }
+ }
+
+ if (other.mCountry == null) {
+ return -1;
+ } else {
+ int diff = this.mCountry.compareTo(other.mCountry);
+
+ if (diff != 0) {
+ return diff;
+ }
+ }
+
+ if (Arrays.equals(this.mTransitions, other.mTransitions)) {
+ Log.e(TAG, "Not expected to be comparing tz with the same country, same offset," +
+ " same dst, same transitions:\n" + this.toString() + "\n" + other.toString());
+ }
+
+ // Finally diff by display name
+ return this.mTz.getDisplayName(Locale.getDefault()).compareTo(
+ other.mTz.getDisplayName(Locale.getDefault()));
+ }
+}
diff --git a/src/com/android/timezonepicker/TimeZonePickerDialog.java b/src/com/android/timezonepicker/TimeZonePickerDialog.java
new file mode 100644
index 0000000..e1d70a7
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZonePickerDialog.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+public class TimeZonePickerDialog extends DialogFragment implements
+ TimeZonePickerView.OnTimeZoneSetListener {
+ public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time";
+ public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone";
+
+ private OnTimeZoneSetListener mTimeZoneSetListener;
+
+ public interface OnTimeZoneSetListener {
+ void onTimeZoneSet(TimeZoneInfo tzi);
+ }
+
+ public void setOnTimeZoneSetListener(OnTimeZoneSetListener l) {
+ mTimeZoneSetListener = l;
+ }
+
+ public TimeZonePickerDialog() {
+ super();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ long timeMillis = 0;
+ String timeZone = null;
+ if (savedInstanceState != null) {
+ // TODO
+ } else {
+ Bundle b = getArguments();
+ if (b != null) {
+ timeMillis = b.getLong(BUNDLE_START_TIME_MILLIS);
+ timeZone = b.getString(BUNDLE_TIME_ZONE);
+ }
+ }
+ return new TimeZonePickerView(getActivity(), null, timeZone, timeMillis, this);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ Window w = dialog.getWindow();
+ WindowManager.LayoutParams a = w.getAttributes();
+ a.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ a.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+ w.setAttributes(a);
+
+ return dialog;
+ }
+
+ @Override
+ public void onTimeZoneSet(TimeZoneInfo tzi) {
+ if (mTimeZoneSetListener != null) {
+ mTimeZoneSetListener.onTimeZoneSet(tzi);
+ }
+ dismiss();
+ }
+}
diff --git a/src/com/android/timezonepicker/TimeZonePickerView.java b/src/com/android/timezonepicker/TimeZonePickerView.java
new file mode 100644
index 0000000..fc7e5e0
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZonePickerView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AutoCompleteTextView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+public class TimeZonePickerView extends LinearLayout implements TextWatcher, OnItemClickListener {
+ private static final String TAG = "TimeZonePickerView";
+
+ private Context mContext;
+ private AutoCompleteTextView mAutoCompleteTextView;
+ private TimeZoneFilterTypeAdapter mFilterAdapter;
+ TimeZoneResultAdapter mResultAdapter;
+
+ public interface OnTimeZoneSetListener {
+ void onTimeZoneSet(TimeZoneInfo tzi);
+ }
+
+ public TimeZonePickerView(Context context, AttributeSet attrs,
+ String timeZone, long timeMillis, OnTimeZoneSetListener l) {
+ super(context, attrs);
+ mContext = context;
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.timezonepickerview, this, true);
+
+ TimeZoneData tzd = new TimeZoneData(mContext, timeZone, timeMillis);
+
+ mResultAdapter = new TimeZoneResultAdapter(mContext, tzd, l);
+ ListView timeZoneList = (ListView) findViewById(R.id.timezonelist);
+ timeZoneList.setAdapter(mResultAdapter);
+
+ mAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.searchBox);
+ mFilterAdapter = new TimeZoneFilterTypeAdapter(mContext, tzd, mResultAdapter);
+ mAutoCompleteTextView.setAdapter(mFilterAdapter);
+ mAutoCompleteTextView.addTextChangedListener(this);
+ mAutoCompleteTextView.setOnItemClickListener(this);
+ }
+
+ // Implementation of TextWatcher
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ // Implementation of TextWatcher
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ mFilterAdapter.getFilter().filter(s.toString());
+ }
+
+ // Implementation of TextWatcher
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ // An onClickListener for the view item because I haven't figured out a
+ // way to update the AutoCompleteTextView without causing an infinite loop.
+ mFilterAdapter.onClick(view);
+ }
+}
diff --git a/src/com/android/timezonepicker/TimeZoneResultAdapter.java b/src/com/android/timezonepicker/TimeZoneResultAdapter.java
new file mode 100644
index 0000000..7d8b10f
--- /dev/null
+++ b/src/com/android/timezonepicker/TimeZoneResultAdapter.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.timezonepicker;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import com.android.timezonepicker.TimeZoneFilterTypeAdapter.OnSetFilterListener;
+import com.android.timezonepicker.TimeZonePickerView.OnTimeZoneSetListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+public class TimeZoneResultAdapter extends BaseAdapter implements OnClickListener,
+ OnSetFilterListener {
+ private static final String TAG = "TimeZoneResultAdapter";
+ private static final int VIEW_TAG_TIME_ZONE = R.id.time_zone;
+
+ /** SharedPref name and key for recent time zones */
+ private static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
+ private static final String KEY_RECENT_TIMEZONES = "preferences_recent_timezones";
+
+ /**
+ * The delimiter we use when serializing recent timezones to shared
+ * preferences
+ */
+ private static final String RECENT_TIMEZONES_DELIMITER = ",";
+
+ /** The maximum number of recent timezones to save */
+ private static final int MAX_RECENT_TIMEZONES = 3;
+
+ static class ViewHolder {
+ TextView timeZone;
+ TextView timeOffset;
+ TextView location;
+
+ static void setupViewHolder(View v) {
+ ViewHolder vh = new ViewHolder();
+ vh.timeZone = (TextView) v.findViewById(R.id.time_zone);
+ vh.timeOffset = (TextView) v.findViewById(R.id.time_offset);
+ vh.location = (TextView) v.findViewById(R.id.location);
+ v.setTag(vh);
+ }
+ }
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+
+ private OnTimeZoneSetListener mTimeZoneSetListener;
+ private TimeZoneData mTimeZoneData;
+
+ private int[] mFilteredTimeZoneIndices;
+ private int mFilteredTimeZoneLength = 0;
+ private int mFilterType;
+
+ public TimeZoneResultAdapter(Context context, TimeZoneData tzd,
+ com.android.timezonepicker.TimeZonePickerView.OnTimeZoneSetListener l) {
+ super();
+
+ mContext = context;
+ mTimeZoneData = tzd;
+ mTimeZoneSetListener = l;
+
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mFilteredTimeZoneIndices = new int[mTimeZoneData.size()];
+
+ onSetFilter(TimeZoneFilterTypeAdapter.FILTER_TYPE_NONE, null, 0);
+ }
+
+ // Implements OnSetFilterListener
+ @Override
+ public void onSetFilter(int filterType, String str, int time) {
+ Log.d(TAG, "onSetFilter: " + filterType + " [" + str + "] " + time);
+
+ mFilterType = filterType;
+ mFilteredTimeZoneLength = 0;
+ int idx = 0;
+
+ switch (filterType) {
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_EMPTY:
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_NONE:
+ // Show the default/current value first
+ int defaultTzIndex = mTimeZoneData.getDefaultTimeZoneIndex();
+ if (defaultTzIndex != -1) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = defaultTzIndex;
+ }
+
+ // Show the recent selections
+ SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFS_NAME,
+ Context.MODE_PRIVATE);
+ String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
+ if (!TextUtils.isEmpty(recentsString)) {
+ String[] recents = recentsString.split(RECENT_TIMEZONES_DELIMITER);
+ for (int i = recents.length - 1; i >= 0; i--) {
+ if (!TextUtils.isEmpty(recents[i])
+ && !recents[i].equals(mTimeZoneData.mDefaultTimeZoneId)) {
+ int index = mTimeZoneData.findIndexByTimeZoneIdSlow(recents[i]);
+ if (index != -1) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = index;
+ }
+ }
+ }
+ }
+
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_GMT:
+ ArrayList<Integer> indices = mTimeZoneData.getTimeZonesByOffset(time);
+ if (indices != null) {
+ for (Integer i : indices) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = i;
+ }
+ }
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_TIME:
+ // TODO Filter by time properly
+ for (TimeZoneInfo tzi : mTimeZoneData.mTimeZones) {
+ if (str.equalsIgnoreCase(tzi.getGmtDisplayName(mContext))) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = idx;
+ }
+ idx++;
+ }
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_TIME_ZONE:
+ for (TimeZoneInfo tzi : mTimeZoneData.mTimeZones) {
+ if (str.equalsIgnoreCase(tzi.mDisplayName)) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = idx;
+ }
+ idx++;
+ }
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_COUNTRY:
+ ArrayList<Integer> tzIds = mTimeZoneData.mTimeZonesByCountry.get(str);
+ if (tzIds != null) {
+ for (Integer tzi : tzIds) {
+ mFilteredTimeZoneIndices[mFilteredTimeZoneLength++] = tzi;
+ }
+ }
+ break;
+ case TimeZoneFilterTypeAdapter.FILTER_TYPE_STATE:
+ // TODO Filter by state
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Saves the given timezone ID as a recent timezone under shared
+ * preferences. If there are already the maximum number of recent timezones
+ * saved, it will remove the oldest and append this one.
+ *
+ * @param id the ID of the timezone to save
+ * @see {@link #MAX_RECENT_TIMEZONES}
+ */
+ public void saveRecentTimezone(String id) {
+ SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFS_NAME,
+ Context.MODE_PRIVATE);
+ String recentsString = prefs.getString(KEY_RECENT_TIMEZONES, null);
+ if (recentsString == null) {
+ recentsString = id;
+ } else {
+ List<String> recents = new ArrayList<String>(
+ Arrays.asList(recentsString.split(RECENT_TIMEZONES_DELIMITER)));
+ Iterator<String> it = recents.iterator();
+ while(it.hasNext()) {
+ String tz = it.next();
+ if (id.equals(tz)) {
+ it.remove();
+ }
+ }
+
+ while (recents.size() >= MAX_RECENT_TIMEZONES) {
+ recents.remove(0);
+ }
+ recents.add(id);
+
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String recent : recents) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(RECENT_TIMEZONES_DELIMITER);
+ }
+ builder.append(recent);
+ }
+ recentsString = builder.toString();
+ }
+
+ prefs.edit().putString(KEY_RECENT_TIMEZONES, recentsString).apply();
+ }
+
+ @Override
+ public int getCount() {
+ return mFilteredTimeZoneLength;
+ }
+
+ @Override
+ public TimeZoneInfo getItem(int position) {
+ if (position < 0 || position >= mFilteredTimeZoneLength) {
+ return null;
+ }
+
+ return mTimeZoneData.get(mFilteredTimeZoneIndices[position]);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mFilteredTimeZoneIndices[position];
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+
+ if (v == null) {
+ v = mInflater.inflate(R.layout.time_zone_item, null);
+ v.setOnClickListener(this);
+ ViewHolder.setupViewHolder(v);
+ }
+
+ TimeZoneInfo tzi = mTimeZoneData.get(mFilteredTimeZoneIndices[position]);
+ v.setTag(VIEW_TAG_TIME_ZONE, tzi);
+
+ ViewHolder vh = (ViewHolder) v.getTag();
+ vh.timeOffset.setText(tzi.getGmtDisplayName(mContext));
+
+ vh.timeZone.setText(tzi.mDisplayName);
+
+ String location = tzi.mCountry;
+ if (location == null) {
+ vh.location.setVisibility(View.INVISIBLE);
+ } else {
+ vh.location.setText(location);
+ vh.location.setVisibility(View.VISIBLE);
+ }
+
+ return v;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ // Implements OnClickListener
+ @Override
+ public void onClick(View v) {
+ if (mTimeZoneSetListener != null) {
+ TimeZoneInfo tzi = (TimeZoneInfo) v.getTag(VIEW_TAG_TIME_ZONE);
+ mTimeZoneSetListener.onTimeZoneSet(tzi);
+ saveRecentTimezone(tzi.mTzId);
+ }
+ }
+
+}