diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Android.mk | 27 | ||||
| -rw-r--r-- | AndroidManifest.xml | 26 | ||||
| -rw-r--r-- | assets/backward | 117 | ||||
| -rw-r--r-- | assets/zone.tab | 441 | ||||
| l--------- | libs/android-support-v4.jar | 1 | ||||
| -rw-r--r-- | project.properties | 15 | ||||
| -rw-r--r-- | res/layout/time_zone_filter_item.xml | 49 | ||||
| -rw-r--r-- | res/layout/time_zone_item.xml | 56 | ||||
| -rw-r--r-- | res/layout/timezonepickerview.xml | 70 | ||||
| -rw-r--r-- | res/values-v11/styles.xml | 26 | ||||
| -rw-r--r-- | res/values-v14/styles.xml | 27 | ||||
| -rw-r--r-- | res/values-v16/styles.xml | 22 | ||||
| -rw-r--r-- | res/values-v17/styles.xml | 22 | ||||
| -rw-r--r-- | res/values/strings.xml | 35 | ||||
| -rw-r--r-- | res/values/styles.xml | 34 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZoneData.java | 442 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZoneFilterTypeAdapter.java | 470 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZoneInfo.java | 352 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZonePickerDialog.java | 85 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZonePickerView.java | 86 | ||||
| -rw-r--r-- | src/com/android/timezonepicker/TimeZoneResultAdapter.java | 283 |
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); + } + } + +} |
