summaryrefslogtreecommitdiff
path: root/chromium/third_party/libaddressinput
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/libaddressinput')
-rw-r--r--chromium/third_party/libaddressinput/chromium/resources/address_input_strings_da.xtb4
-rw-r--r--chromium/third_party/libaddressinput/chromium/resources/address_input_strings_de.xtb2
-rw-r--r--chromium/third_party/libaddressinput/chromium/resources/address_input_strings_id.xtb4
-rw-r--r--chromium/third_party/libaddressinput/chromium/resources/address_input_strings_mr.xtb16
-rw-r--r--chromium/third_party/libaddressinput/chromium/resources/address_input_strings_sk.xtb4
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/android/util/Log.java38
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressAutocompleteControllerTest.java173
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressWidgetUiComponentProviderTest.java212
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApiTest.java146
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AsyncTestCaseTest.java95
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/DummyTest.java15
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/PlaceDetailsClientTest.java87
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImplTest.java147
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/AsyncTestCase.java75
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/TestActivity.java21
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressAutocompleteController.java251
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressUiComponent.java191
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidget.java936
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidgetUiComponentProvider.java108
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApi.java46
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncRequestApi.java111
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsApi.java28
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsClient.java162
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImpl.java107
-rw-r--r--chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompletePredictionImpl.java32
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompleteApi.java27
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompletePrediction.java51
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressData.java789
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressDataKey.java129
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressField.java159
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblemType.java75
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblems.java78
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationData.java157
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationNodeData.java46
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AsyncRequestApi.java49
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/CacheData.java417
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientCacheManager.java31
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientData.java282
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataLoadListener.java26
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataSource.java43
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FieldVerifier.java435
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormController.java324
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormOptions.java186
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormatInterpreter.java408
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/JsoMap.java283
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/LookupKey.java437
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/NotifyingListener.java46
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/OnAddressSelectedListener.java26
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionData.java109
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionDataConstants.java285
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/SimpleClientCacheManager.java41
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardAddressVerifier.java226
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardChecks.java71
-rw-r--r--chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/Util.java247
54 files changed, 8479 insertions, 15 deletions
diff --git a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_da.xtb b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_da.xtb
index d4915ff5ee2..14f01a42e9c 100644
--- a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_da.xtb
+++ b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_da.xtb
@@ -33,7 +33,7 @@
<translation id="7087282848513945231">Amt/region</translation>
<translation id="7139724024395191329">Emirat</translation>
<translation id="7393381084163773901">Adresse</translation>
-<translation id="7602447984296396718">Dette postnummer ser ikke ud til at passe til resten af denne adresse.</translation>
+<translation id="7602447984296396718">Dette postnummer ser ikke ud til at passe til resten af adressen.</translation>
<translation id="7738983109397305830">Dette postnummer stemmer tilsyneladende ikke overens med resten af adressen. Kender du ikke dit postnummer? Find det <ph name="BEGIN_LINK" />her<ph name="END_LINK" />.</translation>
<translation id="777702478322588152">Præfektur</translation>
<translation id="7805765407568469194">Landsby/township</translation>
@@ -42,6 +42,6 @@
<translation id="8446364922515257065">Do/Si</translation>
<translation id="8449204988444194299">Postby</translation>
<translation id="8471101563037901452">Dette postnummer stemmer tilsyneladende ikke overens med resten af adressen. Kender du ikke dit postnummer? Find det <ph name="BEGIN_LINK" />her<ph name="END_LINK" />.</translation>
-<translation id="9104066683700680171">Dette postnummer ser ikke ud til at passe til resten af ​​denne adresse.</translation>
+<translation id="9104066683700680171">Dette postnummer ser ikke ud til at passe til resten af adressen.</translation>
<translation id="9207002871037636573">Du skal angive et postnummer, f.eks. <ph name="EXAMPLE" />.</translation>
</translationbundle> \ No newline at end of file
diff --git a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_de.xtb b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_de.xtb
index 896356439c1..3fd9e26f2d7 100644
--- a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_de.xtb
+++ b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_de.xtb
@@ -40,7 +40,7 @@
<translation id="8184169487503545976">Dieses Postleitzahlenformat wird nicht erkannt.</translation>
<translation id="820600307078153032">Das Format der Postleitzahl wurde nicht erkannt. Beispiel für eine gültige Postleitzahl: <ph name="EXAMPLE" /></translation>
<translation id="8446364922515257065">Do/Si</translation>
-<translation id="8449204988444194299">Post Town</translation>
+<translation id="8449204988444194299">Stadt</translation>
<translation id="8471101563037901452">Die Postleitzahl stimmt offenbar nicht mit dem Rest der Adresse überein. Sie kennen Ihre Postleitzahl nicht? <ph name="BEGIN_LINK" />Schlagen Sie sie hier nach<ph name="END_LINK" />.</translation>
<translation id="9104066683700680171">Diese Postleitzahl scheint nicht zum Rest dieser Adresse zu passen.</translation>
<translation id="9207002871037636573">Sie müssen eine Postleitzahl angeben, wie beispielsweise <ph name="EXAMPLE" />.</translation>
diff --git a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_id.xtb b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_id.xtb
index 6ccce4dee4f..aca4bfef5a4 100644
--- a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_id.xtb
+++ b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_id.xtb
@@ -34,14 +34,14 @@
<translation id="7139724024395191329">Emirat</translation>
<translation id="7393381084163773901">Alamat</translation>
<translation id="7602447984296396718">Tampaknya kode pos ini tidak cocok dengan sisa alamat ini.</translation>
-<translation id="7738983109397305830">Kode postingan ini tampaknya tidak cocok dengan bagian lain dari alamat ini Tidak tahu kode pos Anda? Temukan <ph name="BEGIN_LINK" />di sini<ph name="END_LINK" />.</translation>
+<translation id="7738983109397305830">Kode pos ini tampaknya tidak cocok dengan bagian lain dari alamat ini Tidak tahu kode pos Anda? Temukan <ph name="BEGIN_LINK" />di sini<ph name="END_LINK" />.</translation>
<translation id="777702478322588152">Prefektur</translation>
<translation id="7805765407568469194">Desa/Kecamatan</translation>
<translation id="8184169487503545976">Format kode pos ini tidak dikenal.</translation>
<translation id="820600307078153032">Format kode pos ini tidak dikenal. Contoh kode pos yang valid: <ph name="EXAMPLE" />.</translation>
<translation id="8446364922515257065">Do/Si</translation>
<translation id="8449204988444194299">Kota</translation>
-<translation id="8471101563037901452">Kode postingan ini tampaknya tidak cocok dengan bagian lain dari alamat ini. Tidak tahu kode pos Anda? Temukan <ph name="BEGIN_LINK" />di sini<ph name="END_LINK" />.</translation>
+<translation id="8471101563037901452">Kode pos ini tampaknya tidak cocok dengan bagian lain dari alamat ini. Tidak tahu kode pos Anda? Temukan <ph name="BEGIN_LINK" />di sini<ph name="END_LINK" />.</translation>
<translation id="9104066683700680171">Tampaknya kode pos ini tidak cocok dengan sisa alamat ini.</translation>
<translation id="9207002871037636573">Anda harus memberikan kode pos, misalnya <ph name="EXAMPLE" />.</translation>
</translationbundle> \ No newline at end of file
diff --git a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_mr.xtb b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_mr.xtb
index b7b16ea0702..e049746ad1c 100644
--- a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_mr.xtb
+++ b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_mr.xtb
@@ -1,18 +1,18 @@
<?xml version="1.0" ?>
<!DOCTYPE translationbundle>
<translationbundle lang="mr">
-<translation id="1340068511406764697">आपण एक पोस्टल कोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />. आपला पोस्टल कोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
+<translation id="1340068511406764697">तुम्ही एक पोस्टल कोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />. तुमचा पोस्टल कोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
<translation id="2053553514270667976">पिनकोड</translation>
<translation id="2096368010154057602">विभाग</translation>
<translation id="2577522251608256362">अतिपरिचित क्षेत्र</translation>
-<translation id="3050787670591910834">आपण एक पोस्टल कोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />.</translation>
+<translation id="3050787670591910834">तुम्ही एक पोस्टल कोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />.</translation>
<translation id="3174168572213147020">बेट</translation>
-<translation id="3713769522066937702">हे पिनकोड स्वरूपन ओळखीचे नाही. वैध पिनकोडचे उदाहरण: <ph name="EXAMPLE" />. आपला पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
+<translation id="3713769522066937702">हे पिनकोड स्वरूपन ओळखीचे नाही. वैध पिनकोडचे उदाहरण: <ph name="EXAMPLE" />. तुमचा पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
<translation id="3882422586004212847">हे पोस्टल कोड स्वरूपन ओळखीचे नाही. वैध पोस्टल कोडचे उदाहरण: <ph name="EXAMPLE" />.</translation>
<translation id="3885155851504623709">पॅरिश</translation>
-<translation id="43113324827158664">आपण हे रिक्त सोडू शकत नाही.</translation>
+<translation id="43113324827158664">तुम्ही हे रिक्त सोडू शकत नाही.</translation>
<translation id="4376888869070172068">हे पोस्टल कोड स्वरुपन ओळखले गेले नाही.</translation>
-<translation id="4518701284698680367">आपण एक पिनकोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />. आपला पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
+<translation id="4518701284698680367">तुम्ही एक पिनकोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />. तुमचा पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
<translation id="5089810972385038852">राज्य</translation>
<translation id="5095208057601539847">प्रांत</translation>
<translation id="5327248766486351172">नाव</translation>
@@ -34,14 +34,14 @@
<translation id="7139724024395191329">अमिरात</translation>
<translation id="7393381084163773901">मार्ग पत्ता</translation>
<translation id="7602447984296396718">हा उर्वरित पत्ता जुळविण्यासाठी हा पिन कोड दिसत नाही.</translation>
-<translation id="7738983109397305830">हा पिनकोड या बाकीच्या पत्त्याशी जुळत नाही. आपला पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
+<translation id="7738983109397305830">हा पिनकोड या बाकीच्या पत्त्याशी जुळत नाही. तुमचा पिनकोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
<translation id="777702478322588152">परफेक्चुअर</translation>
<translation id="7805765407568469194">खेडे / उपनगर</translation>
<translation id="8184169487503545976">हे पिन कोड स्वरुपन ओळखले गेले नाही.</translation>
<translation id="820600307078153032">हे पिनकोड स्वरूपन ओळखीचे नाही. वैध पिनकोडचे उदाहरण: <ph name="EXAMPLE" />.</translation>
<translation id="8446364922515257065">Do/Si</translation>
<translation id="8449204988444194299">पोस्ट टाउन</translation>
-<translation id="8471101563037901452">हा पोस्टल कोड या बाकीच्या पत्त्याशी जुळत नाही. आपला पोस्टल कोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
+<translation id="8471101563037901452">हा पोस्टल कोड या बाकीच्या पत्त्याशी जुळत नाही. तुमचा पोस्टल कोड माहीत नाही? तो <ph name="BEGIN_LINK" />येथे<ph name="END_LINK" /> शोधा.</translation>
<translation id="9104066683700680171">हा उर्वरित पत्ता जुळविण्यासाठी हा पोस्टल कोड ‍दिसला नाही.</translation>
-<translation id="9207002871037636573">आपण एक पिनकोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />.</translation>
+<translation id="9207002871037636573">तुम्ही एक पिनकोड प्रदान करणे आवश्यक आहे, उदाहरणार्थ <ph name="EXAMPLE" />.</translation>
</translationbundle> \ No newline at end of file
diff --git a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_sk.xtb b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_sk.xtb
index 5fe20ae6447..5a6d0c5fcc0 100644
--- a/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_sk.xtb
+++ b/chromium/third_party/libaddressinput/chromium/resources/address_input_strings_sk.xtb
@@ -34,10 +34,10 @@
<translation id="7139724024395191329">Emirát</translation>
<translation id="7393381084163773901">Ulica</translation>
<translation id="7602447984296396718">PSČ zrejme nezodpovedá zvyšnej časti adresy.</translation>
-<translation id="7738983109397305830">Toto PSČ zrejme nezodpovedá zvyšku adresy. Nepoznáte svoje PSČ? Zistite ho <ph name="BEGIN_LINK" />tu<ph name="END_LINK" />.</translation>
+<translation id="7738983109397305830">Poštové smerovacie číslo nezodpovedá zvyšku adresy. Nepoznáte svoje poštové smerovacie číslo? <ph name="BEGIN_LINK" />Zistite ho tu<ph name="END_LINK" />.</translation>
<translation id="777702478322588152">Prefektúra</translation>
<translation id="7805765407568469194">Dedina/okres</translation>
-<translation id="8184169487503545976">Tento formát PSČ nebol rozpoznaný.</translation>
+<translation id="8184169487503545976">Formát poštového smerovacieho čísla nebol rozpoznaný.</translation>
<translation id="820600307078153032">Formát tohto PSČ sa nedá rozpoznať. Príklad platného PSČ: <ph name="EXAMPLE" />.</translation>
<translation id="8446364922515257065">Do/Si</translation>
<translation id="8449204988444194299">Poštový okres</translation>
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/android/util/Log.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/android/util/Log.java
new file mode 100644
index 00000000000..71b3e912e72
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/android/util/Log.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ *
+ * 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 android.util;
+
+/**
+ * Simple mock of Android logger class.
+ */
+public final class Log {
+ public static int v(String tag, String msg) { return 0; }
+ public static int v(String tag, String msg, Throwable tr) { return 0; }
+ public static int d(String tag, String msg) { return 0; }
+ public static int d(String tag, String msg, Throwable tr) { return 0; }
+ public static int i(String tag, String msg) { return 0; }
+ public static int i(String tag, String msg, Throwable tr) { return 0; }
+ public static int w(String tag, String msg) { return 0; }
+ public static int w(String tag, String msg, Throwable tr) { return 0; }
+ public static boolean isLoggable(String tag, int level) { return false; }
+ public static int w(String tag, Throwable tr) { return 0; }
+ public static int e(String tag, String msg) { return 0; }
+ public static int e(String tag, String msg, Throwable tr) { return 0; }
+ public static int wtf(String tag, String msg) { return 0; }
+ public static int wtf(String tag, Throwable tr) { return 0; }
+ public static int wtf(String tag, String msg, Throwable tr) { return 0; }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressAutocompleteControllerTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressAutocompleteControllerTest.java
new file mode 100644
index 00000000000..d7898f147aa
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressAutocompleteControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.test.ActivityInstrumentationTestCase2;
+import android.widget.AutoCompleteTextView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.android.i18n.addressinput.AddressAutocompleteController.AddressAdapter;
+import com.android.i18n.addressinput.AddressAutocompleteController.AddressPrediction;
+import com.android.i18n.addressinput.testing.TestActivity;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.i18n.addressinput.common.AddressAutocompleteApi;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.OnAddressSelectedListener;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link AddressAutocompleteController}. */
+public class AddressAutocompleteControllerTest
+ extends ActivityInstrumentationTestCase2<TestActivity> {
+ private static final String TEST_QUERY = "TEST_QUERY";
+
+ private Context context;
+
+ private AddressAutocompleteController controller;
+ private AutoCompleteTextView textView;
+
+ // Mock services
+ private @Mock AddressAutocompleteApi autocompleteApi;
+ private @Mock PlaceDetailsApi placeDetailsApi;
+
+ // Mock data
+ private @Captor ArgumentCaptor<FutureCallback<List<? extends AddressAutocompletePrediction>>>
+ autocompleteCallback;
+ private @Mock AddressAutocompletePrediction autocompletePrediction;
+
+ public AddressAutocompleteControllerTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ context = getActivity();
+
+ textView = new AutoCompleteTextView(context);
+ controller =
+ new AddressAutocompleteController(context, autocompleteApi, placeDetailsApi)
+ .setView(textView);
+ }
+
+ // Tests for the AddressAutocompleteController
+
+ public void testAddressAutocompleteController() throws InterruptedException, ExecutionException {
+ final AddressData expectedAddress = AddressData.builder()
+ .addAddressLine("1600 Amphitheatre Parkway")
+ .setLocality("Mountain View")
+ .setAdminArea("California")
+ .setCountry("US")
+ .build();
+
+ Future<AddressData> actualAddress = getAutocompletePredictions(expectedAddress);
+
+ assertEquals(1, textView.getAdapter().getCount());
+ assertEquals(actualAddress.get(), expectedAddress);
+ }
+
+ // Tests for the AddressAdapter
+
+ public void testAddressAdapter_getItem() {
+ AddressAdapter adapter = new AddressAdapter(context);
+ List<AddressPrediction> predictions =
+ Lists.newArrayList(new AddressPrediction(TEST_QUERY, autocompletePrediction));
+
+ adapter.refresh(predictions);
+ assertEquals(adapter.getCount(), predictions.size());
+ for (int i = 0; i < predictions.size(); i++) {
+ assertEquals("Item #" + i, predictions.get(0), adapter.getItem(0));
+ }
+ }
+
+ public void testAddressAdapter_getView() {
+ AddressAdapter adapter = new AddressAdapter(context);
+ List<AddressPrediction> predictions =
+ Lists.newArrayList(new AddressPrediction(TEST_QUERY, autocompletePrediction));
+
+ adapter.refresh(predictions);
+ for (int i = 0; i < predictions.size(); i++) {
+ assertNotNull("Item #" + i, adapter.getView(0, null, new LinearLayout(context)));
+ }
+ }
+
+ // Helper functions
+
+ private Future<AddressData> getAutocompletePredictions(AddressData expectedAddress) {
+ // Set up the AddressData to be returned from the AddressAutocompleteApi and PlaceDetailsApi.
+ when(autocompleteApi.isConfiguredCorrectly()).thenReturn(true);
+ when(autocompletePrediction.getPlaceId()).thenReturn("TEST_PLACE_ID");
+ when(placeDetailsApi.getAddressData(autocompletePrediction))
+ .thenReturn(Futures.immediateFuture(expectedAddress));
+
+ // Perform a click on the first autocomplete suggestion once it is loaded.
+ textView
+ .getAdapter()
+ .registerDataSetObserver(
+ new DataSetObserver() {
+ @Override
+ public void onInvalidated() {}
+
+ @Override
+ public void onChanged() {
+ // For some reason, performing a click on the view or dropdown view associated with
+ // the first item in the list doesn't trigger the onItemClick listener in tests, so
+ // we trigger it manually here.
+ textView
+ .getOnItemClickListener()
+ .onItemClick(new ListView(context), new TextView(context), 0, 0);
+ }
+ });
+
+ // The OnAddressSelectedListener is the way for the AddressWidget to consume the AddressData
+ // produced by autocompletion.
+ final SettableFuture<AddressData> result = SettableFuture.create();
+ controller.setOnAddressSelectedListener(
+ new OnAddressSelectedListener() {
+ @Override
+ public void onAddressSelected(AddressData address) {
+ result.set(address);
+ }
+ });
+
+ // Actually trigger the behaviors mocked above.
+ textView.setText(TEST_QUERY);
+
+ verify(autocompleteApi)
+ .getAutocompletePredictions(any(String.class), autocompleteCallback.capture());
+ autocompleteCallback.getValue().onSuccess(Lists.newArrayList(autocompletePrediction));
+
+ return result;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressWidgetUiComponentProviderTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressWidgetUiComponentProviderTest.java
new file mode 100644
index 00000000000..5caa764e13e
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AddressWidgetUiComponentProviderTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2014 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import com.android.i18n.addressinput.testing.TestActivity;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.AddressField;
+import com.google.i18n.addressinput.common.AddressProblemType;
+import com.google.i18n.addressinput.common.FormOptions;
+import com.google.i18n.addressinput.common.SimpleClientCacheManager;
+
+/** Test class for {@link AddressWidgetUiComponentProvider}. */
+public class AddressWidgetUiComponentProviderTest
+ extends ActivityInstrumentationTestCase2<TestActivity> {
+ private AddressWidget widget;
+ private AddressWidgetUiComponentProvider componentProvider;
+ private LinearLayout container;
+ private AddressData address;
+ private Context context;
+ private int customTextViewCounter;
+ private int customProgressDialogCounter;
+
+ public AddressWidgetUiComponentProviderTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ AddressData.Builder builder =
+ new AddressData.Builder()
+ .setCountry("US")
+ .setLanguageCode("en")
+ .setAddress("1098 Alta Ave")
+ .setAdminArea("CA");
+ address = builder.build();
+ context = getActivity();
+ container = new LinearLayout(context);
+ }
+
+ public void testCustomWidgets() {
+ customTextViewCounter = 0;
+ customProgressDialogCounter = 0;
+ componentProvider = new TestComponentProvider(context);
+ widget =
+ new AddressWidget(
+ context,
+ container,
+ new FormOptions(),
+ new SimpleClientCacheManager(),
+ componentProvider);
+ widget.renderFormWithSavedAddress(address);
+
+ for (AddressField field : AddressField.values()) {
+ if (field.equals(AddressField.COUNTRY)) {
+ continue;
+ }
+
+ View view = widget.getViewForField(field);
+ if (view instanceof EditText) {
+ assertTrue(
+ "Field " + field + " does not use customized edit text widget.",
+ view instanceof CustomEditText);
+ } else if (view instanceof Spinner) {
+ assertTrue(
+ "Field " + field + " does not use customized spinner widget.",
+ view instanceof CustomSpinner);
+ assertTrue(
+ "Field " + field + " does not use customized ArrayAdapter.",
+ ((Spinner) view).getAdapter() instanceof CustomArrayAdapter);
+ }
+ }
+
+ assertTrue("Custom TextView label not used.", customTextViewCounter > 0);
+ assertTrue("Custom ProgressDialog not used.", customProgressDialogCounter > 0);
+ }
+
+ public void testDisplayAndClearingOfErrorMessages() {
+ customTextViewCounter = 0;
+ customProgressDialogCounter = 0;
+ componentProvider = new TestComponentProvider(context);
+ widget =
+ new AddressWidget(
+ context,
+ container,
+ new FormOptions(),
+ new SimpleClientCacheManager(),
+ componentProvider);
+ widget.renderFormWithSavedAddress(address);
+
+ EditText streetAddressView = (EditText) widget.getViewForField(AddressField.STREET_ADDRESS);
+ EditText addressLine1View = (EditText) widget.getViewForField(AddressField.ADDRESS_LINE_1);
+ EditText localityView = (EditText) widget.getViewForField(AddressField.STREET_ADDRESS);
+ EditText postalCodeView = (EditText) widget.getViewForField(AddressField.POSTAL_CODE);
+
+ // Verify that there are no errors by default.
+ assertNull(streetAddressView.getError());
+ assertNull(addressLine1View.getError()); // Expected to be the same as STREET_ADDRESS
+ assertNull(localityView.getError());
+ assertNull(postalCodeView.getError());
+
+ // Flag STREET_ADDRESS and LOCALITY with errors
+ widget.displayErrorMessageForField(
+ address, AddressField.STREET_ADDRESS, AddressProblemType.MISSING_REQUIRED_FIELD);
+ widget.displayErrorMessageForField(
+ address, AddressField.LOCALITY, AddressProblemType.MISSING_REQUIRED_FIELD);
+
+ // Verify that errors have been applied to the fields above, and other remain unchanged.
+ assertNotNull(streetAddressView.getError());
+ assertNotNull(addressLine1View.getError()); // Expected to be the same as STREET_ADDRESS
+ assertNotNull(localityView.getError());
+
+ // Verify that postal code does not have an error applied.
+ assertNull(postalCodeView.getError());
+
+ // Clear all error messages.
+ widget.clearErrorMessage();
+
+ // Verify that all errors have been cleared.
+ assertNull(streetAddressView.getError());
+ assertNull(addressLine1View.getError()); // Expected to be the same as STREET_ADDRESS
+ assertNull(localityView.getError());
+ assertNull(postalCodeView.getError());
+ }
+
+ private void increaseTextViewCounter() {
+ customTextViewCounter++;
+ }
+
+ private void increaseProgressDialogCounter() {
+ customProgressDialogCounter++;
+ }
+
+ private class CustomEditText extends EditText {
+ CustomEditText(Context context) {
+ super(context);
+ }
+ }
+
+ private class CustomSpinner extends Spinner {
+ CustomSpinner(Context context) {
+ super(context);
+ }
+ }
+
+ private class CustomArrayAdapter extends ArrayAdapter<String> {
+ CustomArrayAdapter(Context context, int id) {
+ super(context, id);
+ }
+ }
+
+ private class TestComponentProvider extends AddressWidgetUiComponentProvider {
+ TestComponentProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected TextView createUiLabel(CharSequence label, AddressField.WidthType widthType) {
+ TextView result = new TextView(context);
+ result.setText(label);
+ AddressWidgetUiComponentProviderTest.this.increaseTextViewCounter();
+ return result;
+ }
+
+ @Override
+ protected EditText createUiTextField(AddressField.WidthType widthType) {
+ return new CustomEditText(context);
+ }
+
+ @Override
+ protected Spinner createUiPickerSpinner(AddressField.WidthType widthType) {
+ return new CustomSpinner(context);
+ }
+
+ @Override
+ protected ArrayAdapter<String> createUiPickerAdapter(AddressField.WidthType widthType) {
+ ArrayAdapter<String> result =
+ new CustomArrayAdapter(context, android.R.layout.simple_spinner_item);
+ result.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ return result;
+ }
+
+ @Override
+ protected ProgressDialog getUiActivityIndicatorView() {
+ AddressWidgetUiComponentProviderTest.this.increaseProgressDialogCounter();
+ return super.getUiActivityIndicatorView();
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApiTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApiTest.java
new file mode 100644
index 00000000000..7b558b124f6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApiTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.android.i18n.addressinput.testing.AsyncTestCase;
+import com.google.i18n.addressinput.common.AsyncRequestApi;
+import com.google.i18n.addressinput.common.AsyncRequestApi.AsyncCallback;
+import com.google.i18n.addressinput.common.JsoMap;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class AndroidAsyncEncodedRequestApiTest extends AsyncTestCase {
+ private AsyncRequestApi requestApi;
+
+ @Override
+ public void setUp() {
+ requestApi = new AndroidAsyncEncodedRequestApi();
+ }
+
+ public void testRequestObject() throws Exception {
+ delayTestFinish(4000);
+
+ String url = HttpServer.execute(1000, "{\"id\": \"data\"}");
+
+ requestApi.requestObject(url, new AsyncCallback() {
+ @Override public void onFailure() {
+ fail("unexpected failure");
+ }
+
+ @Override public void onSuccess(JsoMap result) {
+ assertNotNull(result);
+ assertEquals("data", result.get("id"));
+ finishTest();
+ }
+ }, 2000);
+ }
+
+ public void testTimeout() throws Exception {
+ delayTestFinish(4000);
+
+ String url = HttpServer.execute(2000, "Fubar");
+
+ requestApi.requestObject(url, new AsyncCallback() {
+ @Override public void onFailure() {
+ finishTest();
+ }
+
+ @Override public void onSuccess(JsoMap result) {
+ fail("The request should have timed out.");
+ }
+ }, 1000);
+ }
+
+ public void testUrlEncoding() throws Exception {
+ delayTestFinish(4000);
+
+ String urlBase = HttpServer.execute(1000, "{\"id\": \"data\"}");
+ String url = urlBase + "address/data/VN/B\u1EAFc K\u1EA1n";
+
+ requestApi.requestObject(url, new AsyncCallback() {
+ @Override public void onFailure() {
+ fail("unexpected failure");
+ }
+
+ @Override public void onSuccess(JsoMap result) {
+ assertNotNull(result);
+ assertEquals("data", result.get("id"));
+ finishTest();
+ }
+ }, 2000);
+ }
+
+ /**
+ * Simple implementation of an HTTP server.
+ */
+ private static class HttpServer extends Thread {
+ /**
+ * Start an HTTP server that will serve one request and then terminate.
+ *
+ * @param timeoutMillis
+ * Wait this long before answering a request.
+ * @param response
+ * Reply to any request with this response.
+ * @return The URL to the server.
+ * @throws IOException
+ */
+ public static String execute(long timeoutMillis, String response) throws IOException {
+ HttpServer server = new HttpServer(timeoutMillis, response);
+ server.start();
+ return "http://localhost:" + server.serverSocket.getLocalPort() + "/";
+ }
+
+ @Override
+ public void run() {
+ try {
+ Socket clientSocket = serverSocket.accept();
+ try {
+ synchronized (this) {
+ wait(waitMillis);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ InputStream inputStream = clientSocket.getInputStream();
+ inputStream.read(new byte[1024]); // Discard input.
+ OutputStream outputStream = clientSocket.getOutputStream();
+ outputStream.write(response);
+ outputStream.close();
+ inputStream.close();
+ clientSocket.close();
+ serverSocket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HttpServer(long waitMillis, String response) throws IOException {
+ this.waitMillis = waitMillis;
+ this.response = (HEADER + response).getBytes();
+ serverSocket = new ServerSocket(0);
+ }
+
+ private long waitMillis;
+ private byte[] response;
+ private ServerSocket serverSocket;
+
+ private static final String HEADER = "HTTP/1.0 200 OK\nContent-Type: text/plain\n\n";
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AsyncTestCaseTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AsyncTestCaseTest.java
new file mode 100644
index 00000000000..36833b8915a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/AsyncTestCaseTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.android.i18n.addressinput.testing.AsyncTestCase;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.concurrent.TimeoutException;
+
+public class AsyncTestCaseTest extends AsyncTestCase {
+
+ public void testSuccess() {
+ delayTestFinish(1000);
+ AsyncCallback.execute(500, new Runnable() {
+ @Override
+ public void run() {
+ finishTest();
+ }
+ });
+ }
+
+ public void testFailure() {
+ expectTimeout = true;
+ delayTestFinish(1000);
+ AsyncCallback.execute(1500, new Runnable() {
+ @Override
+ public void run() {
+ finishTest();
+ }
+ });
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ expectTimeout = false;
+ try {
+ super.runTest();
+ } catch (TimeoutException e) {
+ if (expectTimeout) {
+ return;
+ } else {
+ throw e;
+ }
+ }
+ if (expectTimeout) {
+ throw new AssertionFailedError("Test case did not time out.");
+ }
+ }
+
+ private boolean expectTimeout;
+
+ /**
+ * Helper class to perform an asynchronous callback after a specified delay.
+ */
+ private static class AsyncCallback extends Thread {
+ private long waitMillis;
+ private Runnable callback;
+
+ private AsyncCallback(long waitMillis, Runnable callback) {
+ this.waitMillis = waitMillis;
+ this.callback = callback;
+ }
+
+ public static void execute(long waitMillis, Runnable callback) {
+ (new AsyncCallback(waitMillis, callback)).start();
+ }
+
+ @Override
+ public void run() {
+ try {
+ synchronized (this) {
+ wait(this.waitMillis);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ this.callback.run();
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/DummyTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/DummyTest.java
new file mode 100644
index 00000000000..f190998c84e
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/DummyTest.java
@@ -0,0 +1,15 @@
+package com.android.i18n.addressinput;
+
+import android.test.ActivityInstrumentationTestCase2;
+import com.android.i18n.addressinput.testing.TestActivity;
+
+/**
+ * Empty test file. The API level 19 test emulator requires a nonempty dex to run tests.
+ * This empty file is included in the srcs attribute of the android_test rule so that the real test
+ * source files can be included via the binary_under_test attribute.
+ */
+public class DummyTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ public DummyTest() {
+ super(TestActivity.class);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/PlaceDetailsClientTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/PlaceDetailsClientTest.java
new file mode 100644
index 00000000000..23f40eec8d7
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/PlaceDetailsClientTest.java
@@ -0,0 +1,87 @@
+package com.android.i18n.addressinput;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.test.ActivityInstrumentationTestCase2;
+import com.android.i18n.addressinput.testing.TestActivity;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.AsyncRequestApi;
+import com.google.i18n.addressinput.common.AsyncRequestApi.AsyncCallback;
+import com.google.i18n.addressinput.common.JsoMap;
+import java.util.concurrent.ExecutionException;
+import org.json.JSONException;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link PlaceDetailsClient}. */
+public class PlaceDetailsClientTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ @Mock private AsyncRequestApi asyncRequestApi;
+ @Mock private AddressAutocompletePrediction autocompletePrediction;
+
+ @Captor ArgumentCaptor<AsyncCallback> callbackCaptor;
+
+ private PlaceDetailsClient placeDetailsClient;
+
+ public PlaceDetailsClientTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ placeDetailsClient = new PlaceDetailsClient("TEST_API_KEY", asyncRequestApi);
+ when(autocompletePrediction.getPlaceId()).thenReturn("TEST_PLACE_ID");
+ }
+
+ public void testOnSuccess() throws InterruptedException, ExecutionException, JSONException {
+ ListenableFuture<AddressData> addressData =
+ placeDetailsClient.getAddressData(autocompletePrediction);
+
+ verify(asyncRequestApi)
+ .requestObject(any(String.class), callbackCaptor.capture(), eq(PlaceDetailsClient.TIMEOUT));
+ callbackCaptor.getValue().onSuccess(JsoMap.buildJsoMap(TEST_RESPONSE));
+
+ assertEquals(
+ AddressData.builder()
+ .setAddress("1600 Amphitheatre Parkway")
+ .setLocality("Mountain View")
+ .setAdminArea("CA")
+ .setCountry("US")
+ .setPostalCode("94043")
+ .build(),
+ addressData.get());
+ }
+
+ public void testOnFailure() {
+ ListenableFuture<AddressData> addressData =
+ placeDetailsClient.getAddressData(autocompletePrediction);
+
+ verify(asyncRequestApi)
+ .requestObject(any(String.class), callbackCaptor.capture(), eq(PlaceDetailsClient.TIMEOUT));
+ callbackCaptor.getValue().onFailure();
+
+ assertTrue(addressData.isCancelled());
+ }
+
+ private static final String TEST_RESPONSE =
+ "{"
+ + " 'result' : {"
+ + " 'adr_address' : '\\u003cspan class=\\\"street-address\\\"\\u003e1600 Amphitheatre Parkway\\u003c/span\\u003e, \\u003cspan class=\\\"locality\\\"\\u003eMountain View\\u003c/span\\u003e, \\u003cspan class=\\\"region\\\"\\u003eCA\\u003c/span\\u003e \\u003cspan class=\\\"postal-code\\\"\\u003e94043\\u003c/span\\u003e, \\u003cspan class=\\\"country-name\\\"\\u003eUSA\\u003c/span\\u003e',"
+ + " 'address_components' : ["
+ + " {"
+ + " 'long_name' : 'United States',"
+ + " 'short_name' : 'US',"
+ + " 'types' : [ 'country', 'political' ]"
+ + " }"
+ + " ]"
+ + " }"
+ + "}";
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImplTest.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImplTest.java
new file mode 100644
index 00000000000..f0495bf6c69
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImplTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.i18n.addressinput.autocomplete.gmscore;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.location.Location;
+import android.test.ActivityInstrumentationTestCase2;
+import com.android.i18n.addressinput.testing.TestActivity;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.PendingResults;
+import com.google.android.gms.location.FusedLocationProviderApi;
+import com.google.android.gms.location.places.AutocompleteFilter;
+import com.google.android.gms.location.places.AutocompletePrediction;
+import com.google.android.gms.location.places.AutocompletePredictionBuffer;
+import com.google.android.gms.location.places.GeoDataApi;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.i18n.addressinput.common.AddressAutocompleteApi;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/** Unit tests for {@link AddressAutocompleteApi}. */
+public class AddressAutocompleteApiImplTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ private static final String TAG = "AddrAutoApiTest";
+ private static final String TEST_QUERY = "TEST_QUERY";
+
+ private AddressAutocompleteApi addressAutocompleteApi;
+
+ // Mock services
+ private GeoDataApi geoDataApi = mock(GeoDataApi.class);
+ private GoogleApiClient googleApiClient = mock(GoogleApiClient.class);
+ private FusedLocationProviderApi locationApi = mock(FusedLocationProviderApi.class);
+
+ // Mock data
+ private AutocompletePredictionBuffer autocompleteResults =
+ mock(AutocompletePredictionBuffer.class);
+ private PendingResult<AutocompletePredictionBuffer> autocompletePendingResults =
+ PendingResults.immediatePendingResult(autocompleteResults);
+ private AutocompletePrediction autocompletePrediction = mock(AutocompletePrediction.class);
+
+ public AddressAutocompleteApiImplTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() {
+ addressAutocompleteApi =
+ new AddressAutocompleteApiImpl(googleApiClient, geoDataApi, locationApi);
+ }
+
+ // Tests for the AddressAutocompleteApi
+
+ public void testAddressAutocompleteApi() throws InterruptedException, ExecutionException {
+ when(googleApiClient.isConnected()).thenReturn(true);
+ when(locationApi.getLastLocation(googleApiClient)).thenReturn(new Location("TEST_PROVIDER"));
+
+ Future<List<? extends AddressAutocompletePrediction>> actualPredictions =
+ getAutocompleteSuggestions();
+
+ List<AddressAutocompletePrediction> expectedPredictions =
+ Lists.newArrayList(new AddressAutocompletePredictionImpl(autocompletePrediction));
+
+ assertEquals(actualPredictions.get(), expectedPredictions);
+ }
+
+ public void testAddressAutocompleteApi_deviceLocationMissing()
+ throws InterruptedException, ExecutionException {
+ when(googleApiClient.isConnected()).thenReturn(true);
+ when(locationApi.getLastLocation(googleApiClient)).thenReturn(null);
+
+ Future<List<? extends AddressAutocompletePrediction>> actualPredictions =
+ getAutocompleteSuggestions();
+
+ List<AddressAutocompletePrediction> expectedPredictions =
+ Lists.newArrayList(new AddressAutocompletePredictionImpl(autocompletePrediction));
+
+ assertEquals(actualPredictions.get(), expectedPredictions);
+ }
+
+ public void testAddressAutocompleteApi_isConfiguredCorrectly() {
+ when(googleApiClient.isConnected()).thenReturn(true);
+ assertTrue(addressAutocompleteApi.isConfiguredCorrectly());
+ }
+
+ // Helper functions
+
+ private Future<List<? extends AddressAutocompletePrediction>> getAutocompleteSuggestions() {
+ // Set up the AddressData to be returned from the PlaceAutocomplete API + PlaceDetailsApi.
+ // Most of the objects that are mocked here are not services, but simply data without any
+ // public constructors.
+ when(geoDataApi.getAutocompletePredictions(
+ eq(googleApiClient),
+ eq(TEST_QUERY),
+ any(LatLngBounds.class),
+ any(AutocompleteFilter.class)))
+ .thenReturn(autocompletePendingResults);
+ when(autocompletePrediction.getPlaceId()).thenReturn("TEST_PLACE_ID");
+ when(autocompletePrediction.getFullText(null)).thenReturn("TEST_PREDICTION");
+ when(autocompleteResults.iterator())
+ .thenReturn(Arrays.asList(autocompletePrediction).iterator());
+ when(autocompletePrediction.getPlaceId()).thenReturn("TEST_PLACE_ID");
+ when(autocompletePrediction.getPrimaryText(null)).thenReturn("TEST_PRIMARY_ID");
+ when(autocompletePrediction.getSecondaryText(null)).thenReturn("TEST_SECONDARY_ID");
+
+ SettableFuture<List<? extends AddressAutocompletePrediction>> actualPredictions =
+ SettableFuture.create();
+ addressAutocompleteApi.getAutocompletePredictions(
+ TEST_QUERY,
+ new FutureCallback<List<? extends AddressAutocompletePrediction>>() {
+ @Override
+ public void onSuccess(List<? extends AddressAutocompletePrediction> predictions) {
+ actualPredictions.set(predictions);
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ assertTrue("Error getting autocomplete predictions: " + error.toString(), false);
+ }
+ });
+
+ return actualPredictions;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/AsyncTestCase.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/AsyncTestCase.java
new file mode 100644
index 00000000000..d8a5a4304a5
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/AsyncTestCase.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.i18n.addressinput.testing;
+
+import junit.framework.TestCase;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * An extension of TestCase that provides delayTestFinish() and finishTest() methods that behave
+ * like the corresponding methods in GWTTestCase for testing asynchronous code.
+ */
+public abstract class AsyncTestCase extends TestCase {
+ /**
+ * Tracks whether this test is completely done.
+ */
+ private boolean testIsFinished;
+
+ /**
+ * The system time in milliseconds when the test should time out.
+ */
+ private long testTimeoutMillis;
+
+ /**
+ * Puts the current test in asynchronous mode.
+ *
+ * @param timeoutMillis time to wait before failing the test for timing out
+ */
+ protected void delayTestFinish(int timeoutMillis) {
+ testTimeoutMillis = System.currentTimeMillis() + timeoutMillis;
+ }
+
+ /**
+ * Causes this test to succeed during asynchronous mode.
+ */
+ protected void finishTest() {
+ testIsFinished = true;
+ synchronized (this) {
+ notify();
+ }
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ testIsFinished = false;
+ testTimeoutMillis = 0;
+ super.runTest();
+
+ if (testTimeoutMillis > 0) {
+ long timeoutMillis = testTimeoutMillis - System.currentTimeMillis();
+ if (timeoutMillis > 0) {
+ synchronized (this) {
+ wait(timeoutMillis);
+ }
+ }
+ if (!testIsFinished) {
+ throw new TimeoutException("Waited " + timeoutMillis + " ms!");
+ }
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/TestActivity.java b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/TestActivity.java
new file mode 100644
index 00000000000..4e6779ef7f6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/androidTest/java/com/android/i18n/addressinput/testing/TestActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 Google Inc.
+ *
+ * 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.i18n.addressinput.testing;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressAutocompleteController.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressAutocompleteController.java
new file mode 100644
index 00000000000..c71eb4e5d77
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressAutocompleteController.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AutoCompleteTextView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.i18n.addressinput.common.AddressAutocompleteApi;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.OnAddressSelectedListener;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Controller for address autocomplete results. */
+class AddressAutocompleteController {
+
+ private static final String TAG = "AddressAutocompleteCtrl";
+
+ private AddressAutocompleteApi autocompleteApi;
+ private PlaceDetailsApi placeDetailsApi;
+ private AddressAdapter adapter;
+ private OnAddressSelectedListener listener;
+
+ private TextWatcher textChangedListener =
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int after) {
+ getAddressPredictions(s.toString());
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ };
+
+ private AdapterView.OnItemClickListener onItemClickListener =
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (listener != null) {
+ AddressAutocompletePrediction prediction =
+ (AddressAutocompletePrediction)
+ adapter.getItem(position).getAutocompletePrediction();
+
+ (new AsyncTask<AddressAutocompletePrediction, Void, AddressData>() {
+ @Override
+ protected AddressData doInBackground(
+ AddressAutocompletePrediction... predictions) {
+ try {
+ return placeDetailsApi.getAddressData(predictions[0]).get();
+ } catch (Exception e) {
+ cancel(true);
+ Log.i(TAG, "Error getting place details: ", e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(AddressData addressData) {
+ Log.e(TAG, "AddressData: " + addressData.toString());
+ listener.onAddressSelected(addressData);
+ }
+ })
+ .execute(prediction);
+ } else {
+ Log.i(TAG, "No onAddressSelected listener.");
+ }
+ }
+ };
+
+ AddressAutocompleteController(
+ Context context, AddressAutocompleteApi autocompleteApi, PlaceDetailsApi placeDetailsApi) {
+ this.placeDetailsApi = placeDetailsApi;
+ this.autocompleteApi = autocompleteApi;
+
+ adapter = new AddressAdapter(context);
+ }
+
+ AddressAutocompleteController setView(AutoCompleteTextView textView) {
+ textView.setAdapter(adapter);
+ textView.setOnItemClickListener(onItemClickListener);
+ textView.addTextChangedListener(textChangedListener);
+
+ return this;
+ }
+
+ AddressAutocompleteController setOnAddressSelectedListener(OnAddressSelectedListener listener) {
+ this.listener = listener;
+ return this;
+ }
+
+ void getAddressPredictions(final String query) {
+ if (!autocompleteApi.isConfiguredCorrectly()) {
+ return;
+ }
+
+ autocompleteApi.getAutocompletePredictions(
+ query,
+ new FutureCallback<List<? extends AddressAutocompletePrediction>>() {
+ @Override
+ public void onSuccess(List<? extends AddressAutocompletePrediction> predictions) {
+ List<AddressPrediction> wrappedPredictions = new ArrayList<>();
+
+ for (AddressAutocompletePrediction prediction : predictions) {
+ wrappedPredictions.add(new AddressPrediction(query, prediction));
+ }
+
+ adapter.refresh(wrappedPredictions);
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ Log.i(TAG, "Error getting autocomplete predictions: ", error);
+ }
+ });
+ }
+
+ @VisibleForTesting
+ static class AddressPrediction {
+ private String prefix;
+ private AddressAutocompletePrediction autocompletePrediction;
+
+ AddressPrediction(String prefix, AddressAutocompletePrediction prediction) {
+ this.prefix = prefix;
+ this.autocompletePrediction = prediction;
+ }
+
+ String getPrefix() {
+ return prefix;
+ };
+
+ AddressAutocompletePrediction getAutocompletePrediction() {
+ return autocompletePrediction;
+ };
+
+ @Override
+ public final String toString() {
+ return getPrefix();
+ }
+ }
+
+ // The main purpose of this custom adapter is the custom getView function.
+ // This adapter extends BaseAdapter instead of ArrayAdapter because ArrayAdapter has a filtering
+ // bug that is triggered by the AutoCompleteTextView (see
+ // http://www.jaysoyer.com/2014/07/filtering-problems-arrayadapter/).
+ @VisibleForTesting
+ static class AddressAdapter extends BaseAdapter implements Filterable {
+ private Context context;
+
+ private List<AddressPrediction> predictions;
+
+ AddressAdapter(Context context) {
+ this.context = context;
+ this.predictions = new ArrayList<AddressPrediction>();
+ }
+
+ public AddressAdapter refresh(List<AddressPrediction> newPredictions) {
+ predictions = newPredictions;
+ notifyDataSetChanged();
+
+ return this;
+ }
+
+ @Override
+ public int getCount() {
+ return predictions.size();
+ }
+
+ @Override
+ public AddressPrediction getItem(int position) {
+ return predictions.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ // No-op filter.
+ // Results from the PlaceAutocomplete API don't need to be filtered any further.
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ public Filter.FilterResults performFiltering(CharSequence constraint) {
+ Filter.FilterResults results = new Filter.FilterResults();
+ results.count = predictions.size();
+ results.values = predictions;
+
+ return results;
+ }
+
+ @Override
+ public void publishResults(CharSequence constraint, Filter.FilterResults results) {}
+ };
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LinearLayout view =
+ convertView instanceof LinearLayout
+ ? (LinearLayout) convertView
+ : (LinearLayout)
+ inflater.inflate(R.layout.address_autocomplete_dropdown_item, parent, false);
+ AddressPrediction prediction = predictions.get(position);
+
+ TextView line1 = (TextView) view.findViewById(R.id.line_1);
+ if (line1 != null) {
+ line1.setText(prediction.getAutocompletePrediction().getPrimaryText());
+ }
+
+ TextView line2 = (TextView) view.findViewById(R.id.line_2);
+ line2.setText(prediction.getAutocompletePrediction().getSecondaryText());
+
+ return view;
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressUiComponent.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressUiComponent.java
new file mode 100644
index 00000000000..c1d11a4775b
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressUiComponent.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.google.i18n.addressinput.common.AddressField;
+import com.google.i18n.addressinput.common.RegionData;
+
+import android.view.View;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a component in the address widget UI. It could be either a text box (when there is no
+ * candidate) or a spinner.
+ */
+class AddressUiComponent {
+ // The label for the UI component
+ private String fieldName;
+
+ // The type of the UI component
+ private UiComponent uiType;
+
+ // The list of elements in the UI component
+ private List<RegionData> candidatesList = new ArrayList<RegionData>();
+
+ // The id of this UI component
+ private AddressField id;
+
+ // The id of the parent UI component. When the parent UI component is updated, this UI
+ // component should be updated.
+ private AddressField parentId;
+
+ // The View representing the UI component
+ private View view;
+
+ /**
+ * Type of UI component. There are only EDIT (text-box) and SPINNER (drop-down) components.
+ */
+ enum UiComponent {
+ EDIT, SPINNER,
+ }
+
+ AddressUiComponent(AddressField id) {
+ this.id = id;
+ // By default, an AddressUiComponent doesn't depend on anything else.
+ this.parentId = null;
+ this.uiType = UiComponent.EDIT;
+ }
+
+ /**
+ * Initializes the candidatesList, and set the uiType and parentId.
+ * @param candidatesList
+ */
+ void initializeCandidatesList(List<RegionData> candidatesList) {
+ this.candidatesList = candidatesList;
+ if (candidatesList.size() > 1) {
+ uiType = UiComponent.SPINNER;
+ switch (id) {
+ case DEPENDENT_LOCALITY:
+ parentId = AddressField.LOCALITY;
+ break;
+ case LOCALITY:
+ parentId = AddressField.ADMIN_AREA;
+ break;
+ case ADMIN_AREA:
+ parentId = AddressField.COUNTRY;
+ break;
+ default:
+ // Ignore.
+ }
+ }
+ }
+
+ /**
+ * Gets the value entered in the UI component.
+ */
+ String getValue() {
+ if (view == null) {
+ return (candidatesList.size() == 0) ? "" : candidatesList.get(0).getDisplayName();
+ }
+ switch (uiType) {
+ case SPINNER:
+ Object selectedItem = ((Spinner) view).getSelectedItem();
+ if (selectedItem == null) {
+ return "";
+ }
+ return selectedItem.toString();
+ case EDIT:
+ return ((EditText) view).getText().toString();
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Sets the value displayed in the input field.
+ */
+ void setValue(String value) {
+ if (view == null) {
+ return;
+ }
+
+ switch(uiType) {
+ case SPINNER:
+ for (int i = 0; i < candidatesList.size(); i++) {
+ // Assumes that the indices in the candidate list are the same as those used in the
+ // Adapter backing the Spinner.
+ if (candidatesList.get(i).getKey().equals(value)) {
+ ((Spinner) view).setSelection(i);
+ }
+ }
+ return;
+ case EDIT:
+ if (view instanceof AutoCompleteTextView) {
+ // Prevent the AutoCompleteTextView from showing the dropdown.
+ ((AutoCompleteTextView) view).setText(value, false);
+ } else {
+ ((EditText) view).setText(value);
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ String getFieldName() {
+ return fieldName;
+ }
+
+ void setFieldName(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ UiComponent getUiType() {
+ return uiType;
+ }
+
+ void setUiType(UiComponent uiType) {
+ this.uiType = uiType;
+ }
+
+ List<RegionData> getCandidatesList() {
+ return candidatesList;
+ }
+
+ void setCandidatesList(List<RegionData> candidatesList) {
+ this.candidatesList = candidatesList;
+ }
+
+ AddressField getId() {
+ return id;
+ }
+
+ void setId(AddressField id) {
+ this.id = id;
+ }
+
+ AddressField getParentId() {
+ return parentId;
+ }
+
+ void setParentId(AddressField parentId) {
+ this.parentId = parentId;
+ }
+
+ void setView(View view) {
+ this.view = view;
+ }
+
+ View getView() {
+ return view;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidget.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidget.java
new file mode 100644
index 00000000000..0ce8c826301
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidget.java
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.Handler;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.Spinner;
+import android.widget.TextView;
+import com.android.i18n.addressinput.AddressUiComponent.UiComponent;
+import com.google.i18n.addressinput.common.AddressAutocompleteApi;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.AddressDataKey;
+import com.google.i18n.addressinput.common.AddressField;
+import com.google.i18n.addressinput.common.AddressField.WidthType;
+import com.google.i18n.addressinput.common.AddressProblemType;
+import com.google.i18n.addressinput.common.AddressProblems;
+import com.google.i18n.addressinput.common.AddressVerificationNodeData;
+import com.google.i18n.addressinput.common.CacheData;
+import com.google.i18n.addressinput.common.ClientCacheManager;
+import com.google.i18n.addressinput.common.ClientData;
+import com.google.i18n.addressinput.common.DataLoadListener;
+import com.google.i18n.addressinput.common.FieldVerifier;
+import com.google.i18n.addressinput.common.FormController;
+import com.google.i18n.addressinput.common.FormOptions;
+import com.google.i18n.addressinput.common.FormatInterpreter;
+import com.google.i18n.addressinput.common.LookupKey;
+import com.google.i18n.addressinput.common.LookupKey.KeyType;
+import com.google.i18n.addressinput.common.LookupKey.ScriptType;
+import com.google.i18n.addressinput.common.OnAddressSelectedListener;
+import com.google.i18n.addressinput.common.RegionData;
+import com.google.i18n.addressinput.common.StandardAddressVerifier;
+import com.google.i18n.addressinput.common.Util;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Address widget that lays out address fields, validate and format addresses according to local
+ * customs.
+ */
+public class AddressWidget implements AdapterView.OnItemSelectedListener {
+
+ private Context context;
+
+ private ViewGroup rootView;
+
+ private CacheData cacheData;
+
+ private ClientData clientData;
+
+ // A map for all address fields.
+ // TODO(dbeaumont): Fix this to avoid needing to map specific address lines.
+ private final EnumMap<AddressField, AddressUiComponent> inputWidgets =
+ new EnumMap<AddressField, AddressUiComponent>(AddressField.class);
+
+ private FormController formController;
+
+ private FormatInterpreter formatInterpreter;
+
+ private FormOptions.Snapshot formOptions;
+
+ private StandardAddressVerifier verifier;
+
+ private ProgressDialog progressDialog;
+
+ private String currentRegion;
+
+ private boolean autocompleteEnabled = false;
+
+ private AddressAutocompleteController autocompleteController;
+
+ // The current language the widget uses in BCP47 format. It differs from the default locale of
+ // the phone in that it contains information on the script to use.
+ private String widgetLocale;
+
+ private ScriptType script;
+
+ // Possible labels that could be applied to the admin area field of the current country.
+ // Examples include "state", "province", "emirate", etc.
+ private static final Map<String, Integer> ADMIN_LABELS;
+ // Possible labels that could be applied to the locality (city) field of the current country.
+ // Examples include "city" or "district".
+ private static final Map<String, Integer> LOCALITY_LABELS;
+ // Possible labels that could be applied to the sublocality field of the current country.
+ // Examples include "suburb" or "neighborhood".
+ private static final Map<String, Integer> SUBLOCALITY_LABELS;
+
+ private static final FormOptions.Snapshot SHOW_ALL_FIELDS = new FormOptions().createSnapshot();
+
+ // The appropriate label that should be applied to the zip code field of the current country.
+ private enum ZipLabel {
+ ZIP,
+ POSTAL,
+ PIN,
+ EIRCODE
+ }
+
+ private ZipLabel zipLabel;
+
+ static {
+ Map<String, Integer> adminLabelMap = new HashMap<String, Integer>(15);
+ adminLabelMap.put("area", R.string.i18n_area);
+ adminLabelMap.put("county", R.string.i18n_county);
+ adminLabelMap.put("department", R.string.i18n_department);
+ adminLabelMap.put("district", R.string.i18n_district);
+ adminLabelMap.put("do_si", R.string.i18n_do_si);
+ adminLabelMap.put("emirate", R.string.i18n_emirate);
+ adminLabelMap.put("island", R.string.i18n_island);
+ adminLabelMap.put("oblast", R.string.i18n_oblast);
+ adminLabelMap.put("parish", R.string.i18n_parish);
+ adminLabelMap.put("prefecture", R.string.i18n_prefecture);
+ adminLabelMap.put("province", R.string.i18n_province);
+ adminLabelMap.put("state", R.string.i18n_state);
+ ADMIN_LABELS = Collections.unmodifiableMap(adminLabelMap);
+
+ Map<String, Integer> localityLabelMap = new HashMap<String, Integer>(2);
+ localityLabelMap.put("city", R.string.i18n_locality_label);
+ localityLabelMap.put("district", R.string.i18n_district);
+ localityLabelMap.put("post_town", R.string.i18n_post_town);
+ localityLabelMap.put("suburb", R.string.i18n_suburb);
+ LOCALITY_LABELS = Collections.unmodifiableMap(localityLabelMap);
+
+ Map<String, Integer> sublocalityLabelMap = new HashMap<String, Integer>(2);
+ sublocalityLabelMap.put("suburb", R.string.i18n_suburb);
+ sublocalityLabelMap.put("district", R.string.i18n_district);
+ sublocalityLabelMap.put("neighborhood", R.string.i18n_neighborhood);
+ sublocalityLabelMap.put("village_township", R.string.i18n_village_township);
+ sublocalityLabelMap.put("townland", R.string.i18n_townland);
+ SUBLOCALITY_LABELS = Collections.unmodifiableMap(sublocalityLabelMap);
+ }
+
+ // Need handler for callbacks to the UI thread
+ final Handler handler = new Handler();
+
+ final Runnable updateMultipleFields =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateFields();
+ }
+ };
+
+ private class UpdateRunnable implements Runnable {
+ private AddressField myId;
+
+ public UpdateRunnable(AddressField id) {
+ myId = id;
+ }
+
+ @Override
+ public void run() {
+ updateInputWidget(myId);
+ }
+ }
+
+ private static class AddressSpinnerInfo {
+ private Spinner view;
+
+ private AddressField id;
+
+ private AddressField parentId;
+
+ private ArrayAdapter<String> adapter;
+
+ private List<RegionData> currentRegions;
+
+ @SuppressWarnings("unchecked")
+ public AddressSpinnerInfo(Spinner view, AddressField id, AddressField parentId) {
+ this.view = view;
+ this.id = id;
+ this.parentId = parentId;
+ this.adapter = (ArrayAdapter<String>) view.getAdapter();
+ }
+
+ public void setSpinnerList(List<RegionData> list, String defaultKey) {
+ currentRegions = list;
+ adapter.clear();
+ for (RegionData item : list) {
+ adapter.add(item.getDisplayName());
+ }
+ adapter.sort(Collator.getInstance(Locale.getDefault()));
+ if (defaultKey.length() == 0) {
+ view.setSelection(0);
+ } else {
+ int position = adapter.getPosition(defaultKey);
+ view.setSelection(position);
+ }
+ }
+
+ // Returns the region key of the currently selected region in the Spinner.
+ public String getRegionCode(int position) {
+ if (adapter.getCount() <= position) {
+ return "";
+ }
+ String value = adapter.getItem(position);
+ return getRegionDataKeyForValue(value);
+ }
+
+ // Returns the region key for the region value.
+ public String getRegionDataKeyForValue(String value) {
+ for (RegionData data : currentRegions) {
+ if (data.getDisplayName().equals(value)) {
+ return data.getKey();
+ }
+ }
+ for (RegionData data : currentRegions) {
+ if (data.getDisplayName().endsWith(value)) {
+ return data.getKey();
+ }
+ }
+ return "";
+ }
+ }
+
+ private final ArrayList<AddressSpinnerInfo> spinners = new ArrayList<AddressSpinnerInfo>();
+
+ private AddressWidgetUiComponentProvider componentProvider;
+
+ private WidthType getFieldWidthType(AddressUiComponent field) {
+ // TODO(user): For drop-downs (spinners), derive the width-type from the list of values.
+ return field.getId().getWidthTypeForRegion(currentRegion);
+ }
+
+ private void createView(
+ ViewGroup rootView, AddressUiComponent field, String defaultKey, boolean readOnly) {
+ @SuppressWarnings("deprecation") // FILL_PARENT renamed MATCH_PARENT in API Level 8.
+ LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+ String fieldText = field.getFieldName();
+ WidthType widthType = getFieldWidthType(field);
+
+ if (fieldText.length() > 0) {
+ TextView textView = componentProvider.createUiLabel(fieldText, widthType);
+ rootView.addView(textView, lp);
+ }
+ if (field.getUiType().equals(UiComponent.EDIT)) {
+ if (autocompleteEnabled && field.getId() == AddressField.ADDRESS_LINE_1) {
+ AutoCompleteTextView autocomplete =
+ componentProvider.createUiAutoCompleteTextField(widthType);
+ autocomplete.setEnabled(!readOnly);
+ autocompleteController.setView(autocomplete);
+ autocompleteController.setOnAddressSelectedListener(
+ new OnAddressSelectedListener() {
+ @Override
+ public void onAddressSelected(AddressData addressData) {
+ // Autocompletion will never return the recipient or the organization, so we don't
+ // want to overwrite those fields. We copy the recipient and organization fields
+ // over to avoid this.
+ AddressData current = AddressWidget.this.getAddressData();
+ AddressWidget.this.renderFormWithSavedAddress(
+ AddressData.builder(addressData)
+ .setRecipient(current.getRecipient())
+ .setOrganization(current.getOrganization())
+ .build());
+ }
+ });
+ field.setView(autocomplete);
+ rootView.addView(autocomplete, lp);
+ } else {
+ EditText editText = componentProvider.createUiTextField(widthType);
+ field.setView(editText);
+ editText.setEnabled(!readOnly);
+ rootView.addView(editText, lp);
+ }
+ } else if (field.getUiType().equals(UiComponent.SPINNER)) {
+ ArrayAdapter<String> adapter = componentProvider.createUiPickerAdapter(widthType);
+ Spinner spinner = componentProvider.createUiPickerSpinner(widthType);
+
+ field.setView(spinner);
+ spinner.setEnabled(!readOnly);
+ rootView.addView(spinner, lp);
+ spinner.setAdapter(adapter);
+ AddressSpinnerInfo spinnerInfo =
+ new AddressSpinnerInfo(spinner, field.getId(), field.getParentId());
+ spinnerInfo.setSpinnerList(field.getCandidatesList(), defaultKey);
+
+ if (fieldText.length() > 0) {
+ spinner.setPrompt(fieldText);
+ }
+ spinner.setOnItemSelectedListener(this);
+ spinners.add(spinnerInfo);
+ }
+ }
+
+ private void createViewForCountry() {
+ if (!formOptions.isHidden(AddressField.COUNTRY)) {
+ // For initialization when the form is first created.
+ if (!inputWidgets.containsKey(AddressField.COUNTRY)) {
+ buildCountryListBox();
+ }
+ createView(
+ rootView,
+ inputWidgets.get(AddressField.COUNTRY),
+ getLocalCountryName(currentRegion),
+ formOptions.isReadonly(AddressField.COUNTRY));
+ }
+ }
+
+ /** Associates each field with its corresponding AddressUiComponent. */
+ private void buildFieldWidgets() {
+ AddressData data = new AddressData.Builder().setCountry(currentRegion).build();
+ LookupKey key = new LookupKey.Builder(LookupKey.KeyType.DATA).setAddressData(data).build();
+ AddressVerificationNodeData countryNode = clientData.getDefaultData(key.toString());
+
+ // Set up AddressField.ADMIN_AREA
+ AddressUiComponent adminAreaUi = new AddressUiComponent(AddressField.ADMIN_AREA);
+ adminAreaUi.setFieldName(getAdminAreaFieldName(countryNode));
+ inputWidgets.put(AddressField.ADMIN_AREA, adminAreaUi);
+
+ // Set up AddressField.LOCALITY
+ AddressUiComponent localityUi = new AddressUiComponent(AddressField.LOCALITY);
+ localityUi.setFieldName(getLocalityFieldName(countryNode));
+ inputWidgets.put(AddressField.LOCALITY, localityUi);
+
+ // Set up AddressField.DEPENDENT_LOCALITY
+ AddressUiComponent subLocalityUi = new AddressUiComponent(AddressField.DEPENDENT_LOCALITY);
+ subLocalityUi.setFieldName(getSublocalityFieldName(countryNode));
+ inputWidgets.put(AddressField.DEPENDENT_LOCALITY, subLocalityUi);
+
+ // Set up AddressField.ADDRESS_LINE_1
+ AddressUiComponent addressLine1Ui = new AddressUiComponent(AddressField.ADDRESS_LINE_1);
+ addressLine1Ui.setFieldName(context.getString(R.string.i18n_address_line1_label));
+ inputWidgets.put(AddressField.ADDRESS_LINE_1, addressLine1Ui);
+ // Setup an alternate mapping for the first address line which is what validation expects
+ inputWidgets.put(AddressField.STREET_ADDRESS, addressLine1Ui);
+
+ // Set up AddressField.ADDRESS_LINE_2
+ AddressUiComponent addressLine2Ui = new AddressUiComponent(AddressField.ADDRESS_LINE_2);
+ addressLine2Ui.setFieldName("");
+ inputWidgets.put(AddressField.ADDRESS_LINE_2, addressLine2Ui);
+
+ // Set up AddressField.ORGANIZATION
+ AddressUiComponent organizationUi = new AddressUiComponent(AddressField.ORGANIZATION);
+ organizationUi.setFieldName(context.getString(R.string.i18n_organization_label));
+ inputWidgets.put(AddressField.ORGANIZATION, organizationUi);
+
+ // Set up AddressField.RECIPIENT
+ AddressUiComponent recipientUi = new AddressUiComponent(AddressField.RECIPIENT);
+ recipientUi.setFieldName(context.getString(R.string.i18n_recipient_label));
+ inputWidgets.put(AddressField.RECIPIENT, recipientUi);
+
+ // Set up AddressField.POSTAL_CODE
+ AddressUiComponent postalCodeUi = new AddressUiComponent(AddressField.POSTAL_CODE);
+ postalCodeUi.setFieldName(getZipFieldName(countryNode));
+ inputWidgets.put(AddressField.POSTAL_CODE, postalCodeUi);
+
+ // Set up AddressField.SORTING_CODE
+ AddressUiComponent sortingCodeUi = new AddressUiComponent(AddressField.SORTING_CODE);
+ sortingCodeUi.setFieldName("CEDEX");
+ inputWidgets.put(AddressField.SORTING_CODE, sortingCodeUi);
+ }
+
+ private void initializeDropDowns() {
+ AddressUiComponent adminAreaUi = inputWidgets.get(AddressField.ADMIN_AREA);
+ List<RegionData> adminAreaList = getRegionData(AddressField.COUNTRY);
+ adminAreaUi.initializeCandidatesList(adminAreaList);
+
+ AddressUiComponent localityUi = inputWidgets.get(AddressField.LOCALITY);
+ List<RegionData> localityList = getRegionData(AddressField.ADMIN_AREA);
+ localityUi.initializeCandidatesList(localityList);
+ }
+
+ // ZIP code is called postal code in some countries, and PIN code in India. This method returns
+ // the appropriate name for the given countryNode.
+ private String getZipFieldName(AddressVerificationNodeData countryNode) {
+ String zipName;
+ String zipType = countryNode.get(AddressDataKey.ZIP_NAME_TYPE);
+ if (zipType == null || zipType.equals("postal")) {
+ zipLabel = ZipLabel.POSTAL;
+ zipName = context.getString(R.string.i18n_postal_code_label);
+ } else if (zipType.equals("eircode")) {
+ zipLabel = ZipLabel.EIRCODE;
+ zipName = context.getString(R.string.i18n_eir_code_label);
+ } else if (zipType.equals("pin")) {
+ zipLabel = ZipLabel.PIN;
+ zipName = context.getString(R.string.i18n_pin_code_label);
+ } else {
+ zipLabel = ZipLabel.ZIP;
+ zipName = context.getString(R.string.i18n_zip_code_label);
+ }
+ return zipName;
+ }
+
+ private String getLocalityFieldName(AddressVerificationNodeData countryNode) {
+ String localityLabelType = countryNode.get(AddressDataKey.LOCALITY_NAME_TYPE);
+ Integer result = LOCALITY_LABELS.get(localityLabelType);
+ if (result == null) {
+ // Fallback to city.
+ result = R.string.i18n_locality_label;
+ }
+ return context.getString(result);
+ }
+
+ private String getSublocalityFieldName(AddressVerificationNodeData countryNode) {
+ String sublocalityLabelType = countryNode.get(AddressDataKey.SUBLOCALITY_NAME_TYPE);
+ Integer result = SUBLOCALITY_LABELS.get(sublocalityLabelType);
+ if (result == null) {
+ // Fallback to suburb.
+ result = R.string.i18n_suburb;
+ }
+ return context.getString(result);
+ }
+
+ private String getAdminAreaFieldName(AddressVerificationNodeData countryNode) {
+ String adminLabelType = countryNode.get(AddressDataKey.STATE_NAME_TYPE);
+ Integer result = ADMIN_LABELS.get(adminLabelType);
+ if (result == null) {
+ // Fallback to province.
+ result = R.string.i18n_province;
+ }
+ return context.getString(result);
+ }
+
+ private void buildCountryListBox() {
+ // Set up AddressField.COUNTRY
+ AddressUiComponent countryUi = new AddressUiComponent(AddressField.COUNTRY);
+ countryUi.setFieldName(context.getString(R.string.i18n_country_or_region_label));
+ ArrayList<RegionData> countries = new ArrayList<RegionData>();
+ for (RegionData regionData :
+ formController.getRegionData(new LookupKey.Builder(KeyType.DATA).build())) {
+ String regionKey = regionData.getKey();
+ Log.i(this.toString(), "Looking at regionKey: " + regionKey);
+ // ZZ represents an unknown region code.
+ if (!regionKey.equals("ZZ") && !formOptions.isBlacklistedRegion(regionKey)) {
+ Log.i(this.toString(), "Adding " + regionKey);
+ String localCountryName = getLocalCountryName(regionKey);
+ RegionData country =
+ new RegionData.Builder().setKey(regionKey).setName(localCountryName).build();
+ countries.add(country);
+ }
+ }
+ countryUi.initializeCandidatesList(countries);
+ inputWidgets.put(AddressField.COUNTRY, countryUi);
+ }
+
+ private String getLocalCountryName(String regionCode) {
+ return (new Locale("", regionCode)).getDisplayCountry(Locale.getDefault());
+ }
+
+ private AddressSpinnerInfo findSpinnerByView(View view) {
+ for (AddressSpinnerInfo spinnerInfo : spinners) {
+ if (spinnerInfo.view == view) {
+ return spinnerInfo;
+ }
+ }
+ return null;
+ }
+
+ private void updateFields() {
+ removePreviousViews();
+ createViewForCountry();
+ buildFieldWidgets();
+ initializeDropDowns();
+ layoutAddressFields();
+ }
+
+ private void removePreviousViews() {
+ if (rootView == null) {
+ return;
+ }
+ rootView.removeAllViews();
+ }
+
+ private void layoutAddressFields() {
+ for (AddressField field : formatInterpreter.getAddressFieldOrder(script, currentRegion)) {
+ if (!formOptions.isHidden(field)) {
+ createView(rootView, inputWidgets.get(field), "", formOptions.isReadonly(field));
+ }
+ }
+ }
+
+ private void updateChildNodes(AdapterView<?> parent, int position) {
+ AddressSpinnerInfo spinnerInfo = findSpinnerByView(parent);
+ if (spinnerInfo == null) {
+ return;
+ }
+
+ // Find all the child spinners, if any, that depend on this one.
+ final AddressField myId = spinnerInfo.id;
+ if (myId != AddressField.COUNTRY
+ && myId != AddressField.ADMIN_AREA
+ && myId != AddressField.LOCALITY) {
+ // Only a change in the three AddressFields above will trigger a change in other
+ // AddressFields. Therefore, for all other AddressFields, we return immediately.
+ return;
+ }
+
+ String regionCode = spinnerInfo.getRegionCode(position);
+ if (myId == AddressField.COUNTRY) {
+ updateWidgetOnCountryChange(regionCode);
+ return;
+ }
+
+ formController.requestDataForAddress(
+ getAddressData(),
+ new DataLoadListener() {
+ @Override
+ public void dataLoadingBegin() {}
+
+ @Override
+ public void dataLoadingEnd() {
+ Runnable updateChild = new UpdateRunnable(myId);
+ handler.post(updateChild);
+ }
+ });
+ }
+
+ public void updateWidgetOnCountryChange(String regionCode) {
+ if (currentRegion.equalsIgnoreCase(regionCode)) {
+ return;
+ }
+ currentRegion = regionCode;
+ formController.setCurrentCountry(currentRegion);
+ renderForm();
+ }
+
+ private void updateInputWidget(AddressField myId) {
+ for (AddressSpinnerInfo child : spinners) {
+ if (child.parentId == myId) {
+ List<RegionData> candidates = getRegionData(child.parentId);
+ child.setSpinnerList(candidates, "");
+ }
+ }
+ }
+
+ public void renderForm() {
+ createViewForCountry();
+ setWidgetLocaleAndScript();
+ AddressData data =
+ new AddressData.Builder().setCountry(currentRegion).setLanguageCode(widgetLocale).build();
+ formController.requestDataForAddress(
+ data,
+ new DataLoadListener() {
+ @Override
+ public void dataLoadingBegin() {
+ progressDialog = componentProvider.getUiActivityIndicatorView();
+ progressDialog.setMessage(context.getString(R.string.address_data_loading));
+ Log.d(this.toString(), "Progress dialog started.");
+ }
+
+ @Override
+ public void dataLoadingEnd() {
+ Log.d(this.toString(), "Data loading completed.");
+ progressDialog.dismiss();
+ Log.d(this.toString(), "Progress dialog stopped.");
+ handler.post(updateMultipleFields);
+ }
+ });
+ }
+
+ private void setWidgetLocaleAndScript() {
+ widgetLocale = Util.getWidgetCompatibleLanguageCode(Locale.getDefault(), currentRegion);
+ formController.setLanguageCode(widgetLocale);
+ script = Util.isExplicitLatinScript(widgetLocale) ? ScriptType.LATIN : ScriptType.LOCAL;
+ }
+
+ private List<RegionData> getRegionData(AddressField parentField) {
+ AddressData address = getAddressData();
+
+ // Removes language code from address if it is default. This address is used to build
+ // lookup key, which neglects default language. For example, instead of "data/US--en/CA",
+ // the right lookup key is "data/US/CA".
+ if (formController.isDefaultLanguage(address.getLanguageCode())) {
+ address = AddressData.builder(address).setLanguageCode(null).build();
+ }
+
+ LookupKey parentKey =
+ formController.getDataKeyFor(address).getKeyForUpperLevelField(parentField);
+ List<RegionData> candidates;
+ // Can't build a key with parent field, quit.
+ if (parentKey == null) {
+ Log.w(
+ this.toString(),
+ "Can't build key with parent field "
+ + parentField
+ + ". One of"
+ + " the ancestor fields might be empty");
+
+ // Removes candidates that exist from previous settings. For example, data/US has a
+ // list of candidates AB, BC, CA, etc, that list should be cleaned up when user updates
+ // the address by changing country to Channel Islands.
+ candidates = new ArrayList<RegionData>(1);
+ } else {
+ candidates = formController.getRegionData(parentKey);
+ }
+ return candidates;
+ }
+
+ /**
+ * Creates an AddressWidget to be attached to rootView for the specific context using the default
+ * UI component provider.
+ */
+ public AddressWidget(
+ Context context,
+ ViewGroup rootView,
+ FormOptions formOptions,
+ ClientCacheManager cacheManager) {
+ this(
+ context,
+ rootView,
+ formOptions,
+ cacheManager,
+ new AddressWidgetUiComponentProvider(context));
+ }
+
+ /**
+ * Creates an AddressWidget to be attached to rootView for the specific context using UI component
+ * provided by the provider.
+ */
+ public AddressWidget(
+ Context context,
+ ViewGroup rootView,
+ FormOptions formOptions,
+ ClientCacheManager cacheManager,
+ AddressWidgetUiComponentProvider provider) {
+ componentProvider = provider;
+ currentRegion =
+ ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
+ .getSimCountryIso()
+ .toUpperCase(Locale.US);
+ if (currentRegion.length() == 0) {
+ currentRegion = "US";
+ }
+ init(context, rootView, formOptions.createSnapshot(), cacheManager);
+ renderForm();
+ }
+
+ /**
+ * Creates an AddressWidget to be attached to rootView for the specific context using the default
+ * UI component provider, and fill out the address form with savedAddress.
+ */
+ public AddressWidget(
+ Context context,
+ ViewGroup rootView,
+ FormOptions formOptions,
+ ClientCacheManager cacheManager,
+ AddressData savedAddress) {
+ this(
+ context,
+ rootView,
+ formOptions,
+ cacheManager,
+ savedAddress,
+ new AddressWidgetUiComponentProvider(context));
+ }
+
+ /**
+ * Creates an AddressWidget to be attached to rootView for the specific context using UI component
+ * provided by the provider, and fill out the address form with savedAddress.
+ */
+ public AddressWidget(
+ Context context,
+ ViewGroup rootView,
+ FormOptions formOptions,
+ ClientCacheManager cacheManager,
+ AddressData savedAddress,
+ AddressWidgetUiComponentProvider provider) {
+ componentProvider = provider;
+ currentRegion = savedAddress.getPostalCountry();
+ // Postal country must be 2 letter country code. Otherwise default to US.
+ if (currentRegion == null || currentRegion.length() != 2) {
+ currentRegion = "US";
+ } else {
+ currentRegion = currentRegion.toUpperCase();
+ }
+ init(context, rootView, formOptions.createSnapshot(), cacheManager);
+ renderFormWithSavedAddress(savedAddress);
+ }
+
+ /*
+ * Enables autocompletion for the ADDRESS_LINE_1 field. With autocompletion enabled, the user
+ * will see suggested addresses in a dropdown menu below the ADDRESS_LINE_1 field as they are
+ * typing, and when they select an address, the form fields will be autopopulated with the
+ * selected address.
+ *
+ * NOTE: This feature is currently experimental.
+ *
+ * If the AddressAutocompleteApi is not configured correctly, then the AddressWidget will degrade
+ * gracefully to an ordinary plain text input field without autocomplete.
+ */
+ public void enableAutocomplete(
+ AddressAutocompleteApi autocompleteApi, PlaceDetailsApi placeDetailsApi) {
+ AddressAutocompleteController autocompleteController =
+ new AddressAutocompleteController(context, autocompleteApi, placeDetailsApi);
+ if (autocompleteApi.isConfiguredCorrectly()) {
+ this.autocompleteEnabled = true;
+ this.autocompleteController = autocompleteController;
+
+ // The autocompleteEnabled variable set above is used in createView to determine whether to
+ // use an EditText or an AutoCompleteTextView. Re-rendering the form here ensures that
+ // createView is called with the updated value of autocompleteEnabled.
+ renderFormWithSavedAddress(getAddressData());
+ } else {
+ Log.w(
+ this.toString(),
+ "Autocomplete not configured correctly, falling back to a plain text " + "input field.");
+ }
+ }
+
+ public void disableAutocomplete() {
+ this.autocompleteEnabled = false;
+ }
+
+ public void renderFormWithSavedAddress(AddressData savedAddress) {
+ setWidgetLocaleAndScript();
+ removePreviousViews();
+ createViewForCountry();
+ buildFieldWidgets();
+ initializeDropDowns();
+ layoutAddressFields();
+ initializeFieldsWithAddress(savedAddress);
+ }
+
+ private void initializeFieldsWithAddress(AddressData savedAddress) {
+ for (AddressField field : formatInterpreter.getAddressFieldOrder(script, currentRegion)) {
+ String value = savedAddress.getFieldValue(field);
+ if (value == null) {
+ value = "";
+ }
+
+ AddressUiComponent uiComponent = inputWidgets.get(field);
+ if (uiComponent != null) {
+ uiComponent.setValue(value);
+ }
+ }
+ }
+
+ private void init(
+ Context context,
+ ViewGroup rootView,
+ FormOptions.Snapshot formOptions,
+ ClientCacheManager cacheManager) {
+ this.context = context;
+ this.rootView = rootView;
+ this.formOptions = formOptions;
+ // Inject Adnroid specific async request implementation here.
+ this.cacheData = new CacheData(cacheManager, new AndroidAsyncEncodedRequestApi());
+ this.clientData = new ClientData(cacheData);
+ this.formController = new FormController(clientData, widgetLocale, currentRegion);
+ this.formatInterpreter = new FormatInterpreter(formOptions);
+ this.verifier = new StandardAddressVerifier(new FieldVerifier(clientData));
+ }
+
+ /**
+ * Sets address data server URL. Input URL cannot be null.
+ *
+ * @param url The service URL.
+ */
+ // TODO: Remove this method and set the URL in the constructor or via the cacheData directly.
+ public void setUrl(String url) {
+ cacheData.setUrl(url);
+ }
+
+ /** Gets user input address in AddressData format. */
+ public AddressData getAddressData() {
+ AddressData.Builder builder = new AddressData.Builder();
+ builder.setCountry(currentRegion);
+ for (AddressField field : formatInterpreter.getAddressFieldOrder(script, currentRegion)) {
+ AddressUiComponent addressUiComponent = inputWidgets.get(field);
+ if (addressUiComponent != null) {
+ String value = addressUiComponent.getValue();
+ if (addressUiComponent.getUiType() == UiComponent.SPINNER) {
+ // For drop-downs, return the key of the region selected instead of the value.
+ View view = getViewForField(field);
+ AddressSpinnerInfo spinnerInfo = findSpinnerByView(view);
+ if (spinnerInfo != null) {
+ value = spinnerInfo.getRegionDataKeyForValue(value);
+ }
+ }
+ builder.set(field, value);
+ }
+ }
+ builder.setLanguageCode(widgetLocale);
+ return builder.build();
+ }
+
+ /**
+ * Gets the formatted address.
+ *
+ * <p>This method does not validate addresses. Also, it will "normalize" the result strings by
+ * removing redundant spaces and empty lines.
+ *
+ * @return the formatted address
+ */
+ public List<String> getEnvelopeAddress() {
+ return getEnvelopeAddress(getAddressData());
+ }
+
+ /** Gets the formatted address based on the AddressData passed in. */
+ public List<String> getEnvelopeAddress(AddressData address) {
+ return getEnvelopeAddress(formatInterpreter, address);
+ }
+
+ /**
+ * Helper function for getting the formatted address based on the FormatInterpreter and
+ * AddressData passed in.
+ */
+ private static List<String> getEnvelopeAddress(
+ FormatInterpreter interpreter, AddressData address) {
+ String countryCode = address.getPostalCountry();
+ if (countryCode.length() != 2) {
+ return Collections.emptyList();
+ }
+ // Avoid crashes due to lower-case country codes (leniency at the input).
+ String upperCountryCode = countryCode.toUpperCase();
+ if (!countryCode.equals(upperCountryCode)) {
+ address = AddressData.builder(address).setCountry(upperCountryCode).build();
+ }
+ return interpreter.getEnvelopeAddress(address);
+ }
+
+ /**
+ * Gets the formatted address based on the AddressData passed in with none of the relevant fields
+ * hidden.
+ */
+ public static List<String> getFullEnvelopeAddress(AddressData address) {
+ return getEnvelopeAddress(new FormatInterpreter(SHOW_ALL_FIELDS), address);
+ }
+
+ /** Get problems found in the address data entered by the user. */
+ public AddressProblems getAddressProblems() {
+ AddressProblems problems = new AddressProblems();
+ AddressData addressData = getAddressData();
+ verifier.verify(addressData, problems);
+ return problems;
+ }
+
+ /**
+ * Displays an appropriate error message for an AddressField with a problem.
+ *
+ * @return the View object representing the AddressField.
+ */
+ public View displayErrorMessageForField(
+ AddressData address, AddressField field, AddressProblemType problem) {
+ Log.d(this.toString(), "Display error message for the field: " + field.toString());
+ AddressUiComponent addressUiComponent = inputWidgets.get(field);
+ if (addressUiComponent != null && addressUiComponent.getUiType() == UiComponent.EDIT) {
+ EditText view = (EditText) addressUiComponent.getView();
+ view.setError(getErrorMessageForInvalidEntry(address, field, problem));
+ return view;
+ }
+ return null;
+ }
+
+ public String getErrorMessageForInvalidEntry(
+ AddressData address, AddressField field, AddressProblemType problem) {
+ switch (problem) {
+ case MISSING_REQUIRED_FIELD:
+ return context.getString(R.string.i18n_missing_required_field);
+ case UNKNOWN_VALUE:
+ return context.getString(R.string.unknown_entry);
+ case INVALID_FORMAT:
+ // We only support this error type for the Postal Code field.
+ if (zipLabel == ZipLabel.POSTAL) {
+ return context.getString(R.string.unrecognized_format_postal_code);
+ } else if (zipLabel == ZipLabel.PIN) {
+ return context.getString(R.string.unrecognized_format_pin_code);
+ } else {
+ return context.getString(R.string.unrecognized_format_zip_code);
+ }
+ case MISMATCHING_VALUE:
+ // We only support this error type for the Postal Code field.
+ if (zipLabel == ZipLabel.POSTAL) {
+ return context.getString(R.string.mismatching_value_postal_code);
+ } else if (zipLabel == ZipLabel.PIN) {
+ return context.getString(R.string.mismatching_value_pin_code);
+ } else {
+ return context.getString(R.string.mismatching_value_zip_code);
+ }
+ case UNEXPECTED_FIELD:
+ throw new IllegalStateException("unexpected problem type: " + problem);
+ default:
+ throw new IllegalStateException("unknown problem type: " + problem);
+ }
+ }
+
+ /** Clears all error messages in the UI. */
+ public void clearErrorMessage() {
+ for (AddressField field : formatInterpreter.getAddressFieldOrder(script, currentRegion)) {
+ AddressUiComponent addressUiComponent = inputWidgets.get(field);
+
+ if (addressUiComponent != null && addressUiComponent.getUiType() == UiComponent.EDIT) {
+ EditText view = (EditText) addressUiComponent.getView();
+ if (view != null) {
+ view.setError(null);
+ }
+ }
+ }
+ }
+
+ public View getViewForField(AddressField field) {
+ AddressUiComponent component = inputWidgets.get(field);
+ if (component == null) {
+ return null;
+ }
+ return component.getView();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0) {}
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ updateChildNodes(parent, position);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidgetUiComponentProvider.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidgetUiComponentProvider.java
new file mode 100644
index 00000000000..6931847e945
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AddressWidgetUiComponentProvider.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.google.i18n.addressinput.common.AddressField.WidthType;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+/**
+ * Base class for customizing widgets for address input.
+ * <p>
+ * Clients can optionally override this class and pass it to {@link AddressWidget} the constructor.
+ * This will be invoked by the widget to create UI components that provide consistent look-and-feel
+ * with other UI components clients might use alongside the address widget.
+ */
+public class AddressWidgetUiComponentProvider {
+ protected Context context;
+ protected LayoutInflater inflater;
+
+ public AddressWidgetUiComponentProvider(Context context) {
+ this.context = context;
+ this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
+ * Creates a label, e.g. "State", for an address input field.
+ *
+ * @param label the label of the address input field
+ * @param widthType {@link WidthType} of the field
+ * @return a custom {@link TextView} created for the field
+ */
+ protected TextView createUiLabel(CharSequence label, WidthType widthType) {
+ TextView textView = (TextView) inflater.inflate(R.layout.address_textview, null, false);
+ textView.setText(label);
+ return textView;
+ }
+
+ /**
+ * Creates a text input view for an address input field.
+ *
+ * @param widthType {@link WidthType} of the field
+ * @return a custom {@link EditText} created for the field
+ */
+ protected EditText createUiTextField(WidthType widthType) {
+ return (EditText) inflater.inflate(R.layout.address_edittext, null, false);
+ }
+
+ /**
+ * Creates a {@link Spinner} for a input field that uses UI picker.
+ *
+ * @param widthType {@link WidthType} of the field
+ * @return a custom {@link Spinner} created for the field
+ */
+ protected Spinner createUiPickerSpinner(WidthType widthType) {
+ return (Spinner) inflater.inflate(R.layout.address_spinner, null, false);
+ }
+
+ /**
+ * Creates an {@link AutoCompleteTextView} for an input field that uses autocomplete.
+ *
+ * @param widthType {@link WidthType} of the field
+ * @return a custom {@link AutoCompleteTextView} created for the field
+ */
+ protected AutoCompleteTextView createUiAutoCompleteTextField(WidthType widthType) {
+ return (AutoCompleteTextView)
+ inflater.inflate(R.layout.address_autocomplete_textview, null, false);
+ }
+
+ /**
+ * Creates an {@link ArrayAdapter} to work with the custom {@link Spinner} of a input field that
+ * uses UI picker.
+ *
+ * @param widthType {@link WidthType} of the field
+ * @return a custom {@link ArrayAdapter} for the field
+ */
+ protected ArrayAdapter<String> createUiPickerAdapter(WidthType widthType) {
+ ArrayAdapter<String> adapter =
+ new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ return adapter;
+ }
+
+ /** Gets an activity indicator to show that a task is in progress. */
+ protected ProgressDialog getUiActivityIndicatorView() {
+ return new ProgressDialog(context);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApi.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApi.java
new file mode 100644
index 00000000000..bc7305c611f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncEncodedRequestApi.java
@@ -0,0 +1,46 @@
+package com.android.i18n.addressinput;
+
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+
+public class AndroidAsyncEncodedRequestApi extends AndroidAsyncRequestApi {
+ /**
+ * A quick hack to transform a string into an RFC 3986 compliant URL.
+ *
+ * <p>TODO: Refactor the code to stop passing URLs around as strings, to eliminate the need for
+ * this broken hack.
+ */
+ @Override
+ protected URL stringToUrl(String url) throws MalformedURLException {
+ int length = url.length();
+ StringBuilder tmp = new StringBuilder(length);
+
+ try {
+ for (int i = 0; i < length; i++) {
+ int j = i;
+ char c = '\0';
+ for (; j < length; j++) {
+ c = url.charAt(j);
+ if (c == ':' || c == '/') {
+ break;
+ }
+ }
+ if (j == length) {
+ tmp.append(URLEncoder.encode(url.substring(i), "UTF-8"));
+ break;
+ } else if (j > i) {
+ tmp.append(URLEncoder.encode(url.substring(i, j), "UTF-8"));
+ tmp.append(c);
+ i = j;
+ } else {
+ tmp.append(c);
+ }
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e); // Impossible.
+ }
+ return new URL(tmp.toString());
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncRequestApi.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncRequestApi.java
new file mode 100644
index 00000000000..5fe40e934a3
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/AndroidAsyncRequestApi.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.google.i18n.addressinput.common.AsyncRequestApi;
+import com.google.i18n.addressinput.common.JsoMap;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Provider;
+import java.security.Security;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Android implementation of AsyncRequestApi.
+ * <p>
+ * Note that this class uses a thread-per-request approach to asynchronous connections. There are
+ * likely to be better ways of doing this (and even if not, this implementation suffers from several
+ * issues regarding interruption and cancellation). Ultimately this class should be revisited and
+ * most likely rewritten.
+ */
+// TODO: Reimplement this class according to current best-practice for asynchronous requests.
+public class AndroidAsyncRequestApi implements AsyncRequestApi {
+ private static final String TAG = "AsyncRequestApi";
+
+ /** Simple implementation of asynchronous HTTP GET. */
+ private static class AsyncHttp extends Thread {
+ private final URL requestUrl;
+ private final AsyncCallback callback;
+ private final int timeoutMillis;
+
+ protected AsyncHttp(URL requestUrl, AsyncCallback callback, int timeoutMillis) {
+ this.requestUrl = requestUrl;
+ this.callback = callback;
+ this.timeoutMillis = timeoutMillis;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // While MalformedURLException from URL's constructor is a different kind of error than
+ // issues with the HTTP request, we're handling them the same way because the URLs are often
+ // generated based on data returned by previous HTTP requests and we need robust, graceful
+ // handling of any issues.
+ HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection();
+ connection.setConnectTimeout(timeoutMillis);
+ connection.setReadTimeout(timeoutMillis);
+
+ Provider[] providers = Security.getProviders();
+ if (providers.length > 0 && providers[0].getName().equals("GmsCore_OpenSSL")) {
+ // GMS security provider requires special handling of HTTPS connections. (b/29555362)
+ if (connection instanceof HttpsURLConnection) {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null /* KeyManager */, null /* TrustManager */, null /* SecureRandom */);
+ SSLSocketFactory sslSocketFactory = context.getSocketFactory();
+ if (sslSocketFactory != null) {
+ ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
+ }
+ }
+ }
+
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ BufferedReader responseReader = new BufferedReader(
+ new InputStreamReader(connection.getInputStream(), "UTF-8"));
+ StringBuilder responseJson = new StringBuilder();
+ String line;
+ while ((line = responseReader.readLine()) != null) {
+ responseJson.append(line);
+ }
+ responseReader.close();
+ callback.onSuccess(JsoMap.buildJsoMap(responseJson.toString()));
+ } else {
+ callback.onFailure();
+ }
+ connection.disconnect();
+ } catch (Exception e) {
+ callback.onFailure();
+ }
+ }
+ }
+
+ @Override public void requestObject(String url, AsyncCallback callback, int timeoutMillis) {
+ try {
+ (new AsyncHttp(stringToUrl(url), callback, timeoutMillis)).start();
+ } catch (MalformedURLException e) {
+ callback.onFailure();
+ }
+ }
+
+ protected URL stringToUrl(String url) throws MalformedURLException {
+ return new URL(url);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsApi.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsApi.java
new file mode 100644
index 00000000000..3bbb6177407
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsApi.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.i18n.addressinput;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressData;
+
+/**
+ * An interface for transforming an {@link AddressAutocompletePrediction} into {@link AddressData}.
+ */
+public interface PlaceDetailsApi {
+ ListenableFuture<AddressData> getAddressData(AddressAutocompletePrediction prediction);
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsClient.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsClient.java
new file mode 100644
index 00000000000..da7dcf78a47
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/PlaceDetailsClient.java
@@ -0,0 +1,162 @@
+package com.android.i18n.addressinput;
+
+import android.net.Uri;
+import android.util.Log;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressData;
+import com.google.i18n.addressinput.common.AsyncRequestApi;
+import com.google.i18n.addressinput.common.AsyncRequestApi.AsyncCallback;
+import com.google.i18n.addressinput.common.JsoMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Implementation of the PlaceDetailsApi using the Place Details Web API
+ * (https://developers.google.com/places/web-service/details). Unfortunately, the Google Place
+ * Details API for Android does not include a structured representation of the address.
+ */
+public class PlaceDetailsClient implements PlaceDetailsApi {
+
+ private AsyncRequestApi asyncRequestApi;
+ private String apiKey;
+
+ @VisibleForTesting static final int TIMEOUT = 5000;
+
+ private static final String TAG = "PlaceDetailsClient";
+
+ public PlaceDetailsClient(String apiKey, AsyncRequestApi asyncRequestApi) {
+ this.asyncRequestApi = asyncRequestApi;
+ this.apiKey = apiKey;
+ }
+
+ @Override
+ public ListenableFuture<AddressData> getAddressData(AddressAutocompletePrediction prediction) {
+ final SettableFuture<AddressData> addressData = SettableFuture.create();
+
+ asyncRequestApi.requestObject(
+ new Uri.Builder()
+ .scheme("https")
+ .authority("maps.googleapis.com")
+ .path("maps/api/place/details/json")
+ .appendQueryParameter("key", apiKey)
+ .appendQueryParameter("placeid", prediction.getPlaceId())
+ .build()
+ .toString(),
+ new AsyncCallback() {
+ @Override
+ public void onFailure() {
+ addressData.cancel(false);
+ }
+
+ @Override
+ public void onSuccess(JsoMap response) {
+ // Can't use JSONObject#getJSONObject to get the 'result' because #getJSONObject calls
+ // #get, which has been broken by JsoMap to only return String values
+ // *grinds teeth in frustration*.
+ try {
+ if (response.has("result")) {
+ Object result = response.getObject("result");
+ if (result instanceof JSONObject) {
+ addressData.set(getAddressData((JSONObject) result));
+ Log.i(TAG, "Successfully set AddressData from Place Details API response.");
+ } else {
+ Log.e(
+ TAG,
+ "Error parsing JSON response from Place Details API: "
+ + "expected 'result' field to be a JSONObject.");
+ onFailure();
+ }
+ } else if (response.has("error_message")) {
+ String error_message = response.getString("error_message");
+ Log.e(TAG, "Place Details API request failed: " + error_message);
+ } else {
+ Log.e(
+ TAG,
+ "Expected either result or error_message in response "
+ + "from Place Details API");
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Error parsing JSON response from Place Details API", e);
+ onFailure();
+ }
+ }
+ },
+ TIMEOUT);
+
+ return addressData;
+ }
+
+ private AddressData getAddressData(JSONObject result) throws JSONException {
+ AddressData.Builder addressData = AddressData.builder();
+
+ // Get the country code from address_components.
+ JSONArray addressComponents = result.getJSONArray("address_components");
+ // Iterate backwards since country is usually at the end.
+ for (int i = addressComponents.length() - 1; i >= 0; i--) {
+ JSONObject addressComponent = addressComponents.getJSONObject(i);
+
+ List<String> types = new ArrayList<>();
+ JSONArray componentTypes = addressComponent.getJSONArray("types");
+ for (int j = 0; j < componentTypes.length(); j++) {
+ types.add(componentTypes.getString(j));
+ }
+
+ if (types.contains("country")) {
+ addressData.setCountry(addressComponent.getString("short_name"));
+ break;
+ }
+ }
+
+ String unescapedAdrAddress =
+ result
+ .getString("adr_address")
+ .replace("\\\"", "\"")
+ .replace("\\u003c", "<")
+ .replace("\\u003e", ">");
+
+ Pattern adrComponentPattern = Pattern.compile("[, ]{0,2}<span class=\"(.*)\">(.*)<");
+
+ for (String adrComponent : unescapedAdrAddress.split("/span>")) {
+ Matcher m = adrComponentPattern.matcher(adrComponent);
+ Log.i(TAG, adrComponent + " matches: " + m.matches());
+ if (m.matches() && m.groupCount() == 2) {
+ String key = m.group(1);
+ String value = m.group(2);
+ switch (key) {
+ case "street-address":
+ addressData.setAddress(value);
+ // TODO(b/33790911): Include the 'extended-address' and 'post-office-box' adr_address
+ // fields in the AddressData address.
+ break;
+ case "locality":
+ addressData.setLocality(value);
+ break;
+ case "region":
+ addressData.setAdminArea(value);
+ break;
+ case "postal-code":
+ addressData.setPostalCode(value);
+ break;
+ case "country-name":
+ // adr_address country names are not in CLDR format, which is the format used by the
+ // AddressWidget. We fetch the country code from the address_components instead.
+ break;
+ default:
+ Log.e(TAG, "Key " + key + " not recognized in Place Details API response.");
+ }
+ } else {
+ Log.e(TAG, "Failed to match " + adrComponent + " with regexp " + m.pattern().toString());
+ }
+ }
+
+ return addressData.build();
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImpl.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImpl.java
new file mode 100644
index 00000000000..72580167db4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompleteApiImpl.java
@@ -0,0 +1,107 @@
+package com.android.i18n.addressinput.autocomplete.gmscore;
+
+import android.location.Location;
+import android.util.Log;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.location.FusedLocationProviderApi;
+import com.google.android.gms.location.places.AutocompleteFilter;
+import com.google.android.gms.location.places.AutocompletePrediction;
+import com.google.android.gms.location.places.AutocompletePredictionBuffer;
+import com.google.android.gms.location.places.GeoDataApi;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.i18n.addressinput.common.AddressAutocompleteApi;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GMSCore implementation of {@link com.google.i18n.addressinput.common.AddressAutocompleteApi}.
+ *
+ * <p>Internal clients should use the gcoreclient implementation instead.
+ *
+ * Callers should provide a GoogleApiClient with the Places.GEO_DATA_API and
+ * LocationServices.API enabled. The GoogleApiClient should be connected before
+ * it is passed to AddressWidget#enableAutocomplete. The caller will also need to request the
+ * following permissions in their AndroidManifest.xml:
+ *
+ * <pre>
+ * <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
+ * <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ * </pre>
+ *
+ * Callers should check that the required permissions are actually present.
+ * TODO(b/32559817): Handle permission check in libaddressinput so that callers don't need to.
+ */
+public class AddressAutocompleteApiImpl implements AddressAutocompleteApi {
+
+ private static final String TAG = "GmsCoreAddrAutocmplt";
+ private GoogleApiClient googleApiClient;
+
+ // Use Places.GeoDataApi.
+ private GeoDataApi geoDataApi;
+
+ // Use LocationServices.FusedLocationApi.
+ private FusedLocationProviderApi locationApi;
+
+ public AddressAutocompleteApiImpl(
+ GoogleApiClient googleApiClient,
+ GeoDataApi geoDataApi,
+ FusedLocationProviderApi locationApi) {
+ this.googleApiClient = googleApiClient;
+ this.geoDataApi = geoDataApi;
+ this.locationApi = locationApi;
+ }
+
+ // TODO(b/32559817): Add a check to ensure that the required permissions have been granted.
+ @Override
+ public boolean isConfiguredCorrectly() {
+ if (!googleApiClient.isConnected()) {
+ Log.e(TAG, "Cannot get autocomplete predictions because Google API client is not connected.");
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void getAutocompletePredictions(
+ String query, final FutureCallback<List<? extends AddressAutocompletePrediction>> callback) {
+ Location deviceLocation = locationApi.getLastLocation(googleApiClient);
+ LatLngBounds bounds =
+ deviceLocation == null
+ ? null
+ : LatLngBounds.builder()
+ .include(new LatLng(deviceLocation.getLatitude(), deviceLocation.getLongitude()))
+ .build();
+
+ geoDataApi
+ .getAutocompletePredictions(
+ googleApiClient,
+ query,
+ bounds,
+ new AutocompleteFilter.Builder()
+ .setTypeFilter(AutocompleteFilter.TYPE_FILTER_ADDRESS)
+ .build())
+ .setResultCallback(
+ new ResultCallback<AutocompletePredictionBuffer>() {
+ @Override
+ public void onResult(AutocompletePredictionBuffer resultBuffer) {
+ callback.onSuccess(convertPredictions(resultBuffer));
+ }
+ });
+ }
+
+ private List<? extends AddressAutocompletePrediction> convertPredictions(
+ AutocompletePredictionBuffer resultBuffer) {
+ List<AddressAutocompletePrediction> predictions = new ArrayList<>();
+
+ for (AutocompletePrediction prediction : resultBuffer) {
+ predictions.add(new AddressAutocompletePredictionImpl(prediction));
+ }
+
+ return predictions;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompletePredictionImpl.java b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompletePredictionImpl.java
new file mode 100644
index 00000000000..d38b450d8d2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/android/src/main/java/com/android/i18n/addressinput/autocomplete/gmscore/AddressAutocompletePredictionImpl.java
@@ -0,0 +1,32 @@
+package com.android.i18n.addressinput.autocomplete.gmscore;
+
+import com.google.android.gms.location.places.AutocompletePrediction;
+import com.google.i18n.addressinput.common.AddressAutocompletePrediction;
+
+/**
+ * GMSCore implementation of {@link
+ * com.google.i18n.addressinput.common.AddressAutocompletePrediction}.
+ */
+public class AddressAutocompletePredictionImpl extends AddressAutocompletePrediction {
+
+ private AutocompletePrediction prediction;
+
+ AddressAutocompletePredictionImpl(AutocompletePrediction prediction) {
+ this.prediction = prediction;
+ }
+
+ @Override
+ public String getPlaceId() {
+ return prediction.getPlaceId();
+ }
+
+ @Override
+ public CharSequence getPrimaryText() {
+ return prediction.getPrimaryText(null);
+ }
+
+ @Override
+ public CharSequence getSecondaryText() {
+ return prediction.getSecondaryText(null);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompleteApi.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompleteApi.java
new file mode 100644
index 00000000000..1dd4711df6f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompleteApi.java
@@ -0,0 +1,27 @@
+package com.google.i18n.addressinput.common;
+
+import com.google.common.util.concurrent.FutureCallback;
+import java.util.List;
+
+/**
+ * AddressAutocompleteApi encapsulates the functionality required to fetch address autocomplete
+ * suggestions for an unstructured address query string entered by the user.
+ *
+ * An implementation using GMSCore is provided under
+ * libaddressinput/android/src/main.java/com/android/i18n/addressinput/autocomplete/gmscore.
+ */
+public interface AddressAutocompleteApi {
+ /**
+ * Returns true if the AddressAutocompleteApi is properly configured to fetch autocomplete
+ * predictions. This allows the caller to enable autocomplete only if the AddressAutocompleteApi
+ * is properly configured (e.g. the user has granted all the necessary permissions).
+ */
+ boolean isConfiguredCorrectly();
+
+ /**
+ * Given an unstructured address query, getAutocompletePredictions fetches autocomplete
+ * suggestions for the intended address and provides these suggestions via the callback.
+ */
+ void getAutocompletePredictions(
+ String query, FutureCallback<List<? extends AddressAutocompletePrediction>> callback);
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompletePrediction.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompletePrediction.java
new file mode 100644
index 00000000000..6f788a9be6d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressAutocompletePrediction.java
@@ -0,0 +1,51 @@
+package com.google.i18n.addressinput.common;
+
+import java.util.Objects;
+
+/**
+ * AddressAutocompletePrediction represents an autocomplete suggestion.
+ *
+ * Concrete inheriting classes must provide implementations of {@link #getPlaceId}, {@link
+ * #getPrimaryText}, and {@link #getSecondaryText}. An implementation using GMSCore is provided
+ * under libaddressinput/android/src/main.java/com/android/i18n/addressinput/autocomplete/gmscore.
+ */
+public abstract class AddressAutocompletePrediction {
+ /**
+ * Returns the place ID of the predicted place. A place ID is a textual identifier that uniquely
+ * identifies a place, which you can use to retrieve the Place object again later (for example,
+ * with Google's Place Details Web API).
+ */
+ public abstract String getPlaceId();
+
+ /**
+ * Returns the main text describing a place. This is usually the name of the place. Examples:
+ * "Eiffel Tower", and "123 Pitt Street".
+ */
+ public abstract CharSequence getPrimaryText();
+
+ /**
+ * Returns the subsidiary text of a place description. This is useful, for example, as a second
+ * line when showing autocomplete predictions. Examples: "Avenue Anatole France, Paris, France",
+ * and "Sydney, New South Wales".
+ */
+ public abstract CharSequence getSecondaryText();
+
+ // equals and hashCode overridden for testing.
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof AddressAutocompletePrediction)) {
+ return false;
+ }
+ AddressAutocompletePrediction p = (AddressAutocompletePrediction) o;
+
+ return getPlaceId().equals(p.getPlaceId())
+ && getPrimaryText().equals(p.getPrimaryText())
+ && getSecondaryText().equals(p.getSecondaryText());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getPlaceId(), getPrimaryText(), getSecondaryText());
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressData.java
new file mode 100644
index 00000000000..e30a89accd6
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressData.java
@@ -0,0 +1,789 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import static com.google.i18n.addressinput.common.Util.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An immutable data structure for international postal addresses, built using the nested
+ * {@link Builder} class.
+ * <p>
+ * Addresses may seem simple, but even within the US there are many quirks (hyphenated street
+ * addresses, etc.), and internationally addresses vary a great deal. The most sane and complete in
+ * many ways is the OASIS "extensible Address Language", xAL, which is a published and documented
+ * XML schema:<br>
+ * <a href="http://www.oasis-open.org/committees/ciq/download.shtml">
+ * http://www.oasis-open.org/committees/ciq/download.shtml</a>
+ * <p>
+ * An example address:
+ * <pre>
+ * postalCountry: US
+ * streetAddress: 1098 Alta Ave
+ * adminstrativeArea: CA
+ * locality: Mountain View
+ * dependentLocality:
+ * postalCode: 94043
+ * sortingCode:
+ * organization: Google
+ * recipient: Chen-Kang Yang
+ * language code: en
+ * </pre>
+ * <p>
+ * When using this class it's advised to do as little pre- or post-processing of the fields as
+ * possible. Typically we expect instances of this class to be used by the address widget and then
+ * transmitted or converted to other representations using the standard conversion libraries or
+ * formatted using one of the supported formatters. Attempting to infer semantic information from
+ * the values of the fields in this class is generally a bad idea.
+ * <p>
+ * Specifically the {@link #getFieldValue(AddressField)} is a problematic API as it encourages the
+ * belief that it is semantically correct to iterate over the fields in order. In general it should
+ * not be necessary to iterate over the fields in this class; instead use just the specific getter
+ * methods for the fields you need.
+ * <p>
+ * There are valid use cases for examining individual fields, but these are almost always region
+ * dependent:
+ * <ul>
+ * <li>Finding the region of the address. This is really the only completely safe field you can
+ * examine which gives an unambiguous and well defined result under all circumstances.
+ * <li>Testing if two addresses have the same administrative area. This is only reliable if the
+ * data was entered via a drop-down menu, and the size of administrative areas varies greatly
+ * between and within countries so it doesn't infer much about locality.
+ * <li>Extracting postal codes or sorting codes for address validation or external lookup. This
+ * only works for certain countries, such as the United Kingdom, where postal codes have a high
+ * resolution.
+ * </ul>
+ * <p>
+ * All values stored in this class are trimmed of ASCII whitespace. Setting an empty, or whitespace
+ * only field in the builder will clear it and result in a {@code null} being returned from the
+ * corresponding {@code AddressData} instance..
+ */
+// This is an external class and part of the widget's public API.
+// TODO: Review public API for external classes and tidy JavaDoc.
+public final class AddressData {
+ // The list of deprecated address fields which are superseded by STREET_ADDRESS.
+ @SuppressWarnings("deprecation") // For legacy address fields.
+ private static final List<AddressField> ADDRESS_LINE_FIELDS = Collections.unmodifiableList(
+ Arrays.asList(AddressField.ADDRESS_LINE_1, AddressField.ADDRESS_LINE_2));
+
+ private static final int ADDRESS_LINE_COUNT = ADDRESS_LINE_FIELDS.size();
+
+ // The set of address fields for which a single string value can be mapped.
+ private static final EnumSet<AddressField> SINGLE_VALUE_FIELDS;
+ static {
+ SINGLE_VALUE_FIELDS = EnumSet.allOf(AddressField.class);
+ SINGLE_VALUE_FIELDS.removeAll(ADDRESS_LINE_FIELDS);
+ SINGLE_VALUE_FIELDS.remove(AddressField.STREET_ADDRESS);
+ }
+
+ // When this is merged for use by GWT, remember to add @NonFinalForGwt in place of final fields.
+
+ // Detailed information on these fields is available in the javadoc for their respective getters.
+
+ // CLDR (Common Locale Data Repository) country code.
+ private final String postalCountry;
+
+ // The most specific part of any address. They may be left empty if more detailed fields are used
+ // instead, or they may be used in addition to these if the more detailed fields do not fulfill
+ // requirements, or they may be used instead of more detailed fields to represent the street-level
+ // part.
+ private final List<String> addressLines;
+
+ // Top-level administrative subdivision of this country.
+ private final String administrativeArea;
+
+ // Generally refers to the city/town portion of an address.
+ private final String locality;
+
+ // Dependent locality or sublocality. Used for neighborhoods or suburbs.
+ private final String dependentLocality;
+
+ // Values are frequently alphanumeric.
+ private final String postalCode;
+
+ // This corresponds to the SortingCode sub-element of the xAL PostalServiceElements element.
+ // Use is very country-specific.
+ private final String sortingCode;
+
+ // The firm or organization. This goes at a finer granularity than address lines in the address.
+ private final String organization;
+
+ // The recipient. This goes at a finer granularity than address lines in the address. Not present
+ // in xAL.
+ private final String recipient;
+
+ // BCP-47 language code for the address. Can be set to null.
+ private final String languageCode;
+
+ // NOTE: If you add a new field which is semantically significant, you must also add a check for
+ // that field in {@link equals} and {@link hashCode}.
+
+ private AddressData(Builder builder) {
+ this.postalCountry = builder.fields.get(AddressField.COUNTRY);
+ this.administrativeArea = builder.fields.get(AddressField.ADMIN_AREA);
+ this.locality = builder.fields.get(AddressField.LOCALITY);
+ this.dependentLocality = builder.fields.get(AddressField.DEPENDENT_LOCALITY);
+ this.postalCode = builder.fields.get(AddressField.POSTAL_CODE);
+ this.sortingCode = builder.fields.get(AddressField.SORTING_CODE);
+ this.organization = builder.fields.get(AddressField.ORGANIZATION);
+ this.recipient = builder.fields.get(AddressField.RECIPIENT);
+ this.addressLines = Collections.unmodifiableList(
+ normalizeAddressLines(new ArrayList<String>(builder.addressLines)));
+ this.languageCode = builder.language;
+ }
+
+ // Helper to normalize a list of address lines. The input may contain null entries or strings
+ // which must be split into multiple lines. The resulting list entries will be trimmed
+ // consistently with String.trim() and any empty results are ignored.
+ // TODO: Trim entries properly with respect to Unicode whitespace.
+ private static List<String> normalizeAddressLines(List<String> lines) {
+ // Guava equivalent code for each line would look something like:
+ // Splitter.on("\n").trimResults(CharMatcher.inRange('\0', ' ')).omitEmptyStrings();
+ for (int index = 0; index < lines.size(); ) {
+ String line = lines.remove(index);
+ if (line == null) {
+ continue;
+ }
+ if (line.contains("\n")) {
+ for (String splitLine : line.split("\n")) {
+ index = maybeAddTrimmedLine(splitLine, lines, index);
+ }
+ } else {
+ index = maybeAddTrimmedLine(line, lines, index);
+ }
+ }
+ return lines;
+ }
+
+ // Helper to trim a string and (if not empty) add it to the given list at the specified index.
+ // Returns the new index at which any following elements should be added.
+ private static int maybeAddTrimmedLine(String line, List<String> lines, int index) {
+ line = Util.trimToNull(line);
+ if (line != null) {
+ lines.add(index++, line);
+ }
+ return index;
+ }
+
+ /**
+ * Returns a string representation of the address, used for debugging.
+ */
+ @Override
+ public String toString() {
+ StringBuilder output = new StringBuilder("(AddressData: "
+ + "POSTAL_COUNTRY=" + postalCountry + "; "
+ + "LANGUAGE=" + languageCode + "; ");
+ for (String line : addressLines) {
+ output.append(line + "; ");
+ }
+ output.append("ADMIN_AREA=" + administrativeArea + "; "
+ + "LOCALITY=" + locality + "; "
+ + "DEPENDENT_LOCALITY=" + dependentLocality + "; "
+ + "POSTAL_CODE=" + postalCode + "; "
+ + "SORTING_CODE=" + sortingCode + "; "
+ + "ORGANIZATION=" + organization + "; "
+ + "RECIPIENT=" + recipient
+ + ")");
+ return output.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof AddressData)) {
+ return false;
+ }
+ AddressData addressData = (AddressData) o;
+
+ return (postalCountry == null
+ ? addressData.getPostalCountry() == null
+ : postalCountry.equals(addressData.getPostalCountry()))
+ && (addressLines == null
+ ? addressData.getAddressLines() == null
+ : addressLines.equals(addressData.getAddressLines()))
+ && (administrativeArea == null
+ ? addressData.getAdministrativeArea() == null
+ : this.getAdministrativeArea().equals(addressData.getAdministrativeArea()))
+ && (locality == null
+ ? addressData.getLocality() == null
+ : locality.equals(addressData.getLocality()))
+ && (dependentLocality == null
+ ? addressData.getDependentLocality() == null
+ : dependentLocality.equals(addressData.getDependentLocality()))
+ && (postalCode == null
+ ? addressData.getPostalCode() == null
+ : postalCode.equals(addressData.getPostalCode()))
+ && (sortingCode == null
+ ? addressData.getSortingCode() == null
+ : sortingCode.equals(addressData.getSortingCode()))
+ && (organization == null
+ ? addressData.getOrganization() == null
+ : organization.equals(addressData.getOrganization()))
+ && (recipient == null
+ ? addressData.getRecipient() == null
+ : recipient.equals(addressData.getRecipient()))
+ && (languageCode == null
+ ? this.getLanguageCode() == null
+ : languageCode.equals(addressData.getLanguageCode()));
+ }
+
+ @Override
+ public int hashCode() {
+ // 17 and 31 are arbitrary seed values.
+ int result = 17;
+
+ String[] fields =
+ new String[] {
+ postalCountry,
+ administrativeArea,
+ locality,
+ dependentLocality,
+ postalCode,
+ sortingCode,
+ organization,
+ recipient,
+ languageCode
+ };
+
+ for (String field : fields) {
+ result = 31 * result + (field == null ? 0 : field.hashCode());
+ }
+
+ // The only significant field which is not a String.
+ result = 31 * result + (addressLines == null ? 0 : addressLines.hashCode());
+
+ return result;
+ }
+
+ /**
+ * Returns the CLDR region code for this address; note that this is <em>not</em> the same as the
+ * ISO 3166-1 2-letter country code. While technically optional, this field will always be set
+ * by the address widget when an address is entered or edited, and will be assumed to be set by
+ * many other tools.
+ * <p>
+ * While they have most of their values in common with the CLDR region codes, the ISO 2-letter
+ * country codes have one significant disadvantage; they are not stable and values can change over
+ * time. For example {@code "CS"} was originally used to represent Czechoslovakia, but later
+ * represented Serbia and Montenegro. In contrast, CLDR region codes are never reused and can
+ * represent more regions, such as Ascension Island (AC).
+ * <p>
+ * See the page on
+ * <a href="http://unicode.org/cldr/charts/26/supplemental/territory_containment_un_m_49.html">
+ * Territory Containment</a> for a list of CLDR region codes.
+ * <p>
+ * Note that the region codes not user-presentable; "GB" is Great Britain but this should always
+ * be displayed to a user as "UK" or "United Kingdom".
+ */
+ public String getPostalCountry() {
+ return postalCountry;
+ }
+
+ /**
+ * Returns multiple free-form address lines representing low level parts of an address,
+ * possibly empty. The first line represents the lowest level part of the address, other than
+ * recipient or organization.
+ * <p>
+ * Note that the number of lines returned by this method may be greater than the number of lines
+ * set on the original builder if some of the lines contained embedded newlines. The values
+ * returned by this method will never contain embedded newlines.
+ * <p>
+ * For example:
+ * <pre>{@code
+ * data = AddressData.builder()
+ * .setAddressLine1("First line\nSecond line")
+ * .setAddressLine2("Last line")
+ * .build();
+ * // We end up with 3 lines in the final AddressData instance:
+ * // data.getAddressLines() == [ "First line", "Second line", "Last line" ]
+ * }</pre>
+ */
+ public List<String> getAddressLines() {
+ return addressLines;
+ }
+
+ /** @deprecated Use {@link #getAddressLines} in preference. */
+ @Deprecated
+ public String getAddressLine1() {
+ return getAddressLine(1);
+ }
+
+ /** @deprecated Use {@link #getAddressLines} in preference. */
+ @Deprecated
+ public String getAddressLine2() {
+ return getAddressLine(2);
+ }
+
+ // Helper for returning the Nth address line. This is split out here so that it's easily to
+ // change the maximum number of address lines we support.
+ private String getAddressLine(int lineNumber) {
+ // If not the last available line, OR if we're the last line but there are no extra lines...
+ if (lineNumber < ADDRESS_LINE_COUNT || lineNumber >= addressLines.size()) {
+ return (lineNumber <= addressLines.size()) ? addressLines.get(lineNumber - 1) : null;
+ }
+ // We're asking for the last available line and there are additional lines in the data.
+ // Here it should be true that: lineNumber == ADDRESS_LINE_COUNT
+ // Guava equivalent:
+ // return Joiner.on(", ")
+ // .join(addressLines.subList(ADDRESS_LINE_COUNT - 1, addressLines.size()));
+ StringBuilder joinedLastLine = new StringBuilder(addressLines.get(lineNumber - 1));
+ for (String line : addressLines.subList(lineNumber, addressLines.size())) {
+ joinedLastLine.append(", ").append(line);
+ }
+ return joinedLastLine.toString();
+ }
+
+ /**
+ * Returns the top-level administrative subdivision of this country. Different postal countries
+ * use different names to refer to their administrative areas. For example: "state" (US), "region"
+ * (Italy) or "prefecture" (Japan).
+ * <p>
+ * Where data is available, the user will be able to select the administrative area name from a
+ * drop-down list, ensuring that it has only expected values. However this is not always possible
+ * and no strong assumptions about validity should be made by the user for this value.
+ */
+ public String getAdministrativeArea() {
+ return administrativeArea;
+ }
+
+ /**
+ * Returns the language specific locality, if present. The usage of this field varies by region,
+ * but it generally refers to the "city" or "town" of the address. Some regions do not use this
+ * field; their address lines combined with things like postal code or administrative area are
+ * sufficient to locate an address.
+ * <p>
+ * Different countries use different names to refer to their localities. For example: "city" (US),
+ * "comune" (Italy) or "post town" (Great Britain). For Japan this would return the shikuchouson
+ * and sub-shikuchouson.
+ */
+ public String getLocality() {
+ return locality;
+ }
+
+ /**
+ * Returns the dependent locality, if present.
+ * <p>
+ * This is used for neighborhoods and suburbs. Typically a dependent locality will represent a
+ * smaller geographical area than a locality, but need not be contained within it.
+ */
+ public String getDependentLocality() {
+ return dependentLocality;
+ }
+
+ /**
+ * Returns the postal code of the address, if present. This value is not language specific but
+ * may contain arbitrary formatting characters such as spaces or hyphens and might require
+ * normalization before any meaningful comparison of values.
+ * <p>
+ * For example: "94043", "94043-1351", "SW1W", "SW1W 9TQ".
+ */
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ /**
+ * Returns the sorting code if present. Sorting codes are distinct from postal codes and only
+ * used in a handful of regions (eg, France).
+ * <p>
+ * For example in France this field would contain a
+ * <a href="http://en.wikipedia.org/wiki/List_of_postal_codes_in_France">CEDEX</a> value.
+ */
+ public String getSortingCode() {
+ return sortingCode;
+ }
+
+ /**
+ * Returns the free form organization string, if present. No assumptions should be made about
+ * the contents of this field. This field exists because in some situations the organization
+ * and recipient fields must be treated specially during formatting. It is not a good idea to
+ * allow users to enter the organization or recipient in the street address lines as this will
+ * result in badly formatted and non-geocodeable addresses.
+ */
+ public String getOrganization() {
+ return organization;
+ }
+
+ /**
+ * Returns the free form recipient string, if present. No assumptions should be made about the
+ * contents of this field. This field exists because in some situations the organization
+ * and recipient fields must be treated specially during formatting. It is not a good idea to
+ * allow users to enter the organization or recipient in the street address lines as this will
+ * result in badly formatted and non-geocodeable addresses.
+ */
+ public String getRecipient() {
+ return recipient;
+ }
+
+ /**
+ * Returns a value for those address fields which map to a single string value.
+ * <p>
+ * Note that while it is possible to pass {@link AddressField#ADDRESS_LINE_1} and
+ * {@link AddressField#ADDRESS_LINE_2} into this method, these fields are deprecated and will be
+ * removed. In general you should be using named methods to obtain specific values for the address
+ * (eg, {@link #getAddressLines()}) and avoid iterating in a general way over the fields.
+ * This method has very little value outside of the widget itself and is scheduled for removal.
+ *
+ * @deprecated Do not use; scheduled for removal from the public API.
+ */
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ // TODO: Move this to a utility method rather than exposing it in the public API.
+ public String getFieldValue(AddressField field) {
+ switch (field) {
+ case COUNTRY:
+ return postalCountry;
+ case ADMIN_AREA:
+ return administrativeArea;
+ case LOCALITY:
+ return locality;
+ case DEPENDENT_LOCALITY:
+ return dependentLocality;
+ case POSTAL_CODE:
+ return postalCode;
+ case SORTING_CODE:
+ return sortingCode;
+ case ADDRESS_LINE_1:
+ return getAddressLine1();
+ case ADDRESS_LINE_2:
+ return getAddressLine2();
+ case ORGANIZATION:
+ return organization;
+ case RECIPIENT:
+ return recipient;
+ default:
+ throw new IllegalArgumentException("multi-value fields not supported: " + field);
+ }
+ }
+
+ /**
+ * Returns the BCP-47 language code for this address which defines the language we expect to be
+ * used for any language specific fields. If this method returns {@code null} then the language
+ * is assumed to be in the default (most used) language for the region code of the address;
+ * although the precise determination of a default language is often approximate and may change
+ * over time. Wherever possible it is recommended to construct {@code AddressData} instances
+ * with a specific language code.
+ * <p>
+ * Languages are used to guide how the address is <a
+ * href="http://en.wikipedia.org/wiki/Mailing_address_format_by_country"> formatted for
+ * display</a>. The same address may have different {@link AddressData} representations in
+ * different languages. For example, the French name of "New Mexico" is "Nouveau-Mexique".
+ */
+ public String getLanguageCode() {
+ return languageCode;
+ }
+
+ /** Returns a new builder to construct an {@code AddressData} instance. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Returns a new builder to construct an {@code AddressData} instance. */
+ public static Builder builder(AddressData address) {
+ return builder().set(address);
+ }
+
+ /** Builder for AddressData. */
+ public static final class Builder {
+ // A map of single value address fields to their values.
+ private final Map<AddressField, String> fields = new HashMap<AddressField, String>();
+ // The address lines, not normalized.
+ private final List<String> addressLines = new ArrayList<String>();
+ // The BCP-47 language of the address.
+ private String language = null;
+
+ /**
+ * Constructs an empty builder for AddressData instances. Prefer to use one of the
+ * {@link AddressData#builder} methods in preference to this.
+ */
+ // TODO: Migrate users and make this private.
+ public Builder() {}
+
+ /**
+ * Constructs a builder for AddressData instances using data from the given address.
+ * Prefer to use one of the {@link AddressData#builder} methods in preference to this.
+ *
+ * @deprecated Use the builder() methods on AddressData in preference to this.
+ */
+ @Deprecated
+ // TODO: Migrate users and delete this method.
+ public Builder(AddressData address) {
+ set(address);
+ }
+
+ /**
+ * Sets the 2-letter CLDR region code of the address; see
+ * {@link AddressData#getPostalCountry()}. Unlike other values passed to the builder, the
+ * region code can never be null.
+ *
+ * @param regionCode the CLDR region code of the address.
+ */
+ // TODO: Rename to setRegionCode.
+ public Builder setCountry(String regionCode) {
+ return set(AddressField.COUNTRY, checkNotNull(regionCode));
+ }
+
+ /**
+ * Sets or clears the administrative area of the address; see
+ * {@link AddressData#getAdministrativeArea()}.
+ *
+ * @param adminAreaName the administrative area name, or null to clear an existing value.
+ */
+ // TODO: Rename to setAdministrativeArea.
+ public Builder setAdminArea(String adminAreaName) {
+ return set(AddressField.ADMIN_AREA, adminAreaName);
+ }
+
+ /**
+ * Sets or clears the locality of the address; see {@link AddressData#getLocality()}.
+ *
+ * @param locality the locality name, or null to clear an existing value.
+ */
+ public Builder setLocality(String locality) {
+ return set(AddressField.LOCALITY, locality);
+ }
+
+ /**
+ * Sets or clears the dependent locality of the address; see
+ * {@link AddressData#getDependentLocality()}.
+ *
+ * @param dependentLocality the dependent locality name, or null to clear an existing value.
+ */
+ public Builder setDependentLocality(String dependentLocality) {
+ return set(AddressField.DEPENDENT_LOCALITY, dependentLocality);
+ }
+
+ /**
+ * Sets or clears the postal code of the address; see {@link AddressData#getPostalCode()}.
+ *
+ * @param postalCode the postal code, or null to clear an existing value.
+ */
+ public Builder setPostalCode(String postalCode) {
+ return set(AddressField.POSTAL_CODE, postalCode);
+ }
+
+ /**
+ * Sets or clears the sorting code of the address; see {@link AddressData#getSortingCode()}.
+ *
+ * @param sortingCode the sorting code, or null to clear an existing value.
+ */
+ public Builder setSortingCode(String sortingCode) {
+ return set(AddressField.SORTING_CODE, sortingCode);
+ }
+
+ /**
+ * Sets or clears the BCP-47 language code for this address (eg, "en" or "zh-Hant"). If the
+ * language is not set, then the address will be assumed to be in the default language of the
+ * country of the address; however it is highly discouraged to rely on this as the default
+ * language may change over time. See {@link AddressData#getLanguageCode()}.
+ *
+ * @param languageCode the BCP-47 language code, or null to clear an existing value.
+ */
+ public Builder setLanguageCode(String languageCode) {
+ this.language = languageCode;
+ return this;
+ }
+
+ /**
+ * Sets multiple unstructured street level lines in the address. Calling this method will
+ * always discard any existing address lines before adding new ones.
+ * <p>
+ * Note that the number of lines set by this method is preserved in the builder's state but a
+ * single line set here may result in multiple lines in the resulting {@code AddressData}
+ * instance if it contains embedded newline characters.
+ * <p>
+ * For example:
+ * <pre>{@code
+ * data = AddressData.builder()
+ * .setAddressLines(Arrays.asList("First line\nSecond line"))
+ * .setAddressLine2("Last line");
+ * .build();
+ * // data.getAddressLines() == [ "First line", "Second line", "Last line" ]
+ * }</pre>
+ */
+ public Builder setAddressLines(Iterable<String> lines) {
+ addressLines.clear();
+ for (String line : lines) {
+ addressLines.add(line);
+ }
+ return this;
+ }
+
+ /**
+ * Adds another address line. Embedded newlines will be normalized when "build()" is called.
+ */
+ // TODO: Consider removing this method if nobody is using it to simplify the API.
+ public Builder addAddressLine(String value) {
+ addressLines.add(value);
+ return this;
+ }
+
+ /**
+ * Sets multiple street lines from a single street string, clearing any existing address lines
+ * first. The input string may contain new lines which will result in multiple separate lines
+ * in the resulting {@code AddressData} instance. After splitting, each line is trimmed and
+ * empty lines are ignored.
+ * <p>
+ * Example: {@code " \n \n1600 Amphitheatre Ave\n\nRoom 122"} will set the lines:
+ * <ol>
+ * <li>"1600 Amphitheatre Ave"
+ * <li>"Room 122"
+ * </ol>
+ *
+ * @param value a string containing one or more address lines, separated by {@code "\n"}.
+ */
+ public Builder setAddress(String value) {
+ addressLines.clear();
+ addressLines.add(value);
+ normalizeAddressLines(addressLines);
+ return this;
+ }
+
+ /**
+ * Copies all the data of the given address into the builder. Any existing data in the builder
+ * is discarded.
+ */
+ public Builder set(AddressData data) {
+ fields.clear();
+ for (AddressField addressField : SINGLE_VALUE_FIELDS) {
+ set(addressField, data.getFieldValue(addressField));
+ }
+ addressLines.clear();
+ addressLines.addAll(data.addressLines);
+ setLanguageCode(data.getLanguageCode());
+ return this;
+ }
+
+ /**
+ * TODO: Remove this method in favor of setAddressLines(Iterable<String>).
+ *
+ * @deprecated Use {@link #setAddressLines} instead.
+ */
+ @Deprecated
+ public Builder setAddressLine1(String value) {
+ return setAddressLine(1, value);
+ }
+
+ /**
+ * TODO: Remove this method in favor of setAddressLines(Iterable<String>).
+ *
+ * @deprecated Use {@link #setAddressLines} instead.
+ */
+ @Deprecated
+ public Builder setAddressLine2(String value) {
+ return setAddressLine(2, value);
+ }
+
+ /**
+ * Sets or clears the organization of the address; see {@link AddressData#getOrganization()}.
+ *
+ * @param organization the organization, or null to clear an existing value.
+ */
+ public Builder setOrganization(String organization) {
+ return set(AddressField.ORGANIZATION, organization);
+ }
+
+ /**
+ * Sets or clears the recipient of the address; see {@link AddressData#getRecipient()}.
+ *
+ * @param recipient the recipient, or null to clear an existing value.
+ */
+ public Builder setRecipient(String recipient) {
+ return set(AddressField.RECIPIENT, recipient);
+ }
+
+ /**
+ * Sets an address field with the specified value. If the value is empty (null or whitespace),
+ * the original value associated with the field will be removed.
+ *
+ * @deprecated Do not use; scheduled for removal from the public API.
+ */
+ @Deprecated
+ @SuppressWarnings("deprecation")
+ // TODO: Reimplement using public API as a utility function in AddressWidget (the only caller).
+ public Builder set(AddressField field, String value) {
+ if (SINGLE_VALUE_FIELDS.contains(field)) {
+ value = Util.trimToNull(value);
+ if (value == null) {
+ fields.remove(field);
+ } else {
+ fields.put(field, value);
+ }
+ } else if (field == AddressField.STREET_ADDRESS) {
+ if (value == null) {
+ addressLines.clear();
+ } else {
+ setAddress(value);
+ }
+ } else {
+ int lineNum = ADDRESS_LINE_FIELDS.indexOf(field) + 1;
+ if (lineNum > 0) {
+ setAddressLine(lineNum, value);
+ }
+ }
+ return this;
+ }
+
+ // This may preserve whitespace at the ends of lines, but this gets normalized when we build
+ // the data instance.
+ private Builder setAddressLine(int lineNum, String value) {
+ if (Util.trimToNull(value) == null) {
+ if (lineNum < addressLines.size()) {
+ // Clearing an element that isn't the last in the list.
+ addressLines.set(lineNum - 1, null);
+ } else if (lineNum == addressLines.size()) {
+ // Clearing the last element (remove it and clear up trailing nulls).
+ addressLines.remove(lineNum - 1);
+ for (int i = addressLines.size() - 1; i >= 0 && addressLines.get(i) == null; i--) {
+ addressLines.remove(i);
+ }
+ }
+ } else {
+ // Padding the list with nulls if necessary.
+ for (int i = addressLines.size(); i < lineNum; i++) {
+ addressLines.add(null);
+ }
+ // Set the non-null value.
+ addressLines.set(lineNum - 1, value);
+ }
+ return this;
+ }
+
+ /**
+ * Builds an AddressData instance from the current state of the builder. A builder instance may
+ * be used to build multiple data instances.
+ * <p>
+ * During building the street address line information is normalized and the following will be
+ * true for any build instance.
+ * <ol>
+ * <li>The order of address lines is retained relative to the builder.
+ * <li>Empty address lines (empty strings, whitespace only or null) are removed.
+ * <li>Remaining address lines are trimmed of whitespace.
+ * </ol>
+ */
+ public AddressData build() {
+ return new AddressData(this);
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressDataKey.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressDataKey.java
new file mode 100644
index 00000000000..3b8fb49c74f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressDataKey.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Enumerates all the data fields found in the JSON-format address property data that are used by
+ * the Android Address Input Widget.
+ */
+public enum AddressDataKey {
+ /**
+ * Identifies the countries for which data is provided.
+ */
+ COUNTRIES,
+ /**
+ * The standard format string. This identifies which fields can be used in the address, along with
+ * their order. This also carries additional information for use in formatting the fields into
+ * multiple lines. This is also used to indicate which fields should _not_ be used for an address.
+ */
+ FMT,
+ /**
+ * The unique ID of the region, in the form of a path from parent IDs to the key.
+ */
+ ID,
+ /**
+ * The key of the region, unique to its parent. If there is an accepted abbreviation for this
+ * region, then the key will be set to this and name will be set to the local name for this
+ * region. If there is no accepted abbreviation, then this key will be the local name and there
+ * will be no local name specified. This value must be present.
+ */
+ KEY,
+ /**
+ * The default language of any data for this region, if known.
+ */
+ LANG,
+ /**
+ * The languages used by any data for this region, if known.
+ */
+ LANGUAGES,
+ /**
+ * The latin format string {@link #FMT} used when a country defines an alternative format for
+ * use with the latin script, such as in China.
+ */
+ LFMT,
+ /**
+ * Indicates the type of the name used for the locality (city) field.
+ */
+ LOCALITY_NAME_TYPE,
+ /**
+ * Indicates which fields must be present in a valid address.
+ */
+ REQUIRE,
+ /**
+ * Indicates the type of the name used for the state (administrative area) field.
+ */
+ STATE_NAME_TYPE,
+ /**
+ * Indicates the type of the name used for the sublocality field.
+ */
+ SUBLOCALITY_NAME_TYPE,
+ /**
+ * Encodes the {@link #KEY} value of all the children of this region.
+ */
+ SUB_KEYS,
+ /**
+ * Encodes the transliterated latin name value of all the children of this region, if the local
+ * names are not in latin script already.
+ */
+ SUB_LNAMES,
+ /**
+ * Indicates, for each child of this region, whether that child has additional children.
+ */
+ SUB_MORES,
+ /**
+ * Encodes the local name value of all the children of this region.
+ */
+ SUB_NAMES,
+ /**
+ * Encodes width overrides for specific fields.
+ */
+ WIDTH_OVERRIDES,
+ /**
+ * Encodes the {@link #ZIP} value for the subtree beneath this region.
+ */
+ XZIP,
+ /**
+ * Encodes the postal code pattern if at the country level, and the postal code prefix if at a
+ * level below country.
+ */
+ ZIP,
+ /**
+ * Indicates the type of the name used for the ZIP (postal code) field.
+ */
+ ZIP_NAME_TYPE;
+
+ /**
+ * Returns a field based on its keyname (value in the JSON-format file), or null if no field
+ * matches.
+ */
+ static AddressDataKey get(String keyname) {
+ return ADDRESS_KEY_NAME_MAP.get(Util.toLowerCaseLocaleIndependent(keyname));
+ }
+
+ private static final Map<String, AddressDataKey> ADDRESS_KEY_NAME_MAP =
+ new HashMap<String, AddressDataKey>();
+
+ static {
+ // Populates the map of enums against their lower-cased string values for easy look-up.
+ for (AddressDataKey field : values()) {
+ ADDRESS_KEY_NAME_MAP.put(Util.toLowerCaseLocaleIndependent(field.toString()), field);
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressField.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressField.java
new file mode 100644
index 00000000000..08e570e119d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressField.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Identifiers for the input fields of the address widget, used to control options related to
+ * visibility and ordering of UI elements. Note that one {@code AddressField} may represent more
+ * than one input field in the UI (eg, {@link #STREET_ADDRESS}), but each input field can be
+ * identified by exactly one {@code AddressField}.
+ * <p>
+ * In certain use cases not all fields are necessary, and you can hide fields using
+ * {@link FormOptions#setHidden}. An example of this is when you are collecting postal addresses not
+ * intended for delivery and wish to suppress the collection of a recipient's name or organization.
+ * <p>
+ * An alternative to hiding fields is to make them read-only, using {@link FormOptions#setReadonly}.
+ * An example of this would be in the case that the country of an address was already determined but
+ * we wish to make it clear to the user that we have already taken it into account and do not want
+ * it entered again.
+ *
+ * @see FormOptions
+ */
+public enum AddressField {
+ /** The drop-down menu used to select a region for {@link AddressData#getPostalCountry()}. */
+ COUNTRY('R'),
+ /**
+ * The input field used to enter a value for {@link AddressData#getAddressLine1()}.
+ * @deprecated Use {@link #STREET_ADDRESS} instead.
+ */
+ @Deprecated
+ ADDRESS_LINE_1('1'),
+ /**
+ * The input field used to enter a value for {@link AddressData#getAddressLine2()}.
+ * @deprecated Use {@link #STREET_ADDRESS} instead.
+ */
+ @Deprecated
+ ADDRESS_LINE_2('2'),
+ /** The input field(s) used to enter values for {@link AddressData#getAddressLines()}. */
+ STREET_ADDRESS('A'),
+ /** The input field used to enter a value for {@link AddressData#getAdministrativeArea()}. */
+ ADMIN_AREA('S'),
+ /** The input field used to enter a value for {@link AddressData#getLocality()}. */
+ LOCALITY('C'),
+ /** The input field used to enter a value for {@link AddressData#getDependentLocality()}. */
+ DEPENDENT_LOCALITY('D'),
+ /** The input field used to enter a value for {@link AddressData#getPostalCode()}. */
+ POSTAL_CODE('Z'),
+ /** The input field used to enter a value for {@link AddressData#getSortingCode()}. */
+ SORTING_CODE('X'),
+
+ /** The input field used to enter a value for {@link AddressData#getRecipient()}. */
+ RECIPIENT('N'),
+ /** The input field used to enter a value for {@link AddressData#getOrganization()}. */
+ ORGANIZATION('O');
+
+ /** Classification of the visual width of address input fields. */
+ public enum WidthType {
+ /**
+ * Identifies an input field as accepting full-width input, such as address lines or recipient.
+ */
+ LONG,
+ /**
+ * Identifies an input field as accepting short (often bounded) input, such as postal code.
+ */
+ SHORT;
+
+ static WidthType of(char c) {
+ switch (c) {
+ // In case we need a 'narrow'. Map it to 'S' for now to facilitate the rollout.
+ case 'N':
+ case 'S':
+ return SHORT;
+ case 'L':
+ return LONG;
+ default:
+ throw new IllegalArgumentException("invalid width character: " + c);
+ }
+ }
+ }
+
+ private static final Map<Character, AddressField> FIELD_MAPPING;
+
+ static {
+ Map<Character, AddressField> map = new HashMap<Character, AddressField>();
+ for (AddressField value : values()) {
+ map.put(value.getChar(), value);
+ }
+ FIELD_MAPPING = Collections.unmodifiableMap(map);
+ }
+
+ // Defines the character codes used in the metadata to specify the types of fields used in
+ // address formatting. Note that the metadata also has a character for newlines, which is
+ // not defined here.
+ private final char idChar;
+
+ private AddressField(char c) {
+ this.idChar = c;
+ }
+
+ /**
+ * Returns the AddressField corresponding to the given identification character.
+ *
+ * @throws IllegalArgumentException if the identifier does not correspond to a valid field.
+ */
+ static AddressField of(char c) {
+ AddressField field = FIELD_MAPPING.get(c);
+ if (field == null) {
+ throw new IllegalArgumentException("invalid field character: " + c);
+ }
+ return field;
+ }
+
+ /**
+ * Returns the field's identification character, as used in the metadata.
+ *
+ * @return identification char.
+ */
+ char getChar() {
+ return idChar;
+ }
+
+ /**
+ * Returns default width of this address field. This may be overridden for a specific country when
+ * we have data for the possible inputs in that field and use a drop-down, rather than a text
+ * field, in the UI.
+ */
+ // TODO: We'd probably be better off just having a widthType field in the enum.
+ private WidthType getDefaultWidthType() {
+ return this.equals(POSTAL_CODE) || this.equals(SORTING_CODE) ? WidthType.SHORT : WidthType.LONG;
+ }
+
+ /**
+ * Returns default width of this address field. Takes per-country heuristics into account for
+ * text input fields. This may be overridden for a specific country when we have data for the
+ * possible inputs in that field and use a drop-down, rather than a text field, in the UI.
+ */
+ public WidthType getWidthTypeForRegion(String regionCode) {
+ Util.checkNotNull(regionCode);
+ WidthType width = FormatInterpreter.getWidthOverride(this, regionCode);
+ return width != null ? width : getDefaultWidthType();
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblemType.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblemType.java
new file mode 100644
index 00000000000..5afdbe93126
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblemType.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * Enumerates problems that default address verification can report.
+ */
+// This is an external class and part of the widget's public API.
+// TODO: Review public API for external classes.
+public enum AddressProblemType {
+ /**
+ * The field is not null and not whitespace, and the field should not be used by addresses in this
+ * country.
+ * <p>
+ * For example, in the U.S. the SORTING_CODE field is unused, so its presence is an
+ * error. This cannot happen when using the Address Widget to enter an address.
+ */
+ UNEXPECTED_FIELD,
+
+ /**
+ * The field is null or whitespace, and the field is required for addresses in this country.
+ * <p>
+ * For example, in the U.S. ADMIN_AREA is a required field.
+ */
+ MISSING_REQUIRED_FIELD,
+
+ /**
+ * A list of values for the field is defined and the value does not occur in the list. Applies
+ * to hierarchical elements like REGION, ADMIN_AREA, LOCALITY, and DEPENDENT_LOCALITY.
+ *
+ * <p>For example, in the U.S. the only valid values for ADMIN_AREA are the two-letter state
+ * codes.
+ */
+ UNKNOWN_VALUE,
+
+ /**
+ * A format for the field is defined and the value does not match. This is used to match
+ * POSTAL_CODE against the general format pattern. Formats indicate how many digits/letters should
+ * be present, and what punctuation is allowed.
+ * <p>
+ * For example, in the U.S. postal codes are five digits with an optional hyphen followed by
+ * four digits.
+ */
+ INVALID_FORMAT,
+
+ /**
+ * A specific pattern for the field is defined and the value does not match. This is used to match
+ * example) and the value does not match. This is used to match POSTAL_CODE against a regular
+ * expression.
+ * <p>
+ * For example, in the US postal codes in the state of California start with a '9'.
+ */
+ MISMATCHING_VALUE;
+
+ /**
+ * Returns a unique string identifying this problem (for use in a message catalog).
+ */
+ public String keyname() {
+ return Util.toLowerCaseLocaleIndependent(name());
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblems.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblems.java
new file mode 100644
index 00000000000..906d56aa88d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressProblems.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This structure keeps track of any errors found when validating the AddressData.
+ */
+// This is an external class and part of the widget's public API.
+// TODO: Review public API for external classes and tidy JavaDoc.
+public final class AddressProblems {
+ private Map<AddressField, AddressProblemType> problems =
+ new HashMap<AddressField, AddressProblemType>();
+
+ /**
+ * Adds a problem of the given type for the given address field. Only one address problem is
+ * saved per address field.
+ */
+ void add(AddressField addressField, AddressProblemType problem) {
+ problems.put(addressField, problem);
+ }
+
+ /**
+ * Returns true if no problems have been added.
+ */
+ public boolean isEmpty() {
+ return problems.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return problems.toString();
+ }
+
+ public void clear() {
+ problems.clear();
+ }
+
+ /**
+ * Returns null if no problems exists.
+ */
+ public AddressProblemType getProblem(AddressField addressField) {
+ return problems.get(addressField);
+ }
+
+ /**
+ * This will return an empty map if there are no problems.
+ */
+ public Map<AddressField, AddressProblemType> getProblems() {
+ return problems;
+ }
+
+ /**
+ * Adds all problems this object contains to the given {@link AddressProblems} object.
+ */
+ public void copyInto(AddressProblems other) {
+ for (Entry<AddressField, AddressProblemType> problem : problems.entrySet()) {
+ other.add(problem.getKey(), problem.getValue());
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationData.java
new file mode 100644
index 00000000000..35a1e7844c0
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Wraps a Map of address property data to provide the AddressVerificationData API.
+ */
+public final class AddressVerificationData implements DataSource {
+ private final Map<String, String> propertiesMap;
+
+ private static final Pattern KEY_VALUES_PATTERN = Pattern.compile("\"([^\"]+)\":\"([^\"]*)\"");
+
+ private static final Pattern SEPARATOR_PATTERN = Pattern.compile("\",\"");
+
+ /**
+ * Constructs from a map of address property data. This keeps a reference to the map. This
+ * does not mutate the map. The map should not be mutated subsequent to this call.
+ */
+ public AddressVerificationData(Map<String, String> propertiesMap) {
+ this.propertiesMap = propertiesMap;
+ }
+
+ /**
+ * This will not return null.
+ */
+ @Override
+ public AddressVerificationNodeData getDefaultData(String key) {
+ // gets country key
+ if (key.split("/").length > 1) {
+ String[] parts = key.split("/");
+ key = parts[0] + "/" + parts[1];
+ }
+
+ AddressVerificationNodeData data = get(key);
+ if (data == null) {
+ throw new RuntimeException("failed to get default data with key " + key);
+ }
+ return data;
+ }
+
+ @Override
+ public AddressVerificationNodeData get(String key) {
+ String json = propertiesMap.get(key);
+ if (json != null && isValidDataKey(key)) {
+ return createNodeData(json);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a set of the keys for which verification data is provided. The returned set is
+ * immutable.
+ */
+ Set<String> keys() {
+ Set<String> result = new HashSet<String>();
+ for (String key : propertiesMap.keySet()) {
+ if (isValidDataKey(key)) {
+ result.add(key);
+ }
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ /**
+ * Returns whether the key is a "data" key rather than an "examples" key.
+ */
+ private boolean isValidDataKey(String key) {
+ return key.startsWith("data");
+ }
+
+ /**
+ * Returns the contents of the JSON-format string as a map.
+ */
+ AddressVerificationNodeData createNodeData(String json) {
+ // Remove leading and trailing { and }.
+ json = json.substring(1, json.length() - 1);
+ Map<AddressDataKey, String> map = new EnumMap<AddressDataKey, String>(AddressDataKey.class);
+
+ // our objects are very simple so we parse manually
+ // - no double quotes within strings
+ // - no extra spaces
+ // can't use split "," since some data has commas in it.
+ Matcher sm = SEPARATOR_PATTERN.matcher(json);
+ int pos = 0;
+ while (pos < json.length()) {
+ String pair;
+ if (sm.find()) {
+ pair = json.substring(pos, sm.start() + 1);
+ pos = sm.start() + 2;
+ } else {
+ pair = json.substring(pos);
+ pos = json.length();
+ }
+
+ Matcher m = KEY_VALUES_PATTERN.matcher(pair);
+ if (m.matches()) {
+ String value = m.group(2);
+
+ // Remove escaped backslashes.
+ // Java regex doesn't handle a replacement String consisting of
+ // a single backslash, and treats a replacement String consisting of
+ // two backslashes as two backslashes instead of one. So there's
+ // no way to use regex to replace a match with a single backslash,
+ // apparently.
+ if (value.length() > 0) {
+ char[] linechars = m.group(2).toCharArray();
+ int w = 1;
+ for (int r = w; r < linechars.length; ++r) {
+ char c = linechars[r];
+ if (c == '\\' && linechars[w - 1] == '\\') {
+ // don't increment w;
+ continue;
+ }
+ linechars[w++] = c;
+ }
+ value = new String(linechars, 0, w);
+ }
+
+ AddressDataKey df = AddressDataKey.get(m.group(1));
+ if (df == null) {
+ // Skip this data - it isn't used in the Android version.
+ } else {
+ map.put(df, value);
+ }
+ } else {
+ // This is a runtime data sanity check. The data should be
+ // checked when the data is built. The JSON data string should
+ // be parsable into string pairs using SEP_PAT.
+ throw new RuntimeException("could not match '" + pair + "' in '" + json + "'");
+ }
+ }
+
+ return new AddressVerificationNodeData(map);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationNodeData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationNodeData.java
new file mode 100644
index 00000000000..125120b7fc2
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AddressVerificationNodeData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.Map;
+
+/**
+ * A map of {@link AddressDataKey}s to JSON strings. Provides data for a single node in the address
+ * data hierarchy (for example, "data/US/CA"). Key is an AddressDataKey and the value is the raw
+ * string representing that data. This is either a single string, or an array of strings represented
+ * as a single string using '~' to separate the elements of the array, depending on the
+ * AddressDataKey.
+ */
+public final class AddressVerificationNodeData {
+ private final Map<AddressDataKey, String> map;
+
+ public AddressVerificationNodeData(Map<AddressDataKey, String> map) {
+ Util.checkNotNull(map, "Cannot construct StandardNodeData with null map");
+ this.map = map;
+ }
+
+ public boolean containsKey(AddressDataKey key) {
+ return map.containsKey(key);
+ }
+
+ /**
+ * Gets the value for a particular key in the map.
+ */
+ public String get(AddressDataKey key) {
+ return map.get(key);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AsyncRequestApi.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AsyncRequestApi.java
new file mode 100644
index 00000000000..cf5f74aeca1
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/AsyncRequestApi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * Abstracted low-level network request API. Implementations making real network requests should
+ * attempt to redirect and retry as necessary such that any failure can be considered definitive, as
+ * the users of this interface will never retry failed requests.
+ */
+public interface AsyncRequestApi {
+ /**
+ * Requests JSON metadata from the given URL and invokes the appropriate methods in the given
+ * callback. If the given callback is null, the asynchronous request is still made but no callback
+ * methods are invoked. If the given timeout is exceeded then the implementation should, where
+ * feasible, attempt to cancel the in-progress network request, but must always invoke the
+ * {@link AsyncCallback#onFailure()} callback a short, bounded time after the timeout occurred.
+ *
+ * @param url the complete URL for the request
+ * @param callback the optional callback to be invoked when the request is complete
+ * @param timeoutMillis the timeout for the request in milliseconds
+ */
+ void requestObject(String url, AsyncCallback callback, int timeoutMillis);
+
+ /**
+ * Callback API for network requests. One of the methods in this API will be invoked by the
+ * {@link AsyncRequestApi} implementation once the network request is complete or has timed out.
+ */
+ public interface AsyncCallback {
+ /** Invoked with the parsed JsoMap from the successful request. */
+ public void onSuccess(JsoMap result);
+
+ /** Invoked when a request has definitely failed (after possible retries, redirection ...). */
+ public void onFailure();
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/CacheData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/CacheData.java
new file mode 100644
index 00000000000..12c36b6de3d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/CacheData.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import static com.google.i18n.addressinput.common.Util.checkNotNull;
+
+import com.google.i18n.addressinput.common.AsyncRequestApi.AsyncCallback;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.logging.Logger;
+
+/**
+ * Cache for dynamic address data.
+ */
+public final class CacheData {
+ private static final Logger logger = Logger.getLogger(CacheData.class.getName());
+
+ /**
+ * Time-out value for the server to respond in milliseconds.
+ */
+ private static final int TIMEOUT = 5000;
+
+ /**
+ * URL to get address data. You can also reset it by calling {@link #setUrl(String)}.
+ */
+ private String serviceUrl;
+
+ private final AsyncRequestApi asyncRequestApi;
+
+ /**
+ * Storage for all dynamically retrieved data.
+ */
+ private final JsoMap cache;
+
+ /**
+ * CacheManager that handles caching that is needed by the client of the Address Widget.
+ */
+ private final ClientCacheManager clientCacheManager;
+
+ /**
+ * All requests that have been sent.
+ */
+ private final HashSet<String> requestedKeys = new HashSet<String>();
+
+ /**
+ * All invalid requested keys. For example, if we request a random string "asdfsdf9o", and the
+ * server responds by saying this key is invalid, it will be stored here.
+ */
+ private final HashSet<String> badKeys = new HashSet<String>();
+
+ /**
+ * Temporary store for {@code CacheListener}s. When a key is requested and still waiting for
+ * server's response, the listeners for the same key will be temporary stored here. When the
+ * server responded, these listeners will be triggered and then removed.
+ */
+ private final HashMap<LookupKey, HashSet<CacheListener>> temporaryListenerStore =
+ new HashMap<LookupKey, HashSet<CacheListener>>();
+
+ /**
+ * Creates an instance of CacheData with an empty cache, and uses no caching that is external
+ * to the AddressWidget.
+ */
+ // TODO(dbeaumont): Remove this method (avoid needless dependency on SimpleClientCacheManager).
+ public CacheData(AsyncRequestApi asyncRequestApi) {
+ this(new SimpleClientCacheManager(), asyncRequestApi);
+ }
+
+ /**
+ * Creates an instance of CacheData with an empty cache, and uses additional caching (external
+ * to the AddressWidget) specified by clientCacheManager.
+ */
+ public CacheData(ClientCacheManager clientCacheManager, AsyncRequestApi asyncRequestApi) {
+ this.clientCacheManager = clientCacheManager;
+ setUrl(this.clientCacheManager.getAddressServerUrl());
+ this.cache = JsoMap.createEmptyJsoMap();
+ this.asyncRequestApi = asyncRequestApi;
+ }
+
+ /**
+ * This constructor is meant to be used together with external caching. Use case:
+ * <p>
+ * After having finished using the address widget:
+ * <ol>
+ * <li>String allCachedData = getJsonString();
+ * <li>Cache (save) allCachedData wherever makes sense for your service / activity
+ * </ol>
+ * <p>
+ * Before using it next time:
+ * <ol>
+ * <li>Get the saved allCachedData string
+ * <li>new ClientData(new CacheData(allCachedData))
+ * </ol>
+ * <p>
+ * If you don't have any saved data you can either just pass an empty string to
+ * this constructor or use the other constructor.
+ *
+ * @param jsonString cached data from last time the class was used
+ */
+ // TODO(dbeaumont): Remove this method (if callers need to build from json string, do it first).
+ public CacheData(String jsonString, AsyncRequestApi asyncRequestApi) {
+ clientCacheManager = new SimpleClientCacheManager();
+ setUrl(clientCacheManager.getAddressServerUrl());
+ JsoMap tempMap = null;
+ try {
+ tempMap = JsoMap.buildJsoMap(jsonString);
+ } catch (JSONException jsonE) {
+ // If parsing the JSON string throws an exception, default to
+ // starting with an empty cache.
+ logger.warning("Could not parse json string, creating empty cache instead.");
+ tempMap = JsoMap.createEmptyJsoMap();
+ } finally {
+ cache = tempMap;
+ }
+ this.asyncRequestApi = asyncRequestApi;
+ }
+
+ /**
+ * Interface for all listeners to {@link CacheData} change. This is only used when multiple
+ * requests of the same key is dispatched and server has not responded yet.
+ */
+ private static interface CacheListener extends EventListener {
+ /**
+ * The function that will be called when valid data is about to be put in the cache.
+ *
+ * @param key the key for newly arrived data.
+ */
+ void onAdd(String key);
+ }
+
+ /**
+ * Class to handle JSON response.
+ */
+ private class JsonHandler {
+ /**
+ * Key for the requested data.
+ */
+ private final String key;
+
+ /**
+ * Pre-existing data for the requested key. Null is allowed.
+ */
+ private final JSONObject existingJso;
+
+ private final DataLoadListener listener;
+
+ /**
+ * Constructs a JsonHandler instance.
+ *
+ * @param key The key for requested data.
+ * @param oldJso Pre-existing data for this key or null.
+ */
+ private JsonHandler(String key, JSONObject oldJso, DataLoadListener listener) {
+ checkNotNull(key);
+ this.key = key;
+ this.existingJso = oldJso;
+ this.listener = listener;
+ }
+
+ /**
+ * Saves valid responded data to the cache once data arrives, or if the key is invalid,
+ * saves it in the invalid cache. If there is pre-existing data for the key, it will merge
+ * the new data will the old one. It also triggers {@link DataLoadListener#dataLoadingEnd()}
+ * method before it returns (even when the key is invalid, or input jso is null). This is
+ * called from a background thread.
+ *
+ * @param map The received JSON data as a map.
+ */
+ private void handleJson(JsoMap map) {
+ // Can this ever happen?
+ if (map == null) {
+ logger.warning("server returns null for key:" + key);
+ badKeys.add(key);
+ notifyListenersAfterJobDone(key);
+ triggerDataLoadingEndIfNotNull(listener);
+ return;
+ }
+
+ JSONObject json = map;
+ String idKey = Util.toLowerCaseLocaleIndependent(AddressDataKey.ID.name());
+ if (!json.has(idKey)) {
+ logger.warning("invalid or empty data returned for key: " + key);
+ badKeys.add(key);
+ notifyListenersAfterJobDone(key);
+ triggerDataLoadingEndIfNotNull(listener);
+ return;
+ }
+
+ if (existingJso != null) {
+ map.mergeData((JsoMap) existingJso);
+ }
+
+ cache.putObj(key, map);
+ notifyListenersAfterJobDone(key);
+ triggerDataLoadingEndIfNotNull(listener);
+ }
+ }
+
+ /**
+ * Sets address data server URL. Input URL cannot be null.
+ *
+ * @param url The service URL.
+ */
+ public void setUrl(String url) {
+ checkNotNull(url, "Cannot set URL of address data server to null.");
+ serviceUrl = url;
+ }
+
+ /**
+ * Gets address data server URL.
+ */
+ public String getUrl() {
+ return serviceUrl;
+ }
+
+ /**
+ * Returns a JSON string representing the data currently stored in this cache. It can be used
+ * to later create a new CacheData object containing the same cached data.
+ *
+ * @return a JSON string representing the data stored in this cache
+ */
+ public String getJsonString() {
+ return cache.toString();
+ }
+
+ /**
+ * Checks if key and its value is cached (Note that only valid ones are cached).
+ */
+ public boolean containsKey(String key) {
+ return cache.containsKey(key);
+ }
+
+ // This method is called from a background thread.
+ private void triggerDataLoadingEndIfNotNull(DataLoadListener listener) {
+ if (listener != null) {
+ listener.dataLoadingEnd();
+ }
+ }
+
+ /**
+ * Fetches data from server, or returns if the data is already cached. If the fetched data is
+ * valid, it will be added to the cache. This method also triggers {@link
+ * DataLoadListener#dataLoadingEnd()} method before it returns.
+ *
+ * @param existingJso Pre-existing data for this key or null if none.
+ * @param listener An optional listener to call when done.
+ */
+ void fetchDynamicData(
+ final LookupKey key, JSONObject existingJso, final DataLoadListener listener) {
+ checkNotNull(key, "null key not allowed.");
+
+ notifyStart(listener);
+
+ // Key is valid and cached.
+ if (cache.containsKey(key.toString())) {
+ triggerDataLoadingEndIfNotNull(listener);
+ return;
+ }
+
+ // Key is invalid and cached.
+ if (badKeys.contains(key.toString())) {
+ triggerDataLoadingEndIfNotNull(listener);
+ return;
+ }
+
+ // Already requested the key, and is still waiting for server's response.
+ if (!requestedKeys.add(key.toString())) {
+ logger.fine("data for key " + key + " requested but not cached yet");
+ addListenerToTempStore(key, new CacheListener() {
+ @Override
+ public void onAdd(String myKey) {
+ triggerDataLoadingEndIfNotNull(listener);
+ }
+ });
+ return;
+ }
+
+ // Key is in the cache maintained by the client of the AddressWidget.
+ String dataFromClientCache = clientCacheManager.get(key.toString());
+ if (dataFromClientCache != null && dataFromClientCache.length() > 0) {
+ final JsonHandler handler = new JsonHandler(key.toString(), existingJso, listener);
+ try {
+ handler.handleJson(JsoMap.buildJsoMap(dataFromClientCache));
+ return;
+ } catch (JSONException e) {
+ logger.warning("Data from client's cache is in the wrong format: " + dataFromClientCache);
+ }
+ }
+
+ // Key is not cached yet, now sending the request to the server.
+ final JsonHandler handler = new JsonHandler(key.toString(), existingJso, listener);
+ asyncRequestApi.requestObject(serviceUrl + "/" + key.toString(), new AsyncCallback() {
+ @Override
+ public void onFailure() {
+ logger.warning("Request for key " + key + " failed");
+ requestedKeys.remove(key.toString());
+ notifyListenersAfterJobDone(key.toString());
+ triggerDataLoadingEndIfNotNull(listener);
+ }
+
+ @Override
+ public void onSuccess(JsoMap result) {
+ handler.handleJson(result);
+ // Put metadata into the cache maintained by the client of the AddressWidget.
+ String dataRetrieved = result.toString();
+ clientCacheManager.put(key.toString(), dataRetrieved);
+ }
+ },
+ TIMEOUT);
+ }
+
+ /**
+ * Gets region data from our compiled-in java file and stores it in the cache. This is only called
+ * when data cannot be obtained from the server, so there will be no pre-existing data for this
+ * key.
+ */
+ void getFromRegionDataConstants(final LookupKey key) {
+ checkNotNull(key, "null key not allowed.");
+ String data = RegionDataConstants.getCountryFormatMap().get(
+ key.getValueForUpperLevelField(AddressField.COUNTRY));
+ if (data != null) {
+ try {
+ cache.putObj(key.toString(), JsoMap.buildJsoMap(data));
+ } catch (JSONException e) {
+ logger.warning("Failed to parse data for key " + key + " from RegionDataConstants");
+ }
+ }
+ }
+
+ /**
+ * Retrieves string data identified by key.
+ *
+ * @param key Non-null key. E.g., "data/US/CA".
+ * @return String value for specified key.
+ */
+ public String get(String key) {
+ checkNotNull(key, "null key not allowed");
+ return cache.get(key);
+ }
+
+ /**
+ * Retrieves JsoMap data identified by key.
+ *
+ * @param key Non-null key. E.g., "data/US/CA".
+ * @return String value for specified key.
+ */
+ public JsoMap getObj(String key) {
+ checkNotNull(key, "null key not allowed");
+ return cache.getObj(key);
+ }
+
+ /** Notifies the listener when we start loading data. */
+ private void notifyStart(DataLoadListener listener) {
+ if (listener != null) {
+ listener.dataLoadingBegin();
+ }
+ }
+
+ private void notifyListenersAfterJobDone(String key) {
+ LookupKey lookupKey = new LookupKey.Builder(key).build();
+ HashSet<CacheListener> listeners = temporaryListenerStore.get(lookupKey);
+ if (listeners != null) {
+ for (CacheListener listener : listeners) {
+ listener.onAdd(key.toString());
+ }
+ listeners.clear();
+ }
+ }
+
+ private void addListenerToTempStore(LookupKey key, CacheListener listener) {
+ checkNotNull(key);
+ checkNotNull(listener);
+ HashSet<CacheListener> listeners = temporaryListenerStore.get(key);
+ if (listeners == null) {
+ listeners = new HashSet<CacheListener>();
+ temporaryListenerStore.put(key, listeners);
+ }
+ listeners.add(listener);
+ }
+
+ /**
+ * Added for testing purposes. Adds a new object into the cache.
+ *
+ * @param id string of the format "data/country/.." ie. "data/US/CA"
+ * @param object The JSONObject to be put into cache.
+ */
+ void addToJsoMap(String id, JSONObject object) {
+ cache.putObj(id, object);
+ }
+
+ /**
+ * Added for testing purposes. Checks to see if the cache is empty,
+ *
+ * @return true if the internal cache is empty
+ */
+ boolean isEmpty() {
+ return cache.length() == 0;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientCacheManager.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientCacheManager.java
new file mode 100644
index 00000000000..22769864fcc
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientCacheManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * Used by AddressWidget to handle caching in client-specific ways.
+ */
+// This is an external class and part of the widget's public API.
+// TODO: Review public API for external classes and tidy JavaDoc.
+public interface ClientCacheManager {
+ /** Get the data that is cached for the given key. */
+ public String get(String key);
+ /** Put the data for the given key into the cache. */
+ public void put(String key, String data);
+ /** Get the URL of the server that serves address metadata. */
+ public String getAddressServerUrl();
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientData.java
new file mode 100644
index 00000000000..2b0578d3d15
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/ClientData.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import com.google.i18n.addressinput.common.LookupKey.KeyType;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Access point for the cached address verification data. The data contained here will mainly be
+ * used to build {@link FieldVerifier}'s.
+ */
+public final class ClientData implements DataSource {
+ private static final Logger logger = Logger.getLogger(ClientData.class.getName());
+
+ /**
+ * Data to bootstrap the process. The data are all regional (country level) data. Keys are like
+ * "data/US/CA"
+ */
+ private final Map<String, JsoMap> bootstrapMap = new HashMap<String, JsoMap>();
+
+ private CacheData cacheData;
+
+ public ClientData(CacheData cacheData) {
+ this.cacheData = cacheData;
+ buildRegionalData();
+ }
+
+ @Override
+ public AddressVerificationNodeData get(String key) {
+ JsoMap jso = cacheData.getObj(key);
+ if (jso == null) { // Not cached.
+ fetchDataIfNotAvailable(key);
+ jso = cacheData.getObj(key);
+ }
+ if (jso != null && isValidDataKey(key)) {
+ return createNodeData(jso);
+ }
+ return null;
+ }
+
+ @Override
+ public AddressVerificationNodeData getDefaultData(String key) {
+ // root data
+ if (key.split("/").length == 1) {
+ JsoMap jso = bootstrapMap.get(key);
+ if (jso == null || !isValidDataKey(key)) {
+ throw new RuntimeException("key " + key + " does not have bootstrap data");
+ }
+ return createNodeData(jso);
+ }
+
+ key = getCountryKey(key);
+ JsoMap jso = bootstrapMap.get(key);
+ if (jso == null || !isValidDataKey(key)) {
+ throw new RuntimeException("key " + key + " does not have bootstrap data");
+ }
+ return createNodeData(jso);
+ }
+
+ private String getCountryKey(String hierarchyKey) {
+ if (hierarchyKey.split("/").length <= 1) {
+ throw new RuntimeException("Cannot get country key with key '" + hierarchyKey + "'");
+ }
+ if (isCountryKey(hierarchyKey)) {
+ return hierarchyKey;
+ }
+
+ String[] parts = hierarchyKey.split("/");
+ return parts[0] + "/" + parts[1];
+ }
+
+ private boolean isCountryKey(String hierarchyKey) {
+ Util.checkNotNull(hierarchyKey, "Cannot use null as a key");
+ return hierarchyKey.split("/").length == 2;
+ }
+
+ /**
+ * Returns the contents of the JSON-format string as a map.
+ */
+ protected AddressVerificationNodeData createNodeData(JsoMap jso) {
+ Map<AddressDataKey, String> map = new EnumMap<AddressDataKey, String>(AddressDataKey.class);
+
+ JSONArray arr = jso.getKeys();
+ for (int i = 0; i < arr.length(); i++) {
+ try {
+ AddressDataKey key = AddressDataKey.get(arr.getString(i));
+
+ if (key == null) {
+ // Not all keys are supported by Android, so we continue if we encounter one
+ // that is not used.
+ continue;
+ }
+
+ String value = jso.get(Util.toLowerCaseLocaleIndependent(key.toString()));
+ map.put(key, value);
+ } catch (JSONException e) {
+ // This should not happen - we should not be fetching a key from outside the bounds
+ // of the array.
+ }
+ }
+
+ return new AddressVerificationNodeData(map);
+ }
+
+ /**
+ * We can be initialized with the full set of address information, but validation only uses info
+ * prefixed with "data" (in particular, no info prefixed with "examples").
+ */
+ private boolean isValidDataKey(String key) {
+ return key.startsWith("data");
+ }
+
+ /**
+ * Initializes regionalData structure based on property file.
+ */
+ private void buildRegionalData() {
+ StringBuilder countries = new StringBuilder();
+
+ for (String countryCode : RegionDataConstants.getCountryFormatMap().keySet()) {
+ countries.append(countryCode + "~");
+ String json = RegionDataConstants.getCountryFormatMap().get(countryCode);
+ JsoMap jso = null;
+ try {
+ jso = JsoMap.buildJsoMap(json);
+ } catch (JSONException e) {
+ // Ignore.
+ }
+
+ AddressData data = new AddressData.Builder().setCountry(countryCode).build();
+ LookupKey key = new LookupKey.Builder(KeyType.DATA).setAddressData(data).build();
+ bootstrapMap.put(key.toString(), jso);
+ }
+ countries.setLength(countries.length() - 1);
+
+ // TODO: this is messy. do we have better ways to do it?
+ // Creates verification data for key="data". This will be used for the root FieldVerifier.
+ String str = "{\"id\":\"data\",\"countries\": \"" + countries.toString() + "\"}";
+ JsoMap jsoData = null;
+ try {
+ jsoData = JsoMap.buildJsoMap(str);
+ } catch (JSONException e) {
+ // Ignore.
+ }
+ bootstrapMap.put("data", jsoData);
+ }
+
+ /**
+ * Fetches data from remote server if it is not cached yet.
+ *
+ * @param key The key for data that being requested. Key can be either a data key (starts with
+ * "data") or example key (starts with "examples")
+ */
+ private void fetchDataIfNotAvailable(String key) {
+ JsoMap jso = cacheData.getObj(key);
+ if (jso == null) {
+ // If there is bootstrap data for the key, pass the data to fetchDynamicData
+ JsoMap regionalData = bootstrapMap.get(key);
+ NotifyingListener listener = new NotifyingListener();
+ // If the key was invalid, we don't want to attempt to fetch it.
+ if (LookupKey.hasValidKeyPrefix(key)) {
+ LookupKey lookupKey = new LookupKey.Builder(key).build();
+ cacheData.fetchDynamicData(lookupKey, regionalData, listener);
+ try {
+ listener.waitLoadingEnd();
+ // Check to see if there is data for this key now.
+ if (cacheData.getObj(key) == null && isCountryKey(key)) {
+ // If not, see if there is data in RegionDataConstants.
+ logger.info("Server failure: looking up key in region data constants.");
+ cacheData.getFromRegionDataConstants(lookupKey);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ public void requestData(LookupKey key, DataLoadListener listener) {
+ Util.checkNotNull(key, "Null lookup key not allowed");
+ JsoMap regionalData = bootstrapMap.get(key.toString());
+ cacheData.fetchDynamicData(key, regionalData, listener);
+ }
+
+ /**
+ * Fetches all data for the specified country from the remote server.
+ */
+ public void prefetchCountry(String country, DataLoadListener listener) {
+ String key = "data/" + country;
+ Set<RecursiveLoader> loaders = new HashSet<RecursiveLoader>();
+ listener.dataLoadingBegin();
+ cacheData.fetchDynamicData(
+ new LookupKey.Builder(key).build(), null, new RecursiveLoader(key, loaders, listener));
+ }
+
+ /**
+ * A helper class to recursively load all sub keys using fetchDynamicData().
+ */
+ private class RecursiveLoader implements DataLoadListener {
+ private final String key;
+
+ private final Set<RecursiveLoader> loaders;
+
+ private final DataLoadListener listener;
+
+ public RecursiveLoader(String key, Set<RecursiveLoader> loaders, DataLoadListener listener) {
+ this.key = key;
+ this.loaders = loaders;
+ this.listener = listener;
+
+ synchronized (loaders) {
+ loaders.add(this);
+ }
+ }
+
+ @Override
+ public void dataLoadingBegin() {
+ }
+
+ @Override
+ public void dataLoadingEnd() {
+ final String subKeys = Util.toLowerCaseLocaleIndependent(AddressDataKey.SUB_KEYS.name());
+ final String subMores = Util.toLowerCaseLocaleIndependent(AddressDataKey.SUB_MORES.name());
+
+ JsoMap map = cacheData.getObj(key);
+ // It is entirely possible that data loading failed and that the map is null.
+ if (map != null && map.containsKey(subMores)) {
+ // This key could have sub keys.
+ String[] mores = map.get(subMores).split("~");
+ String[] keys = {};
+
+ if (map.containsKey(subKeys)) {
+ keys = map.get(subKeys).split("~");
+ }
+
+ if (mores.length != keys.length) { // This should never happen.
+ throw new IndexOutOfBoundsException("mores.length != keys.length");
+ }
+
+ for (int i = 0; i < mores.length; i++) {
+ if (mores[i].equalsIgnoreCase("true")) {
+ // This key should have sub keys.
+ String subKey = key + "/" + keys[i];
+ cacheData.fetchDynamicData(new LookupKey.Builder(subKey).build(), null,
+ new RecursiveLoader(subKey, loaders, listener));
+ }
+ }
+ }
+
+ // TODO: Rethink how notification is handled to avoid error-prone synchronization.
+ boolean wasLastLoader = false;
+ synchronized (loaders) {
+ wasLastLoader = loaders.remove(this) && loaders.isEmpty();
+ }
+ if (wasLastLoader) {
+ listener.dataLoadingEnd();
+ }
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataLoadListener.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataLoadListener.java
new file mode 100644
index 00000000000..89997b92fa0
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataLoadListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * Invoked when the data is fetched from the server or the cache.
+ */
+public interface DataLoadListener {
+ // These callbacks are invoked from a background thread.
+ void dataLoadingBegin();
+ void dataLoadingEnd();
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataSource.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataSource.java
new file mode 100644
index 00000000000..21d677caaa7
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/DataSource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * API for returning JSON content for a given key string (eg, "data/US"). The content is
+ * returned as a map within an {@link AddressVerificationNodeData} instance. This interface
+ * exists only to isolate this API and allow us to swap in a different version in future
+ * without risking callers depending on unexpected parts of the verifier API.
+ */
+// TODO: Add a way to load static data without using the AddressVerificationData and remove
+// this interface (along with AddressVerificationData).
+public interface DataSource {
+ /**
+ * Returns the default JSON data for the given key string (this method should complete immediately
+ * and must not trigger any network requests.
+ */
+ AddressVerificationNodeData getDefaultData(String key);
+
+ /**
+ * A <b>blocking</b> method to return the JSON data for the given key string. This method will
+ * block the current thread until data is available or until a timeout occurs (at which point the
+ * default data will be returned). All networking and failure states are hidden from the caller by
+ * this API.
+ */
+ // TODO: This is very poor API and should be changed to avoid blocking and let the caller
+ // manage requests asynchronously.
+ AddressVerificationNodeData get(String key);
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FieldVerifier.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FieldVerifier.java
new file mode 100644
index 00000000000..7ee0151ca64
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FieldVerifier.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import com.google.i18n.addressinput.common.LookupKey.KeyType;
+import com.google.i18n.addressinput.common.LookupKey.ScriptType;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Accesses address verification data used to verify components of an address.
+ * <p>
+ * Not all fields require all types of validation, although this could be done. In particular,
+ * the current implementation only provides known value verification for the hierarchical fields,
+ * and only provides format and match verification for the postal code field.
+ */
+public final class FieldVerifier {
+ // A value for a particular language is has the language separated by this String.
+ private static final String LOCALE_DELIMITER = "--";
+ // Node data values are delimited by this symbol.
+ private static final String LIST_DELIMITER = "~";
+ // Keys are built up using this delimiter: eg data/US, data/US/CA.
+ private static final String KEY_NODE_DELIMITER = "/";
+
+ private static final FormatInterpreter FORMAT_INTERPRETER =
+ new FormatInterpreter(new FormOptions().createSnapshot());
+
+ // Package-private so it can be accessed by tests.
+ String id;
+ private DataSource dataSource;
+ private boolean useRegionDataConstants;
+
+ // Package-private so they can be accessed by tests.
+ Set<AddressField> possiblyUsedFields;
+ Set<AddressField> required;
+ // Known values. Can be either a key, a name in Latin, or a name in native script.
+ private Map<String, String> candidateValues;
+
+ // Keys for the subnodes of this verifier. For example, a key for the US would be CA, since
+ // there is a sub-verifier with the ID "data/US/CA". Keys may be the local names of the
+ // locations in the next level of the hierarchy, or the abbreviations if suitable abbreviations
+ // exist. Package-private so it can be accessed by tests.
+ String[] keys;
+ // Names in Latin. These are only populated if the native/local names are in a script other than
+ // latin.
+ private String[] latinNames;
+ // Names in native script.
+ private String[] localNames;
+
+ // Pattern representing the format of a postal code number.
+ private Pattern format;
+ // Defines the valid range of a postal code number.
+ private Pattern match;
+
+ /**
+ * Creates the root field verifier for a particular data source. Defaults useRegionDataConstants
+ * to true.
+ */
+ public FieldVerifier(DataSource dataSource) {
+ this(dataSource, true /* useRegionDataConstants */);
+ }
+
+ /**
+ * Creates the root field verifier for a particular data source.
+ */
+ public FieldVerifier(DataSource dataSource, boolean useRegionDataConstants) {
+ this.dataSource = dataSource;
+ this.useRegionDataConstants = useRegionDataConstants;
+ populateRootVerifier();
+ }
+
+ /**
+ * Creates a field verifier based on its parent and on the new data for this node supplied by
+ * nodeData (which may be null).
+ * Package-private so it can be accessed by tests.
+ */
+ FieldVerifier(FieldVerifier parent, AddressVerificationNodeData nodeData) {
+ // Most information is inherited from the parent.
+ possiblyUsedFields = new HashSet<AddressField>(parent.possiblyUsedFields);
+ required = new HashSet<AddressField>(parent.required);
+ dataSource = parent.dataSource;
+ useRegionDataConstants = parent.useRegionDataConstants;
+ format = parent.format;
+ match = parent.match;
+ // Here we add in any overrides from this particular node as well as information such as
+ // localNames, latinNames and keys.
+ populate(nodeData);
+ // candidateValues should never be inherited from the parent, but built up from the
+ // localNames in this node.
+ candidateValues = Util.buildNameToKeyMap(keys, localNames, latinNames);
+ }
+
+ /**
+ * Sets possiblyUsedFields, required, keys and candidateValues for the root field verifier.
+ */
+ private void populateRootVerifier() {
+ id = "data";
+ // Keys come from the countries under "data".
+ AddressVerificationNodeData rootNode = dataSource.getDefaultData("data");
+ if (rootNode.containsKey(AddressDataKey.COUNTRIES)) {
+ keys = rootNode.get(AddressDataKey.COUNTRIES).split(LIST_DELIMITER);
+ }
+ // candidateValues is just the set of keys.
+ candidateValues = Util.buildNameToKeyMap(keys, null, null);
+
+ // TODO: Investigate if these need to be set here. The country level population already
+ // handles the fallback, the question is if validation can be done without a country level
+ // validator being created.
+ // Copy "possiblyUsedFields" and "required" from the defaults here for bootstrapping.
+ possiblyUsedFields = new HashSet<AddressField>();
+ required = new HashSet<AddressField>();
+ populatePossibleAndRequired("ZZ");
+ }
+
+ /**
+ * Populates this verifier with data from the node data passed in and from RegionDataConstants.
+ * The node data may be null.
+ */
+ private void populate(AddressVerificationNodeData nodeData) {
+ if (nodeData == null) {
+ return;
+ }
+ if (nodeData.containsKey(AddressDataKey.ID)) {
+ id = nodeData.get(AddressDataKey.ID);
+ }
+ if (nodeData.containsKey(AddressDataKey.SUB_KEYS)) {
+ keys = nodeData.get(AddressDataKey.SUB_KEYS).split(LIST_DELIMITER);
+ }
+ if (nodeData.containsKey(AddressDataKey.SUB_LNAMES)) {
+ latinNames = nodeData.get(AddressDataKey.SUB_LNAMES).split(LIST_DELIMITER);
+ }
+ if (nodeData.containsKey(AddressDataKey.SUB_NAMES)) {
+ localNames = nodeData.get(AddressDataKey.SUB_NAMES).split(LIST_DELIMITER);
+ }
+ if (nodeData.containsKey(AddressDataKey.XZIP)) {
+ format = Pattern.compile(nodeData.get(AddressDataKey.XZIP), Pattern.CASE_INSENSITIVE);
+ }
+ if (nodeData.containsKey(AddressDataKey.ZIP)) {
+ // This key has two different meanings, depending on whether this is a country-level key
+ // or not.
+ if (isCountryKey()) {
+ format = Pattern.compile(nodeData.get(AddressDataKey.ZIP), Pattern.CASE_INSENSITIVE);
+ } else {
+ match = Pattern.compile(nodeData.get(AddressDataKey.ZIP), Pattern.CASE_INSENSITIVE);
+ }
+ }
+ // If there are latin names but no local names, and there are the same number of latin names
+ // as there are keys, then we assume the local names are the same as the keys.
+ if (keys != null && localNames == null && latinNames != null
+ && keys.length == latinNames.length) {
+ localNames = keys;
+ }
+ if (isCountryKey()) {
+ populatePossibleAndRequired(getRegionCodeFromKey(id));
+ }
+ }
+
+ /**
+ * This method assumes the hierarchyKey contains a region code. If not, returns ZZ.
+ */
+ private static String getRegionCodeFromKey(String hierarchyKey) {
+ String[] parts = hierarchyKey.split(KEY_NODE_DELIMITER);
+ if (parts.length == 1) {
+ // Return the unknown region if none was found.
+ return "ZZ";
+ }
+ return parts[1].split(LOCALE_DELIMITER)[0];
+ }
+
+ // TODO: We should be consistent with where the language data comes from; what are the
+ // consequences if the server is out-of-sync with the client? We should get the language from the
+ // same place here and in FormController; it's not obvious that happens right now.
+ private Set<String> getAcceptableAlternateLanguages(String regionCode) {
+ // TODO: We should have a class that knows how to get information about the data, rather than
+ // getting the node and extracting keys here.
+ AddressVerificationNodeData countryNode = getCountryNode(regionCode);
+ String languages = countryNode.get(AddressDataKey.LANGUAGES);
+ String defaultLanguage = countryNode.get(AddressDataKey.LANG);
+ Set<String> alternateLanguages = new HashSet<String>();
+ // If languages is set, defaultLanguage will be set as well.
+ if (languages != null && defaultLanguage != null) {
+ String languagesArray[] = languages.split(LIST_DELIMITER);
+ for (String lang : languagesArray) {
+ // The default language is never appended to keys.
+ if (!lang.equals(defaultLanguage)) {
+ alternateLanguages.add(lang);
+ }
+ }
+ }
+ return alternateLanguages;
+ }
+
+ private AddressVerificationNodeData getCountryNode(String regionCode) {
+ LookupKey lookupKey = new LookupKey.Builder(KeyType.DATA)
+ .setAddressData(new AddressData.Builder().setCountry(regionCode).build())
+ .build();
+ return dataSource.getDefaultData(lookupKey.toString());
+ }
+
+ private void populatePossibleAndRequired(String regionCode) {
+ // If useRegionDataConstants is true, these fields are populated from RegionDataConstants so
+ // that the metadata server can be updated without needing to be in sync with clients;
+ // otherwise, these fields are populated from dataSource.
+ if (!useRegionDataConstants) {
+ AddressVerificationNodeData countryNode = getCountryNode(regionCode);
+ AddressVerificationNodeData defaultNode = getCountryNode("ZZ");
+
+ String formatString = countryNode.get(AddressDataKey.FMT);
+ if (formatString == null) {
+ formatString = defaultNode.get(AddressDataKey.FMT);
+ }
+ if (formatString != null) {
+ List<AddressField> possible =
+ FORMAT_INTERPRETER.getAddressFieldOrder(formatString, regionCode);
+ possiblyUsedFields.addAll(convertAddressFieldsToPossiblyUsedSet(possible));
+ } /* else: shouldn't ever happen */
+ String requireString = countryNode.get(AddressDataKey.REQUIRE);
+ if (requireString == null) {
+ requireString = defaultNode.get(AddressDataKey.REQUIRE);
+ }
+ if (requireString != null) {
+ required = FormatInterpreter.getRequiredFields(requireString, regionCode);
+ } /* else: shouldn't ever happen */
+ return;
+ }
+
+ List<AddressField> possible =
+ FORMAT_INTERPRETER.getAddressFieldOrder(ScriptType.LOCAL, regionCode);
+ possiblyUsedFields = convertAddressFieldsToPossiblyUsedSet(possible);
+ required = FormatInterpreter.getRequiredFields(regionCode);
+ }
+
+ FieldVerifier refineVerifier(String sublevel) {
+ if (Util.trimToNull(sublevel) == null || id == null) {
+ return new FieldVerifier(this, null);
+ }
+
+ // Split the subkey into key + language (if any). Check the language is an acceptable
+ // alternative for the region, for which we have data. If not, we drop it from the data key.
+ String[] parts = sublevel.split(LOCALE_DELIMITER);
+
+ if (parts.length == 0){
+ // May only contains the LOCALE_DELIMITER.
+ return new FieldVerifier(this, null);
+ }
+
+ // Makes the new key - the old key, plus the new data, minus the language code.
+ String currentFullKey = id + KEY_NODE_DELIMITER + parts[0];
+
+ // If a language was present, check that it is valid.
+ if (parts.length > 1) {
+ // Since currentFullKey must have the KEY_NODE_DELIMITER - we added it above - this is safe.
+ String regionCode = getRegionCodeFromKey(currentFullKey);
+ if (getAcceptableAlternateLanguages(regionCode).contains(parts[1])) {
+ currentFullKey = currentFullKey + LOCALE_DELIMITER + parts[1];
+ }
+ }
+
+ // This fixes the position of the language in the key, so data/CA--fr/Quebec would be
+ // canonicalised to data/CA/Quebec--fr.
+ currentFullKey = new LookupKey.Builder(currentFullKey).build().toString();
+ // For names with no Latin equivalent, we can look up the sublevel name directly.
+ AddressVerificationNodeData nodeData = dataSource.get(currentFullKey);
+ if (nodeData != null) {
+ return new FieldVerifier(this, nodeData);
+ }
+ // If that failed, then we try to look up the local name equivalent of this latin name.
+ // First check these exist.
+ if (latinNames == null) {
+ return new FieldVerifier(this, null);
+ }
+ for (int n = 0; n < latinNames.length; n++) {
+ if (latinNames[n].equalsIgnoreCase(sublevel)) {
+ // We found a match - we should try looking up a key with the local name at the same
+ // index.
+ currentFullKey =
+ new LookupKey.Builder(id + KEY_NODE_DELIMITER + localNames[n]).build().toString();
+ nodeData = dataSource.get(currentFullKey);
+ if (nodeData != null) {
+ return new FieldVerifier(this, nodeData);
+ }
+ }
+ }
+ // No sub-verifiers were found.
+ return new FieldVerifier(this, null);
+ }
+
+ /**
+ * Returns the ID of this verifier.
+ */
+ @Override
+ public String toString() {
+ return id;
+ }
+
+ /**
+ * Checks a value in a particular script for a particular field to see if it causes the problem
+ * specified. If so, this problem is added to the AddressProblems collection passed in. Returns
+ * true if no problem was found.
+ *
+ * @param script the script type used to verify address. This affects countries
+ * where there are values in the local language and in latin script, such as China.
+ * If null, do not consider script type, so both latin and local language values would be
+ * considered valid.
+ * @param problem problem type to check. For example, when problem type is
+ * {@code UNEXPECTED_FIELD}, checks that the input {@code field} is not used.
+ * @param field address field to verify.
+ * @param value field value.
+ * @param problems collection of problems collected during verification.
+ * @return true if verification passes.
+ */
+ protected boolean check(ScriptType script, AddressProblemType problem, AddressField field,
+ String value, AddressProblems problems) {
+ boolean problemFound = false;
+
+ String trimmedValue = Util.trimToNull(value);
+ switch (problem) {
+ case UNEXPECTED_FIELD:
+ if (trimmedValue != null && !possiblyUsedFields.contains(field)) {
+ problemFound = true;
+ }
+ break;
+ case MISSING_REQUIRED_FIELD:
+ if (required.contains(field) && trimmedValue == null) {
+ problemFound = true;
+ }
+ break;
+ case UNKNOWN_VALUE:
+ // An empty string will never be an UNKNOWN_VALUE. It is invalid
+ // only when it appears in a required field (In that case it will
+ // be reported as MISSING_REQUIRED_FIELD).
+ if (trimmedValue == null) {
+ break;
+ }
+ problemFound = !isKnownInScript(script, trimmedValue);
+ break;
+ case INVALID_FORMAT:
+ if (trimmedValue != null && format != null && !format.matcher(trimmedValue).matches()) {
+ problemFound = true;
+ }
+ break;
+ case MISMATCHING_VALUE:
+ if (trimmedValue != null && match != null && !match.matcher(trimmedValue).lookingAt()) {
+ problemFound = true;
+ }
+ break;
+ default:
+ throw new RuntimeException("Unknown problem: " + problem);
+ }
+ if (problemFound) {
+ problems.add(field, problem);
+ }
+ return !problemFound;
+ }
+
+ /**
+ * Checks the value of a particular field in a particular script against the known values for
+ * this field. If script is null, it checks both the local and the latin values. Otherwise it
+ * checks only the values in the script specified.
+ */
+ private boolean isKnownInScript(ScriptType script, String value) {
+ String trimmedValue = Util.trimToNull(value);
+ Util.checkNotNull(trimmedValue);
+ if (script == null) {
+ return (candidateValues == null || candidateValues.containsKey(
+ Util.toLowerCaseLocaleIndependent(trimmedValue)));
+ }
+ // Otherwise, if we know the script, we want to restrict the candidates to only names in
+ // that script.
+ String[] namesToConsider = (script == ScriptType.LATIN) ? latinNames : localNames;
+ Set<String> candidates = new HashSet<String>();
+ if (namesToConsider != null) {
+ for (String name : namesToConsider) {
+ candidates.add(Util.toLowerCaseLocaleIndependent(name));
+ }
+ }
+ if (keys != null) {
+ for (String name : keys) {
+ candidates.add(Util.toLowerCaseLocaleIndependent(name));
+ }
+ }
+
+ if (candidates.size() == 0 || trimmedValue == null) {
+ return true;
+ }
+
+ return candidates.contains(Util.toLowerCaseLocaleIndependent(value));
+ }
+
+ /**
+ * Converts a list of address fields to a set of possibly used fields. Adds country and handles
+ * street address.
+ */
+ private static Set<AddressField> convertAddressFieldsToPossiblyUsedSet(
+ List<AddressField> fields) {
+ // COUNTRY is never unexpected.
+ EnumSet<AddressField> result = EnumSet.of(AddressField.COUNTRY);
+ for (AddressField field : fields) {
+ // Replace ADDRESS_LINE with STREET_ADDRESS because that's what the validation expects.
+ if (field == AddressField.ADDRESS_LINE_1 || field == AddressField.ADDRESS_LINE_2) {
+ result.add(AddressField.STREET_ADDRESS);
+ } else {
+ result.add(field);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if this key represents a country. We assume all keys with only one delimiter are
+ * at the country level (such as "data/US").
+ */
+ private boolean isCountryKey() {
+ Util.checkNotNull(id, "Cannot use null as key");
+ return id.split(KEY_NODE_DELIMITER).length == 2;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormController.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormController.java
new file mode 100644
index 00000000000..1f80e21a6f5
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormController.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import com.google.i18n.addressinput.common.LookupKey.KeyType;
+import com.google.i18n.addressinput.common.LookupKey.ScriptType;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Responsible for looking up data for address fields. This fetches possible
+ * values for the next level down in the address hierarchy, if these are known.
+ */
+public final class FormController {
+ // For address hierarchy in lookup key.
+ private static final String SLASH_DELIM = "/";
+ // For joined values.
+ private static final String TILDE_DELIM = "~";
+ // For language code info in lookup key (E.g., data/CA--fr).
+ private static final String DASH_DELIM = "--";
+ private static final LookupKey ROOT_KEY = FormController.getDataKeyForRoot();
+ private static final String DEFAULT_REGION_CODE = "ZZ";
+ private static final AddressField[] ADDRESS_HIERARCHY = {
+ AddressField.COUNTRY,
+ AddressField.ADMIN_AREA,
+ AddressField.LOCALITY,
+ AddressField.DEPENDENT_LOCALITY};
+
+ // Current user language.
+ private String languageCode;
+ private final ClientData integratedData;
+ private String currentCountry;
+
+ /**
+ * Constructor that populates this with data. languageCode should be a BCP language code (such
+ * as "en" or "zh-Hant") and currentCountry should be an ISO 2-letter region code (such as "GB"
+ * or "US").
+ */
+ public FormController(ClientData integratedData, String languageCode, String currentCountry) {
+ Util.checkNotNull(integratedData, "null data not allowed");
+ this.languageCode = languageCode;
+ this.currentCountry = currentCountry;
+
+ AddressData address = new AddressData.Builder().setCountry(DEFAULT_REGION_CODE).build();
+ LookupKey defaultCountryKey = getDataKeyFor(address);
+
+ AddressVerificationNodeData defaultCountryData =
+ integratedData.getDefaultData(defaultCountryKey.toString());
+ Util.checkNotNull(defaultCountryData,
+ "require data for default country key: " + defaultCountryKey);
+ this.integratedData = integratedData;
+ }
+
+ public void setLanguageCode(String languageCode) {
+ this.languageCode = languageCode;
+ }
+
+ public void setCurrentCountry(String currentCountry) {
+ this.currentCountry = currentCountry;
+ }
+
+ private ScriptType getScriptType() {
+ if (languageCode != null && Util.isExplicitLatinScript(languageCode)) {
+ return ScriptType.LATIN;
+ }
+ return ScriptType.LOCAL;
+ }
+
+ private static LookupKey getDataKeyForRoot() {
+ AddressData address = new AddressData.Builder().build();
+ return new LookupKey.Builder(KeyType.DATA).setAddressData(address).build();
+ }
+
+ public LookupKey getDataKeyFor(AddressData address) {
+ return new LookupKey.Builder(KeyType.DATA).setAddressData(address).build();
+ }
+
+ /**
+ * Requests data for the input address. This method chains multiple requests together. For
+ * example, an address for Mt View, California needs data from "data/US", "data/US/CA", and
+ * "data/US/CA/Mt View" to support it. This method will request them one by one (from top level
+ * key down to the most granular) and evokes {@link DataLoadListener#dataLoadingEnd} method when
+ * all data is collected. If the address is invalid, it will request the first valid child key
+ * instead. For example, a request for "data/US/Foo" will end up requesting data for "data/US",
+ * "data/US/AL".
+ *
+ * @param address the current address.
+ * @param listener triggered when requested data for the address is returned.
+ */
+ public void requestDataForAddress(AddressData address, DataLoadListener listener) {
+ Util.checkNotNull(address.getPostalCountry(), "null country not allowed");
+
+ // Gets the key for deepest available node.
+ Queue<String> subkeys = new LinkedList<String>();
+
+ for (AddressField field : ADDRESS_HIERARCHY) {
+ String value = address.getFieldValue(field);
+ if (value == null) {
+ break;
+ }
+ subkeys.add(value);
+ }
+ if (subkeys.size() == 0) {
+ throw new RuntimeException("Need at least country level info");
+ }
+
+ if (listener != null) {
+ listener.dataLoadingBegin();
+ }
+ requestDataRecursively(ROOT_KEY, subkeys, listener);
+ }
+
+ private void requestDataRecursively(
+ final LookupKey key, final Queue<String> subkeys, final DataLoadListener listener) {
+ Util.checkNotNull(key, "Null key not allowed");
+ Util.checkNotNull(subkeys, "Null subkeys not allowed");
+
+ integratedData.requestData(key, new DataLoadListener() {
+ @Override
+ public void dataLoadingBegin() {
+ }
+
+ @Override
+ public void dataLoadingEnd() {
+ List<RegionData> subregions = getRegionData(key);
+ if (subregions.isEmpty()) {
+ if (listener != null) {
+ listener.dataLoadingEnd();
+ }
+ // TODO: Should update the selectors here.
+ return;
+ } else if (subkeys.size() > 0) {
+ String subkey = subkeys.remove();
+ for (RegionData subregion : subregions) {
+ if (subregion.isValidName(subkey)) {
+ LookupKey nextKey = buildDataLookupKey(key, subregion.getKey());
+ requestDataRecursively(nextKey, subkeys, listener);
+ return;
+ }
+ }
+ }
+
+ // Current value in the field is not valid, use the first valid subkey
+ // to request more data instead.
+ String firstSubkey = subregions.get(0).getKey();
+ LookupKey nextKey = buildDataLookupKey(key, firstSubkey);
+ Queue<String> emptyList = new LinkedList<String>();
+ requestDataRecursively(nextKey, emptyList, listener);
+ }
+ });
+ }
+
+ private LookupKey buildDataLookupKey(LookupKey lookupKey, String subKey) {
+ String[] subKeys = lookupKey.toString().split(SLASH_DELIM);
+ String languageCodeSubTag =
+ (languageCode == null) ? null : Util.getLanguageSubtag(languageCode);
+ String key = lookupKey.toString() + SLASH_DELIM + subKey;
+
+ // Country level key
+ if (subKeys.length == 1 && languageCodeSubTag != null
+ && !isDefaultLanguage(languageCodeSubTag)) {
+ key += DASH_DELIM + languageCodeSubTag.toString();
+ }
+ return new LookupKey.Builder(key).build();
+ }
+
+ /**
+ * Compares the language subtags of input {@code languageCode} and default language code. For
+ * example, "zh-Hant" and "zh" are viewed as identical.
+ */
+ public boolean isDefaultLanguage(String languageCode) {
+ if (languageCode == null) {
+ return true;
+ }
+ AddressData addr = new AddressData.Builder().setCountry(currentCountry).build();
+ LookupKey lookupKey = getDataKeyFor(addr);
+ AddressVerificationNodeData data = integratedData.getDefaultData(lookupKey.toString());
+ String defaultLanguage = data.get(AddressDataKey.LANG);
+
+ // Current language is not the default language for the country.
+ if (Util.trimToNull(defaultLanguage) != null
+ && !Util.getLanguageSubtag(languageCode).equals(Util.getLanguageSubtag(languageCode))) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets a list of {@link RegionData} for sub-regions for a given key. For example, sub regions
+ * for "data/US" are AL/Alabama, AK/Alaska, etc.
+ *
+ * <p> TODO: Rename/simplify RegionData to avoid confusion with RegionDataConstants elsewhere
+ * since it does not contain anything more than key/value pairs now.
+ *
+ * @return A list of sub-regions, each sub-region represented by a {@link RegionData}.
+ */
+ public List<RegionData> getRegionData(LookupKey key) {
+ if (key.getKeyType() == KeyType.EXAMPLES) {
+ throw new RuntimeException("example key not allowed for getting region data");
+ }
+ Util.checkNotNull(key, "null regionKey not allowed");
+ LookupKey normalizedKey = normalizeLookupKey(key);
+ List<RegionData> results = new ArrayList<RegionData>();
+
+ // Root key.
+ if (normalizedKey.equals(ROOT_KEY)) {
+ AddressVerificationNodeData data = integratedData.getDefaultData(normalizedKey.toString());
+ String[] countries = splitData(data.get(AddressDataKey.COUNTRIES));
+ for (int i = 0; i < countries.length; i++) {
+ RegionData rd = new RegionData.Builder().setKey(countries[i]).setName(countries[i]).build();
+ results.add(rd);
+ }
+ return results;
+ }
+
+ AddressVerificationNodeData data = integratedData.get(normalizedKey.toString());
+ if (data != null) {
+ String[] keys = splitData(data.get(AddressDataKey.SUB_KEYS));
+ String[] names = (getScriptType() == ScriptType.LOCAL)
+ ? splitData(data.get(AddressDataKey.SUB_NAMES))
+ : splitData(data.get(AddressDataKey.SUB_LNAMES));
+
+ for (int i = 0; i < keys.length; i++) {
+ RegionData rd = new RegionData.Builder()
+ .setKey(keys[i])
+ .setName((i < names.length) ? names[i] : keys[i])
+ .build();
+ results.add(rd);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Split a '~' delimited string into an array of strings. This method is null tolerant and
+ * considers an empty string to contain no elements.
+ *
+ * @param raw The data to split
+ * @return an array of strings
+ */
+ private String[] splitData(String raw) {
+ if (raw == null || raw.isEmpty()) {
+ return new String[]{};
+ }
+ return raw.split(TILDE_DELIM);
+ }
+
+ private String getSubKey(LookupKey parentKey, String name) {
+ for (RegionData subRegion : getRegionData(parentKey)) {
+ if (subRegion.isValidName(name)) {
+ return subRegion.getKey();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Normalizes {@code key} by replacing field values with sub-keys. For example, California is
+ * replaced with CA. The normalization goes from top (country) to bottom (dependent locality)
+ * and if any field value is empty, unknown, or invalid, it will stop and return whatever it
+ * gets. For example, a key "data/US/California/foobar/kar" will be normalized into
+ * "data/US/CA/foobar/kar" since "foobar" is unknown. This method supports only keys of
+ * {@link KeyType#DATA} type.
+ *
+ * @return normalized {@link LookupKey}.
+ */
+ private LookupKey normalizeLookupKey(LookupKey key) {
+ Util.checkNotNull(key);
+ if (key.getKeyType() != KeyType.DATA) {
+ throw new RuntimeException("Only DATA keyType is supported");
+ }
+
+ String subStr[] = key.toString().split(SLASH_DELIM);
+
+ // Root key does not need to be normalized.
+ if (subStr.length < 2) {
+ return key;
+ }
+
+ StringBuilder sb = new StringBuilder(subStr[0]);
+ for (int i = 1; i < subStr.length; ++i) {
+ // Strips the language code if there was one.
+ String languageCode = null;
+ if (i == 1 && subStr[i].contains(DASH_DELIM)) {
+ String[] s = subStr[i].split(DASH_DELIM);
+ subStr[i] = s[0];
+ languageCode = s[1];
+ }
+
+ String normalizedSubKey = getSubKey(new LookupKey.Builder(sb.toString()).build(), subStr[i]);
+
+ // Can't find normalized sub-key; assembles the lookup key with the
+ // remaining sub-keys and returns it.
+ if (normalizedSubKey == null) {
+ for (; i < subStr.length; ++i) {
+ sb.append(SLASH_DELIM).append(subStr[i]);
+ }
+ break;
+ }
+ sb.append(SLASH_DELIM).append(normalizedSubKey);
+ if (languageCode != null) {
+ sb.append(DASH_DELIM).append(languageCode);
+ }
+ }
+ return new LookupKey.Builder(sb.toString()).build();
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormOptions.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormOptions.java
new file mode 100644
index 00000000000..0704fc6a790
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormOptions.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Configuration options for the address input widget used to control the visibility and interaction
+ * of specific fields to suit specific use cases (eg, collecting business addresses, collecting
+ * addresses for credit card verification etc...).
+ * <p>
+ * When form options are passed to the address widget a snapshot is taken and any further changes to
+ * the options are ignored.
+ * <p>
+ * This design is somewhat like using a builder but has the advantage that the caller only sees the
+ * outer (mutable) type and never needs to know about the "built" snapshot. This reduces the public
+ * API footprint and simplifies usage of this class.
+ */
+// This is an external class and part of the widget's public API.
+public final class FormOptions {
+
+ // These fields must never be null).
+ private Set<AddressField> hiddenFields = EnumSet.noneOf(AddressField.class);
+ private Set<AddressField> readonlyFields = EnumSet.noneOf(AddressField.class);
+ private Set<String> blacklistedRegions = new HashSet<String>();
+ // Key is ISO 2-letter region code.
+ private Map<String, List<AddressField>> customFieldOrder =
+ new HashMap<String, List<AddressField>>();
+
+ /** Creates an empty, mutable, form options instance. */
+ public FormOptions() {
+ }
+
+ /**
+ * Hides the given address field. Calls to this method <strong>are cumulative</strong>. Fields
+ * which are specified here but not part of a country's specified fields will be ignored.
+ * <p>
+ * Note also that hiding fields occurs after custom ordering has been applied, although combining
+ * these two features is not generally recommended due to the confusion it is likely to cause.
+ */
+ public FormOptions setHidden(AddressField field) {
+ hiddenFields.add(field);
+ return this;
+ }
+
+ /**
+ * Sets the given address field as read-only. Calls to this method <strong>are cumulative
+ * </strong>. Fields which are specified here but not part of a country's specified fields will be
+ * ignored.
+ * <p>
+ * This method is j2objc- & iOS API friendly as the signature does not expose varargs / Java
+ * arrays or collections.
+ */
+ public FormOptions setReadonly(AddressField field) {
+ readonlyFields.add(field);
+ return this;
+ }
+
+ /**
+ * Sets the order of address input fields for the given ISO 3166-1 two letter country code.
+ * <p>
+ * Input fields affected by custom ordering will be shown in the widget in the order they are
+ * given to this method (for the associated region code). Fields which are visible for a region,
+ * but which are not specified here, will appear in their original position in the form. For
+ * example, if a region defines the following fields:
+ * <pre>
+ * [ RECIPIENT -> ORGANIZATION -> STREET_ADDRESS -> LOCALITY -> ADMIN_AREA -> COUNTRY ]
+ * </pre>
+ * and the custom ordering for that region is (somewhat contrived):
+ * <pre>
+ * [ ORGANIZATION -> COUNTRY -> RECIPIENT ]
+ * </pre>
+ * Then the visible order of the input fields will be:
+ * <pre>
+ * [ ORGANIZATION -> COUNTRY -> STREET_ADDRESS -> LOCALITY -> ADMIN_AREA -> RECIPIENT ]
+ * </pre>
+ * <ul>
+ * <li>Fields not specified in the custom ordering (STREET_ADDRESS, LOCALITY, ADMIN_AREA)
+ * remain in their original, absolute, positions.
+ * <li>Custom ordered fields are re-positioned such that their relative order is now as
+ * specified (but other, non custom-ordered, fields can appear between them).
+ * </ul>
+ * <p>
+ * If the custom order contains a field which is not present for the specified region, it is
+ * silently ignored. Setting a custom ordering can never be used as a way to add fields for a
+ * region.
+ * <p>
+ * Typically this feature is used to reverse things like RECIPIENT and ORGANIZATION for certain
+ * business related use cases. It should not be used to "correct" perceived bad field ordering
+ * or make different countries "more consistent with each other".
+ */
+ public FormOptions setCustomFieldOrder(String regionCode, AddressField... fields) {
+ // TODO: Consider checking the given region code for validity against RegionDataConstants.
+ List<AddressField> fieldList = Collections.unmodifiableList(Arrays.asList(fields));
+ if (fieldList.size() > 0) {
+ if (EnumSet.copyOf(fieldList).size() != fieldList.size()) {
+ throw new IllegalArgumentException("duplicate address field: " + fieldList);
+ }
+ customFieldOrder.put(regionCode, fieldList);
+ } else {
+ customFieldOrder.remove(regionCode);
+ }
+ return this;
+ }
+
+ /**
+ * Blacklist the given CLDR (Common Locale Data Repository) region (country) code
+ * indicating countries that for legal or other reasons should not be available.
+ * <p>
+ * Calls are cumulative, call this method once for each region that needs to be blacklisted.
+ * <p>
+ * We reserve the right to change this API from taking individual regions to taking a set.
+ */
+ public FormOptions blacklistRegion(String regionCode) {
+ if (regionCode == null) {
+ throw new NullPointerException();
+ }
+ // TODO(addresswidget-team): Add region code validation against RegionDataConstants.
+ blacklistedRegions.add(Util.toUpperCaseLocaleIndependent(regionCode));
+ return this;
+ }
+
+ /** Returns an immutable snapshot of the current state of the form options. */
+ public Snapshot createSnapshot() {
+ return new Snapshot(this);
+ }
+
+ /** An immutable snapshot of a {@code FormOptions} instance. */
+ public static class Snapshot {
+ private final Set<AddressField> hiddenFields;
+ private final Set<AddressField> readonlyFields;
+ private final Set<String> blacklistedRegions;
+ private final Map<String, List<AddressField>> customFieldOrder;
+
+ Snapshot(FormOptions options) {
+ this.hiddenFields = Collections.unmodifiableSet(EnumSet.copyOf(options.hiddenFields));
+ this.readonlyFields = Collections.unmodifiableSet(EnumSet.copyOf(options.readonlyFields));
+ // Shallow copy as lists are already immutable.
+ this.customFieldOrder = Collections.unmodifiableMap(
+ new HashMap<String, List<AddressField>>(options.customFieldOrder));
+ this.blacklistedRegions =
+ Collections.unmodifiableSet(new HashSet<String>(options.blacklistedRegions));
+ }
+
+ public boolean isHidden(AddressField field) {
+ return hiddenFields.contains(field);
+ }
+
+ public boolean isReadonly(AddressField field) {
+ return readonlyFields.contains(field);
+ }
+
+ /**
+ * Gets the overridden field orders with their corresponding region code. Returns null if field
+ * orders for {@code regionCode} is not specified.
+ */
+ List<AddressField> getCustomFieldOrder(String regionCode) {
+ return customFieldOrder.get(regionCode);
+ }
+
+ public boolean isBlacklistedRegion(String regionCode) {
+ return blacklistedRegions.contains(Util.toUpperCaseLocaleIndependent(regionCode));
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormatInterpreter.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormatInterpreter.java
new file mode 100644
index 00000000000..ba4c668ffb4
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/FormatInterpreter.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import com.google.i18n.addressinput.common.AddressField.WidthType;
+import com.google.i18n.addressinput.common.LookupKey.ScriptType;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Address format interpreter. A utility to find address format related info.
+ */
+public final class FormatInterpreter {
+ private static final String NEW_LINE = "%n";
+
+ private final FormOptions.Snapshot formOptions;
+
+ /**
+ * Creates a new instance of {@link FormatInterpreter}.
+ */
+ public FormatInterpreter(FormOptions.Snapshot options) {
+ Util.checkNotNull(
+ RegionDataConstants.getCountryFormatMap(), "null country name map not allowed");
+ Util.checkNotNull(options);
+ this.formOptions = options;
+ Util.checkNotNull(getJsonValue("ZZ", AddressDataKey.FMT),
+ "Could not obtain a default address field order.");
+ }
+
+ /**
+ * Returns a list of address fields based on the format of {@code regionCode}. Script type is
+ * needed because some countries uses different address formats for local/Latin scripts.
+ *
+ * @param scriptType if {@link ScriptType#LOCAL}, use local format; else use Latin format.
+ */
+ // TODO: Consider not re-doing this work every time the widget is re-rendered.
+ @SuppressWarnings("deprecation") // For legacy address fields.
+ public List<AddressField> getAddressFieldOrder(ScriptType scriptType, String regionCode) {
+ Util.checkNotNull(scriptType);
+ Util.checkNotNull(regionCode);
+ String formatString = getFormatString(scriptType, regionCode);
+ return getAddressFieldOrder(formatString, regionCode);
+ }
+
+ List<AddressField> getAddressFieldOrder(String formatString, String regionCode) {
+ EnumSet<AddressField> visibleFields = EnumSet.noneOf(AddressField.class);
+ List<AddressField> fieldOrder = new ArrayList<AddressField>();
+ // TODO: Change this to just enumerate the address fields directly.
+ for (String substring : getFormatSubstrings(formatString)) {
+ // Skips un-escaped characters and new lines.
+ if (!substring.matches("%.") || substring.equals(NEW_LINE)) {
+ continue;
+ }
+ AddressField field = getFieldForFormatSubstring(substring);
+ // Accept only the first instance for any duplicate fields (which can occur because the
+ // string we start with defines format order, which can contain duplicate fields).
+ if (!visibleFields.contains(field)) {
+ visibleFields.add(field);
+ fieldOrder.add(field);
+ }
+ }
+ applyFieldOrderOverrides(regionCode, fieldOrder);
+
+ // Uses two address lines instead of street address.
+ for (int n = 0; n < fieldOrder.size(); n++) {
+ if (fieldOrder.get(n) == AddressField.STREET_ADDRESS) {
+ fieldOrder.set(n, AddressField.ADDRESS_LINE_1);
+ fieldOrder.add(n + 1, AddressField.ADDRESS_LINE_2);
+ break;
+ }
+ }
+ return Collections.unmodifiableList(fieldOrder);
+ }
+
+ /**
+ * Returns true if this format substring (e.g. %C) represents an address field. Returns false if
+ * it is a literal or newline.
+ */
+ private static boolean formatSubstringRepresentsField(String formatSubstring) {
+ return !formatSubstring.equals(NEW_LINE) && formatSubstring.startsWith("%");
+ }
+
+ /**
+ * Gets data from the address represented by a format substring such as %C. Will throw an
+ * exception if no field can be found.
+ */
+ private static AddressField getFieldForFormatSubstring(String formatSubstring) {
+ return AddressField.of(formatSubstring.charAt(1));
+ }
+
+ /**
+ * Returns true if the address has any data for this address field.
+ */
+ private static boolean addressHasValueForField(AddressData address, AddressField field) {
+ if (field == AddressField.STREET_ADDRESS) {
+ return address.getAddressLines().size() > 0;
+ } else {
+ String value = address.getFieldValue(field);
+ return (value != null && !value.isEmpty());
+ }
+ }
+
+ private void applyFieldOrderOverrides(String regionCode, List<AddressField> fieldOrder) {
+ List<AddressField> customFieldOrder = formOptions.getCustomFieldOrder(regionCode);
+ if (customFieldOrder == null) {
+ return;
+ }
+
+ // We can assert that fieldOrder and customFieldOrder contain no duplicates.
+ // We know this by the construction above and in FormOptions but we still have to think
+ // about fields in the custom ordering which aren't visible (the loop below will fail if
+ // a non-visible field appears in the custom ordering). However in that case it's safe to
+ // just ignore the extraneous field.
+ Set<AddressField> nonVisibleCustomFields = EnumSet.copyOf(customFieldOrder);
+ nonVisibleCustomFields.removeAll(fieldOrder);
+ if (nonVisibleCustomFields.size() > 0) {
+ // Local mutable copy to remove non visible fields - this shouldn't happen often.
+ customFieldOrder = new ArrayList<AddressField>(customFieldOrder);
+ customFieldOrder.removeAll(nonVisibleCustomFields);
+ }
+ // It is vital for this loop to work correctly that every element in customFieldOrder
+ // appears in fieldOrder exactly once.
+ for (int fieldIdx = 0, customIdx = 0; fieldIdx < fieldOrder.size(); fieldIdx++) {
+ if (customFieldOrder.contains(fieldOrder.get(fieldIdx))) {
+ fieldOrder.set(fieldIdx, customFieldOrder.get(customIdx++));
+ }
+ }
+ }
+
+ /**
+ * Returns the fields that are required to be filled in for this country. This is based upon the
+ * "required" field in RegionDataConstants for {@code regionCode}, and handles falling back to
+ * the default data if necessary.
+ */
+ static Set<AddressField> getRequiredFields(String regionCode) {
+ Util.checkNotNull(regionCode);
+ String requireString = getRequiredString(regionCode);
+ return getRequiredFields(requireString, regionCode);
+ }
+
+ static Set<AddressField> getRequiredFields(String requireString, String regionCode) {
+ EnumSet<AddressField> required = EnumSet.of(AddressField.COUNTRY);
+ for (char c : requireString.toCharArray()) {
+ required.add(AddressField.of(c));
+ }
+ return required;
+ }
+
+ private static String getRequiredString(String regionCode) {
+ String required = getJsonValue(regionCode, AddressDataKey.REQUIRE);
+ if (required == null) {
+ required = getJsonValue("ZZ", AddressDataKey.REQUIRE);
+ }
+ return required;
+ }
+
+ /**
+ * Returns the field width override for the specified country, or null if there's none. This is
+ * based upon the "width_overrides" field in RegionDataConstants for {@code regionCode}.
+ */
+ static WidthType getWidthOverride(AddressField field, String regionCode) {
+ return getWidthOverride(field, regionCode, RegionDataConstants.getCountryFormatMap());
+ }
+
+ /**
+ * Visible for Testing - same as {@link #getWidthOverride(AddressField, String)} but testable with
+ * fake data.
+ */
+ static WidthType getWidthOverride(
+ AddressField field, String regionCode, Map<String, String> regionDataMap) {
+ Util.checkNotNull(regionCode);
+ String overridesString =
+ getJsonValue(regionCode, AddressDataKey.WIDTH_OVERRIDES, regionDataMap);
+ if (overridesString == null || overridesString.isEmpty()) {
+ return null;
+ }
+
+ // The field width overrides string starts with a %, so we skip the first one.
+ // Example string: "%C:L%S:S" which is a repeated string of
+ // '<%> field_character <:> width_character'.
+ for (int pos = 0; pos != -1;) {
+ int keyStartIndex = pos + 1;
+ int valueStartIndex = overridesString.indexOf(':', keyStartIndex + 1) + 1;
+ if (valueStartIndex == 0 || valueStartIndex == overridesString.length()) {
+ // Malformed string -- % not followed by ':' or trailing ':'
+ return null;
+ }
+ // Prepare for next iteration.
+ pos = overridesString.indexOf('%', valueStartIndex + 1);
+ if (valueStartIndex != keyStartIndex + 2 ||
+ overridesString.charAt(keyStartIndex) != field.getChar()) {
+ // Key is not a high level field (unhandled by this code) or does not match.
+ // Also catches malformed string where key is of zero length (skip, not error).
+ continue;
+ }
+ int valueLength = (pos != -1 ? pos : overridesString.length()) - valueStartIndex;
+ if (valueLength != 1) {
+ // Malformed string -- value has length other than 1
+ return null;
+ }
+ return WidthType.of(overridesString.charAt(valueStartIndex));
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets formatted address. For example,
+ *
+ * <p> John Doe</br>
+ * Dnar Corp</br>
+ * 5th St</br>
+ * Santa Monica CA 90123 </p>
+ *
+ * This method does not validate addresses. Also, it will "normalize" the result strings by
+ * removing redundant spaces and empty lines.
+ */
+ public List<String> getEnvelopeAddress(AddressData address) {
+ Util.checkNotNull(address, "null input address not allowed");
+ String regionCode = address.getPostalCountry();
+
+ String lc = address.getLanguageCode();
+ ScriptType scriptType = ScriptType.LOCAL;
+ if (lc != null) {
+ scriptType = Util.isExplicitLatinScript(lc) ? ScriptType.LATIN : ScriptType.LOCAL;
+ }
+
+ List<String> prunedFormat = new ArrayList<String>();
+ String formatString = getFormatString(scriptType, regionCode);
+ List<String> formatSubstrings = getFormatSubstrings(formatString);
+ for (int i = 0; i < formatSubstrings.size(); i++) {
+ String formatSubstring = formatSubstrings.get(i);
+ // Always keep the newlines.
+ if (formatSubstring.equals(NEW_LINE)) {
+ prunedFormat.add(NEW_LINE);
+ } else if (formatSubstringRepresentsField(formatSubstring)) {
+ // Always keep the non-empty address fields.
+ if (addressHasValueForField(address, getFieldForFormatSubstring(formatSubstring))) {
+ prunedFormat.add(formatSubstring);
+ }
+ } else if (
+ // Only keep literals that satisfy these 2 conditions:
+ // (1) Not preceding an empty field.
+ (i == formatSubstrings.size() - 1 || formatSubstrings.get(i + 1).equals(NEW_LINE)
+ || addressHasValueForField(address, getFieldForFormatSubstring(
+ formatSubstrings.get(i + 1))))
+ // (2) Not following a removed field.
+ && (i == 0 || !formatSubstringRepresentsField(formatSubstrings.get(i - 1))
+ || (!prunedFormat.isEmpty()
+ && formatSubstringRepresentsField(prunedFormat.get(prunedFormat.size() - 1))))) {
+ prunedFormat.add(formatSubstring);
+ }
+ }
+
+ List<String> lines = new ArrayList<>();
+ StringBuilder currentLine = new StringBuilder();
+ for (String formatSubstring : prunedFormat) {
+ if (formatSubstring.equals(NEW_LINE)) {
+ if (currentLine.length() > 0) {
+ lines.add(currentLine.toString());
+ currentLine.setLength(0);
+ }
+ } else if (formatSubstringRepresentsField(formatSubstring)) {
+ switch (getFieldForFormatSubstring(formatSubstring)) {
+ case STREET_ADDRESS:
+ // The field "street address" represents the street address lines of an address, so
+ // there can be multiple values.
+ List<String> addressLines = address.getAddressLines();
+ if (addressLines.size() > 0) {
+ currentLine.append(addressLines.get(0));
+ if (addressLines.size() > 1) {
+ lines.add(currentLine.toString());
+ currentLine.setLength(0);
+ lines.addAll(addressLines.subList(1, addressLines.size()));
+ }
+ }
+ break;
+ case COUNTRY:
+ // Country name is treated separately.
+ break;
+ case ADMIN_AREA:
+ currentLine.append(address.getAdministrativeArea());
+ break;
+ case LOCALITY:
+ currentLine.append(address.getLocality());
+ break;
+ case DEPENDENT_LOCALITY:
+ currentLine.append(address.getDependentLocality());
+ break;
+ case RECIPIENT:
+ currentLine.append(address.getRecipient());
+ break;
+ case ORGANIZATION:
+ currentLine.append(address.getOrganization());
+ break;
+ case POSTAL_CODE:
+ currentLine.append(address.getPostalCode());
+ break;
+ case SORTING_CODE:
+ currentLine.append(address.getSortingCode());
+ break;
+ default:
+ break;
+ }
+ } else {
+ // Not a symbol we recognise, so must be a literal. We append it unchanged.
+ currentLine.append(formatSubstring);
+ }
+ }
+ if (currentLine.length() > 0) {
+ lines.add(currentLine.toString());
+ }
+ return lines;
+ }
+
+ /**
+ * Tokenizes the format string and returns the token string list. "%" is treated as an escape
+ * character. For example, "%n%a%nxyz" will be split into "%n", "%a", "%n", "xyz".
+ * Escaped tokens correspond to either new line or address fields. The output of this method
+ * may contain duplicates.
+ */
+ // TODO: Create a common method which does field parsing in one place (there are about 4 other
+ // places in this library where format strings are parsed).
+ private List<String> getFormatSubstrings(String formatString) {
+ List<String> parts = new ArrayList<String>();
+
+ boolean escaped = false;
+ StringBuilder currentLiteral = new StringBuilder();
+ for (char c : formatString.toCharArray()) {
+ if (escaped) {
+ escaped = false;
+ parts.add("%" + c);
+ } else if (c == '%') {
+ if (currentLiteral.length() > 0) {
+ parts.add(currentLiteral.toString());
+ currentLiteral.setLength(0);
+ }
+ escaped = true;
+ } else {
+ currentLiteral.append(c);
+ }
+ }
+ if (currentLiteral.length() > 0) {
+ parts.add(currentLiteral.toString());
+ }
+ return parts;
+ }
+
+ private static String getFormatString(ScriptType scriptType, String regionCode) {
+ String format = (scriptType == ScriptType.LOCAL)
+ ? getJsonValue(regionCode, AddressDataKey.FMT)
+ : getJsonValue(regionCode, AddressDataKey.LFMT);
+ if (format == null) {
+ format = getJsonValue("ZZ", AddressDataKey.FMT);
+ }
+ return format;
+ }
+
+ private static String getJsonValue(String regionCode, AddressDataKey key) {
+ return getJsonValue(regionCode, key, RegionDataConstants.getCountryFormatMap());
+ }
+
+ /**
+ * Visible for testing only.
+ */
+ static String getJsonValue(
+ String regionCode, AddressDataKey key, Map<String, String> regionDataMap) {
+ Util.checkNotNull(regionCode);
+ String jsonString = regionDataMap.get(regionCode);
+ Util.checkNotNull(jsonString, "no json data for region code " + regionCode);
+
+ try {
+ JSONObject jsonObj = new JSONObject(new JSONTokener(jsonString));
+ if (!jsonObj.has(Util.toLowerCaseLocaleIndependent(key.name()))) {
+ // Key not found. Return null.
+ return null;
+ }
+ // Gets the string for this key.
+ String parsedJsonString = jsonObj.getString(Util.toLowerCaseLocaleIndependent(key.name()));
+ return parsedJsonString;
+ } catch (JSONException e) {
+ throw new RuntimeException("Invalid json for region code " + regionCode + ": " + jsonString);
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/JsoMap.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/JsoMap.java
new file mode 100644
index 00000000000..cc5a2cffec7
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/JsoMap.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Compatibility methods on top of the JSON data.
+ */
+public final class JsoMap extends JSONObject {
+ /**
+ * Construct a JsoMap object given some json text. This method directly evaluates the String
+ * that you pass in; no error or safety checking is performed, so be very careful about the
+ * source of your data.
+ *
+ * @param json JSON text describing an address format
+ * @return a JsoMap object made from the supplied JSON.
+ */
+ public static JsoMap buildJsoMap(String json) throws JSONException {
+ return new JsoMap(new JSONTokener(json));
+ }
+
+ /**
+ * Construct an empty JsoMap.
+ *
+ * @return the empty object.
+ */
+ static JsoMap createEmptyJsoMap() {
+ return new JsoMap();
+ }
+
+ /**
+ * constructor.
+ */
+ protected JsoMap() {
+ }
+
+ private JsoMap(JSONTokener readFrom) throws JSONException {
+ super(readFrom);
+ }
+
+ private JsoMap(JSONObject copyFrom, String[] names) throws JSONException {
+ super(copyFrom, names);
+ }
+
+ /**
+ * Remove the specified key.
+ *
+ * @param key key name.
+ */
+ void delKey(String key) {
+ super.remove(key);
+ }
+
+ /**
+ * Retrieve the string value for specified key.
+ *
+ * @param key key name.
+ * @return string value.
+ * @throws ClassCastException, IllegalArgumentException.
+ */
+ @Override
+ public String get(String key) {
+ try {
+ Object o = super.get(key);
+ if (o instanceof String) {
+ return (String) o;
+ } else if (o instanceof Integer) {
+ throw new IllegalArgumentException();
+ } else {
+ throw new ClassCastException();
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Access JSONObject.get(String) which is shadowed by JsoMap.get(String).
+ *
+ * @param name A key string.
+ * @return The object associated with the key.
+ * @throws JSONException if the key is not found.
+ */
+ public Object getObject(String name) throws JSONException {
+ return super.get(name);
+ }
+
+ /**
+ * Retrieves the integer value for specified key.
+ *
+ * @return integer value or -1 if value is undefined.
+ */
+ @Override
+ public int getInt(String key) {
+ try {
+ Object o = super.get(key);
+ if (o instanceof Integer) {
+ return ((Integer) o).intValue();
+ } else {
+ throw new RuntimeException("Something other than an int was returned");
+ }
+ } catch (JSONException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Collect all the keys and return as a JSONArray.
+ *
+ * @return A JSONArray that contains all the keys.
+ */
+ JSONArray getKeys() {
+ // names() returns null if the array was empty!
+ JSONArray names = super.names();
+ return names != null ? names : new JSONArray();
+ }
+
+ /**
+ * Retrieve the JsoMap object for specified key.
+ *
+ * @param key key name.
+ * @return JsoMap object.
+ * @throws ClassCastException, IllegalArgumentException.
+ */
+ @SuppressWarnings("unchecked")
+ // JSONObject.keys() has no type information.
+ JsoMap getObj(String key) throws ClassCastException, IllegalArgumentException {
+ try {
+ Object o = super.get(key);
+ if (o instanceof JSONObject) {
+ JSONObject value = (JSONObject) o;
+ ArrayList<String> keys = new ArrayList<String>(value.length());
+ for (Iterator<String> it = value.keys(); it.hasNext(); ) {
+ keys.add(it.next());
+ }
+ String[] names = new String[keys.size()];
+ return new JsoMap(value, keys.toArray(names));
+ } else if (o instanceof Integer) {
+ throw new IllegalArgumentException();
+ } else {
+ throw new ClassCastException();
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Check if the object has specified key.
+ *
+ * @param key The key name to be checked.
+ * @return true if key can be found.
+ */
+ boolean containsKey(String key) {
+ return super.has(key);
+ }
+
+ /**
+ * Merge those keys not found in this object from specified object.
+ *
+ * @param obj The other object to be merged.
+ */
+ void mergeData(JsoMap obj) {
+ if (obj == null) {
+ return;
+ }
+
+ JSONArray names = obj.names();
+ if (names == null) {
+ return;
+ }
+
+ for (int i = 0; i < names.length(); i++) {
+ try {
+ String name = names.getString(i);
+ try {
+ if (!super.has(name)) {
+ super.put(name, obj.getObject(name));
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (JSONException e) {
+ // Ignored.
+ }
+ }
+ }
+
+ /**
+ * Save a string to string mapping into this map.
+ *
+ * @param key the string key.
+ * @param value the String value.
+ */
+ void put(String key, String value) {
+ try {
+ super.put(key, value);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Save a string to integer mapping into this map.
+ *
+ * @param key the string key.
+ * @param value the integer value.
+ */
+ void putInt(String key, int value) {
+ try {
+ super.put(key, value);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Save a string to JSONObject mapping into this map.
+ *
+ * @param key the string key.
+ * @param value a JSONObject as value.
+ */
+ void putObj(String key, JSONObject value) {
+ try {
+ super.put(key, value);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ String string() throws ClassCastException, IllegalArgumentException {
+ StringBuilder sb = new StringBuilder("JsoMap[\n");
+ JSONArray keys = getKeys();
+ for (int i = 0; i < keys.length(); i++) {
+ String key;
+ try {
+ key = keys.getString(i);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ sb.append('(').append(key).append(':').append(get(key)).append(")\n");
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ String map() throws ClassCastException, IllegalArgumentException {
+ StringBuilder sb = new StringBuilder("JsoMap[\n");
+ JSONArray keys = getKeys();
+ for (int i = 0; i < keys.length(); i++) {
+ String key;
+ try {
+ key = keys.getString(i);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ sb.append('(').append(key).append(':').append(getObj(key).string()).append(")\n");
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/LookupKey.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/LookupKey.java
new file mode 100644
index 00000000000..2e1c397d750
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/LookupKey.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * A builder for creating keys that are used to lookup data in the local cache and fetch data from
+ * the server. There are two key types: {@link KeyType#DATA} or {@link KeyType#EXAMPLES}.
+ * <p>
+ * The {@link KeyType#DATA} key is built based on a universal Address hierarchy, which is:<br>
+ * {@link AddressField#COUNTRY} -> {@link AddressField#ADMIN_AREA} -> {@link AddressField#LOCALITY}
+ * -> {@link AddressField#DEPENDENT_LOCALITY}
+ * <p>
+ * The {@link KeyType#EXAMPLES} key is built with the following format:<br>
+ * {@link AddressField#COUNTRY} -> {@link ScriptType} -> language. </p>
+ */
+public final class LookupKey {
+ /**
+ * Key types. Address Widget organizes address info based on key types. For example, if you want
+ * to know how to verify or format an US address, you need to use {@link KeyType#DATA} to get
+ * that info; if you want to get an example address, you use {@link KeyType#EXAMPLES} instead.
+ */
+ public enum KeyType {
+ /**
+ * Key type for getting address data.
+ */
+ DATA,
+ /**
+ * Key type for getting examples.
+ */
+ EXAMPLES
+ }
+
+ /**
+ * Script types. This is used for countries that do not use Latin script, but accept it for
+ * transcribing their addresses. For example, you can write a Japanese address in Latin script
+ * instead of Japanese:
+ * <pre>7-2, Marunouchi 2-Chome, Chiyoda-ku, Tokyo 100-8799 </pre>
+ * <p>
+ * Notice that {@link ScriptType} is based on country/region, not language.
+ */
+ public enum ScriptType {
+ /**
+ * The script that uses Roman characters like ABC (as opposed to scripts like Cyrillic or
+ * Arabic).
+ */
+ LATIN,
+
+ /**
+ * Local scripts. For Japan, it's Japanese (including Hiragana, Katagana, and Kanji); For
+ * Saudi Arabia, it's Arabic. Notice that for US, the local script is actually Latin script
+ * (The same goes for other countries that use Latin script). For these countries, we do not
+ * provide two set of data (Latin and local) since they use only Latin script. You have to
+ * specify the {@link ScriptType} as local instead Latin.
+ */
+ LOCAL
+ }
+
+ /**
+ * The universal address hierarchy. Notice that sub-administrative area is neglected here since
+ * it is not required to fill out address forms.
+ */
+ private static final AddressField[] HIERARCHY = {
+ AddressField.COUNTRY,
+ AddressField.ADMIN_AREA,
+ AddressField.LOCALITY,
+ AddressField.DEPENDENT_LOCALITY};
+
+ private static final String SLASH_DELIM = "/";
+
+ private static final String DASH_DELIM = "--";
+
+ private static final String DEFAULT_LANGUAGE = "_default";
+
+ private final KeyType keyType;
+
+ private final ScriptType scriptType;
+
+ // Values for each address field in the hierarchy.
+ private final Map<AddressField, String> nodes;
+
+ private final String keyString;
+
+ private final String languageCode;
+
+ private LookupKey(Builder builder) {
+ this.keyType = builder.keyType;
+ this.scriptType = builder.script;
+ this.nodes = builder.nodes;
+ this.languageCode = builder.languageCode;
+ this.keyString = createKeyString();
+ }
+
+ /**
+ * Gets a lookup key built from the values of nodes in the hierarchy up to and including the input
+ * address field. This method does not allow keys with a key type of {@link KeyType#EXAMPLES}.
+ *
+ * @param field a field in the address hierarchy.
+ * @return key of the specified address field. If address field is not in the hierarchy, or is
+ * more granular than the data present in the current key, returns null. For example,
+ * if your current key is "data/US" (down to COUNTRY level), and you want to get the key
+ * for LOCALITY (more granular than COUNTRY), it will return null.
+ */
+ public LookupKey getKeyForUpperLevelField(AddressField field) {
+ if (keyType != KeyType.DATA) {
+ // We only support getting the parent key for the data key type.
+ throw new RuntimeException("Only support getting parent keys for the data key type.");
+ }
+ Builder newKeyBuilder = new Builder(this);
+
+ boolean removeNode = false;
+ boolean fieldInHierarchy = false;
+ for (AddressField hierarchyField : HIERARCHY) {
+ if (removeNode) {
+ if (newKeyBuilder.nodes.containsKey(hierarchyField)) {
+ newKeyBuilder.nodes.remove(hierarchyField);
+ }
+ }
+ if (hierarchyField == field) {
+ if (!newKeyBuilder.nodes.containsKey(hierarchyField)) {
+ return null;
+ }
+ removeNode = true;
+ fieldInHierarchy = true;
+ }
+ }
+
+ if (!fieldInHierarchy) {
+ return null;
+ }
+
+ newKeyBuilder.languageCode = languageCode;
+ newKeyBuilder.script = scriptType;
+
+ return newKeyBuilder.build();
+ }
+
+ /**
+ * Returns the string value of a field in a key for a particular
+ * AddressField. For example, for the key "data/US/CA" and the address
+ * field AddressField.COUNTRY, "US" would be returned. Returns an empty
+ * string if the key does not have this field in it.
+ */
+ String getValueForUpperLevelField(AddressField field) {
+ if (!this.nodes.containsKey(field)) {
+ return "";
+ }
+
+ return this.nodes.get(field);
+ }
+
+ /**
+ * Gets parent key for data key. For example, parent key for "data/US/CA" is "data/US". This
+ * method does not allow key with key type of {@link KeyType#EXAMPLES}.
+ */
+ LookupKey getParentKey() {
+ if (keyType != KeyType.DATA) {
+ throw new RuntimeException("Only support getting parent keys for the data key type.");
+ }
+ // Root key's parent should be null.
+ if (!nodes.containsKey(AddressField.COUNTRY)) {
+ return null;
+ }
+
+ Builder parentKeyBuilder = new Builder(this);
+ AddressField mostGranularField = AddressField.COUNTRY;
+
+ for (AddressField hierarchyField : HIERARCHY) {
+ if (!nodes.containsKey(hierarchyField)) {
+ break;
+ }
+ mostGranularField = hierarchyField;
+ }
+ parentKeyBuilder.nodes.remove(mostGranularField);
+ return parentKeyBuilder.build();
+ }
+
+ public KeyType getKeyType() {
+ return keyType;
+ }
+
+ /**
+ * Creates the string format of the given key. E.g., "data/US/CA".
+ */
+ private String createKeyString() {
+ StringBuilder keyBuilder = new StringBuilder(Util.toLowerCaseLocaleIndependent(keyType.name()));
+
+ if (keyType == KeyType.DATA) {
+ for (AddressField field : HIERARCHY) {
+ if (!nodes.containsKey(field)) {
+ break;
+ }
+ keyBuilder.append(SLASH_DELIM).append(nodes.get(field));
+ }
+ // Only append the language if this is not the root key and there was a language.
+ if (languageCode != null && nodes.size() > 0) {
+ keyBuilder.append(DASH_DELIM).append(languageCode);
+ }
+ } else {
+ if (nodes.containsKey(AddressField.COUNTRY)) {
+ // Example key. E.g., "examples/TW/local/_default".
+ keyBuilder.append(SLASH_DELIM)
+ .append(nodes.get(AddressField.COUNTRY))
+ .append(SLASH_DELIM)
+ .append(Util.toLowerCaseLocaleIndependent(scriptType.name()))
+ .append(SLASH_DELIM)
+ .append(DEFAULT_LANGUAGE);
+ }
+ }
+
+ return keyBuilder.toString();
+ }
+
+ /**
+ * Gets a lookup key as a plain text string., e.g., "data/US/CA".
+ */
+ @Override
+ public String toString() {
+ return keyString;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (obj.getClass() != this.getClass())) {
+ return false;
+ }
+
+ return ((LookupKey) obj).toString().equals(keyString);
+ }
+
+ @Override
+ public int hashCode() {
+ return keyString.hashCode();
+ }
+
+ static boolean hasValidKeyPrefix(String key) {
+ for (KeyType type : KeyType.values()) {
+ if (key.startsWith(Util.toLowerCaseLocaleIndependent(type.name()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Builds lookup keys.
+ */
+ // TODO: This is used in AddressWidget in a small number of places and it should be possible
+ // to hide this type within this package quite easily.
+ public static class Builder {
+ private KeyType keyType;
+
+ // Default to LOCAL script.
+ private ScriptType script = ScriptType.LOCAL;
+
+ private final Map<AddressField, String> nodes =
+ new EnumMap<AddressField, String>(AddressField.class);
+
+ private String languageCode;
+
+ /**
+ * Creates a new builder for the specified key type. keyType cannot be null.
+ */
+ public Builder(KeyType keyType) {
+ this.keyType = keyType;
+ }
+
+ /**
+ * Creates a new builder for the specified key. oldKey cannot be null.
+ */
+ Builder(LookupKey oldKey) {
+ this.keyType = oldKey.keyType;
+ this.script = oldKey.scriptType;
+ this.languageCode = oldKey.languageCode;
+ for (AddressField field : HIERARCHY) {
+ if (!oldKey.nodes.containsKey(field)) {
+ break;
+ }
+ this.nodes.put(field, oldKey.nodes.get(field));
+ }
+ }
+
+ /**
+ * Builds the {@link LookupKey} with the input key string. Input string has to represent
+ * either a {@link KeyType#DATA} key or a {@link KeyType#EXAMPLES} key. Also, key hierarchy
+ * deeper than {@link AddressField#DEPENDENT_LOCALITY} is not allowed. Notice that if any
+ * node in the hierarchy is empty, all the descendant nodes' values will be neglected. For
+ * example, input string "data/US//Mt View" will become "data/US".
+ *
+ * @param keyString e.g., "data/US/CA"
+ */
+ public Builder(String keyString) {
+ String[] parts = keyString.split(SLASH_DELIM);
+
+ if (!parts[0].equals(Util.toLowerCaseLocaleIndependent(KeyType.DATA.name()))
+ && !parts[0].equals(Util.toLowerCaseLocaleIndependent(KeyType.EXAMPLES.name()))) {
+ throw new RuntimeException("Wrong key type: " + parts[0]);
+ }
+ if (parts.length > HIERARCHY.length + 1) {
+ // Assume that any extra elements found in the key belong in the 'dependent locality' field.
+ // This means that a key string of /EXAMPLES/C/A/L/D/E would result in a dependent locality
+ // value of 'D/E'. This also means that if it's the actual locality name has a slash in it
+ // (for example 'L/D'), the locality field which we break down will be incorrect
+ // (for example: 'L'). Regardless, the actual breakdown of the key doesn't impact the server
+ // lookup, so there will be no problems.
+ String[] extraParts = Arrays.copyOfRange(parts, HIERARCHY.length + 1, parts.length + 1);
+
+ // Update the original array to only contain the number of elements which we expect.
+ parts = Arrays.copyOfRange(parts, 0, HIERARCHY.length + 1);
+
+ // Append the extra parts to the last element (dependent locality).
+ for (String element : extraParts) {
+ if (element != null) {
+ parts[4] += SLASH_DELIM + element;
+ }
+ }
+ }
+
+ if (parts[0].equals("data")) {
+ keyType = KeyType.DATA;
+
+ // Process all parts of the key, starting from the country.
+ for (int i = 1; i < parts.length; i++) {
+ // TODO: We shouldn't need the trimToNull here.
+ String substr = Util.trimToNull(parts[i]);
+ if (substr == null) {
+ break;
+ }
+ // If a language code specification was present, extract this. This should only be there
+ // (if it ever is) on the last node.
+ if (substr.contains(DASH_DELIM)) {
+ String[] s = substr.split(DASH_DELIM);
+ if (s.length != 2) {
+ throw new RuntimeException(
+ "Wrong format: Substring should be <last node value>--<language code>");
+ }
+ substr = s[0];
+ languageCode = s[1];
+ }
+
+ this.nodes.put(HIERARCHY[i - 1], substr);
+ }
+ } else if (parts[0].equals("examples")) {
+ keyType = KeyType.EXAMPLES;
+
+ // Parses country info.
+ if (parts.length > 1) {
+ this.nodes.put(AddressField.COUNTRY, parts[1]);
+ }
+
+ // Parses script types.
+ if (parts.length > 2) {
+ String scriptStr = parts[2];
+ if (scriptStr.equals("local")) {
+ this.script = ScriptType.LOCAL;
+ } else if (scriptStr.equals("latin")) {
+ this.script = ScriptType.LATIN;
+ } else {
+ throw new RuntimeException("Script type has to be either latin or local.");
+ }
+ }
+
+ // Parses language code. Example: "zh_Hant" in
+ // "examples/TW/local/zH_Hant".
+ if (parts.length > 3 && !parts[3].equals(DEFAULT_LANGUAGE)) {
+ languageCode = parts[3];
+ }
+ }
+ }
+
+ Builder setLanguageCode(String languageCode) {
+ this.languageCode = languageCode;
+ return this;
+ }
+
+ /**
+ * Sets key using {@link AddressData}. Notice that if any node in the hierarchy is empty,
+ * all the descendant nodes' values will be neglected. For example, the following address
+ * misses {@link AddressField#ADMIN_AREA}, thus its data key will be "data/US".
+ *
+ * <p> country: US<br> administrative area: null<br> locality: Mt. View </p>
+ */
+ public Builder setAddressData(AddressData data) {
+ languageCode = data.getLanguageCode();
+ if (languageCode != null) {
+ if (Util.isExplicitLatinScript(languageCode)) {
+ script = ScriptType.LATIN;
+ }
+ }
+
+ if (data.getPostalCountry() == null) {
+ return this;
+ }
+ this.nodes.put(AddressField.COUNTRY, data.getPostalCountry());
+
+ if (data.getAdministrativeArea() == null) {
+ return this;
+ }
+ this.nodes.put(AddressField.ADMIN_AREA, data.getAdministrativeArea());
+
+ if (data.getLocality() == null) {
+ return this;
+ }
+ this.nodes.put(AddressField.LOCALITY, data.getLocality());
+
+ if (data.getDependentLocality() == null) {
+ return this;
+ }
+ this.nodes.put(AddressField.DEPENDENT_LOCALITY, data.getDependentLocality());
+ return this;
+ }
+
+ public LookupKey build() {
+ return new LookupKey(this);
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/NotifyingListener.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/NotifyingListener.java
new file mode 100644
index 00000000000..9ca8643b99c
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/NotifyingListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * A helper class to let the calling thread wait until loading has finished.
+ */
+// TODO: Consider dealing with interruption in a more recoverable way.
+public final class NotifyingListener implements DataLoadListener {
+ private boolean done = false;
+
+ @Override
+ public void dataLoadingBegin() {
+ }
+
+ @Override
+ public synchronized void dataLoadingEnd() {
+ done = true;
+ notifyAll();
+ }
+
+ /**
+ * Waits for a call to {@link #dataLoadingEnd} to have occurred. If this thread is interrupted,
+ * the {@code InterruptedException} is propagated immediately and the loading may not yet have
+ * finished. This leaves callers in a potentially unrecoverable state.
+ */
+ public synchronized void waitLoadingEnd() throws InterruptedException {
+ while (!done) {
+ wait();
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/OnAddressSelectedListener.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/OnAddressSelectedListener.java
new file mode 100644
index 00000000000..b5b91634f6f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/OnAddressSelectedListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * If autocomplete is enabled on the AddressWidget, setting an OnAddressSelectedListener
+ * will cause onAddressSelected to be called when the user clicks on an autocomplete
+ * suggestion in the dropdown list.
+ */
+public interface OnAddressSelectedListener {
+ void onAddressSelected(AddressData addressData);
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionData.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionData.java
new file mode 100644
index 00000000000..d03320f0f51
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionData.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * A simple class to hold region data.
+ */
+// This class used to purport to be immutable, but it is no such thing.
+// TODO: Make this class actually immutable and not just pretending to be immutable.
+public final class RegionData {
+ private String key;
+ private String name;
+
+ /**
+ * Create a new RegionData object.
+ */
+ private RegionData() {
+ }
+
+ /**
+ * Copy constructor. data should not be null.
+ *
+ * @param data A populated instance of RegionData
+ */
+ private RegionData(RegionData data) {
+ Util.checkNotNull(data);
+ this.key = data.key;
+ this.name = data.name;
+ }
+
+ /**
+ * Gets the key of the region. For example, California's key is "CA".
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the name. Returns null if not specified.
+ */
+ String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the best display name. Returns the name if this is not null, otherwise the key.
+ */
+ public String getDisplayName() {
+ return (name != null) ? name : key;
+ }
+
+ /**
+ * Checks if the input subkey is the name (in Latin or local script) of the region. Returns
+ * false if subkey is not a valid name for the region, or the input subkey is null.
+ *
+ * @param subkey a string that refers to the name of a geo location. Like "California", "CA", or
+ * "Mountain View". Names in the local script are also supported.
+ */
+ public boolean isValidName(String subkey) {
+ if (subkey == null) {
+ return false;
+ }
+ if (subkey.equalsIgnoreCase(key) || subkey.equalsIgnoreCase(name)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * A builder class to facilitate the creation of RegionData objects.
+ */
+ // TODO: Replace this broken builder implementation with a simple static factory method.
+ public static class Builder {
+ RegionData data = new RegionData();
+
+ public RegionData build() {
+ return new RegionData(data);
+ }
+
+ public Builder setKey(String key) {
+ Util.checkNotNull(key, "Key should not be null.");
+ data.key = key;
+ return this;
+ }
+
+ /**
+ * Sets name of the region. For example, "California". If the name is an empty string, sets
+ * it to null.
+ */
+ public Builder setName(String name) {
+ data.name = Util.trimToNull(name);
+ return this;
+ }
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionDataConstants.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionDataConstants.java
new file mode 100644
index 00000000000..fce8747b51a
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/RegionDataConstants.java
@@ -0,0 +1,285 @@
+// Copyright (C) 2010 Google Inc.
+//
+// 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.google.i18n.addressinput.common;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+public final class RegionDataConstants {
+ private static final Map<String, String> addressDataMap = createMap();
+
+ public static Map<String, String> getCountryFormatMap() {
+ return addressDataMap;
+ }
+
+ private static Map<String, String> createMap() {
+ TreeMap<String, String> map = new TreeMap<String, String>();
+ map.put("AC", "{\"name\":\"ASCENSION ISLAND\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("AD", "{\"name\":\"ANDORRA\",\"lang\":\"ca\",\"languages\":\"ca\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("AE", "{\"name\":\"UNITED ARAB EMIRATES\",\"lang\":\"ar\",\"languages\":\"ar\",\"lfmt\":\"%N%n%O%n%A%n%S\",\"fmt\":\"%N%n%O%n%A%n%S\",\"require\":\"AS\",\"state_name_type\":\"emirate\"}");
+ map.put("AF", "{\"name\":\"AFGHANISTAN\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("AG", "{\"name\":\"ANTIGUA AND BARBUDA\",\"require\":\"A\"}");
+ map.put("AI", "{\"name\":\"ANGUILLA\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("AL", "{\"name\":\"ALBANIA\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C\"}");
+ map.put("AM", "{\"name\":\"ARMENIA\",\"lang\":\"hy\",\"languages\":\"hy\",\"lfmt\":\"%N%n%O%n%A%n%Z%n%C%n%S\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C%n%S\"}");
+ map.put("AO", "{\"name\":\"ANGOLA\"}");
+ map.put("AQ", "{\"name\":\"ANTARCTICA\"}");
+ map.put("AR", "{\"name\":\"ARGENTINA\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\",\"upper\":\"ACZ\"}");
+ map.put("AS", "{\"name\":\"AMERICAN SAMOA\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("AT", "{\"name\":\"AUSTRIA\",\"fmt\":\"%O%n%N%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("AU", "{\"name\":\"AUSTRALIA\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%O%n%N%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"CS\",\"locality_name_type\":\"suburb\",\"state_name_type\":\"state\"}");
+ map.put("AW", "{\"name\":\"ARUBA\"}");
+ map.put("AX", "{\"name\":\"FINLAND\",\"fmt\":\"%O%n%N%n%A%nAX-%Z %C%nÅLAND\",\"require\":\"ACZ\",\"postprefix\":\"AX-\"}");
+ map.put("AZ", "{\"name\":\"AZERBAIJAN\",\"fmt\":\"%N%n%O%n%A%nAZ %Z %C\",\"postprefix\":\"AZ \"}");
+ map.put("BA", "{\"name\":\"BOSNIA AND HERZEGOVINA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("BB", "{\"name\":\"BARBADOS\",\"fmt\":\"%N%n%O%n%A%n%C, %S %Z\",\"state_name_type\":\"parish\"}");
+ map.put("BD", "{\"name\":\"BANGLADESH\",\"fmt\":\"%N%n%O%n%A%n%C - %Z\"}");
+ map.put("BE", "{\"name\":\"BELGIUM\",\"fmt\":\"%O%n%N%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("BF", "{\"name\":\"BURKINA FASO\",\"fmt\":\"%N%n%O%n%A%n%C %X\"}");
+ map.put("BG", "{\"name\":\"BULGARIA (REP.)\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("BH", "{\"name\":\"BAHRAIN\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("BI", "{\"name\":\"BURUNDI\"}");
+ map.put("BJ", "{\"name\":\"BENIN\",\"upper\":\"AC\"}");
+ map.put("BL", "{\"name\":\"SAINT BARTHELEMY\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("BM", "{\"name\":\"BERMUDA\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("BN", "{\"name\":\"BRUNEI DARUSSALAM\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("BO", "{\"name\":\"BOLIVIA\",\"upper\":\"AC\"}");
+ map.put("BQ", "{\"name\":\"BONAIRE, SINT EUSTATIUS, AND SABA\"}");
+ map.put("BR", "{\"name\":\"BRAZIL\",\"lang\":\"pt\",\"languages\":\"pt\",\"fmt\":\"%O%n%N%n%A%n%D%n%C-%S%n%Z\",\"require\":\"ASCZ\",\"upper\":\"CS\",\"sublocality_name_type\":\"neighborhood\",\"state_name_type\":\"state\",\"width_overrides\":\"%C:L%S:S\",\"label_overrides\":[{\"field\":\"S2\",\"label\":\"Setor/ADE/Folha\"},{\"field\":\"S3\",\"label\":\"Quadra\"},{\"field\":\"S4\",\"label\":\"Trecho/AE/Modulo\"},{\"field\":\"S5\",\"label\":\"Cj/Bl/MI/Projeção/Etapa\"},{\"field\":\"LP\",\"label\":\"Lote\"},{\"field\":\"BI\",\"label\":\"Casa/Comercio\"},{\"field\":\"CG\",\"label\":\"Complexo/Chácara\"}]}");
+ map.put("BS", "{\"name\":\"BAHAMAS\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%C, %S\",\"state_name_type\":\"island\"}");
+ map.put("BT", "{\"name\":\"BHUTAN\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("BV", "{\"name\":\"BOUVET ISLAND\"}");
+ map.put("BW", "{\"name\":\"BOTSWANA\"}");
+ map.put("BY", "{\"name\":\"BELARUS\",\"fmt\":\"%S%n%Z %C%n%A%n%O%n%N\"}");
+ map.put("BZ", "{\"name\":\"BELIZE\"}");
+ map.put("CA", "{\"name\":\"CANADA\",\"lang\":\"en\",\"languages\":\"en~fr\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOSZ\"}");
+ map.put("CC", "{\"name\":\"COCOS (KEELING) ISLANDS\",\"fmt\":\"%O%n%N%n%A%n%C %S %Z\",\"upper\":\"CS\"}");
+ map.put("CD", "{\"name\":\"CONGO (DEM. REP.)\"}");
+ map.put("CF", "{\"name\":\"CENTRAL AFRICAN REPUBLIC\"}");
+ map.put("CG", "{\"name\":\"CONGO (REP.)\"}");
+ map.put("CH", "{\"name\":\"SWITZERLAND\",\"fmt\":\"%O%n%N%n%A%nCH-%Z %C\",\"require\":\"ACZ\",\"upper\":\"\",\"postprefix\":\"CH-\"}");
+ map.put("CI", "{\"name\":\"COTE D'IVOIRE\",\"fmt\":\"%N%n%O%n%X %A %C %X\"}");
+ map.put("CK", "{\"name\":\"COOK ISLANDS\"}");
+ map.put("CL", "{\"name\":\"CHILE\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\"}");
+ map.put("CM", "{\"name\":\"CAMEROON\"}");
+ map.put("CN", "{\"name\":\"CHINA\",\"lang\":\"zh\",\"languages\":\"zh\",\"lfmt\":\"%N%n%O%n%A%n%D%n%C%n%S, %Z\",\"fmt\":\"%Z%n%S%C%D%n%A%n%O%n%N\",\"require\":\"ACSZ\",\"upper\":\"S\",\"sublocality_name_type\":\"district\",\"width_overrides\":\"%S:S%C:S%D:S\",\"label_overrides\":[{\"field\":\"C\",\"label\":\"市/自治州/地区/盟\",\"lang\":\"zh\"},{\"field\":\"S\",\"label\":\"省/自治区/直辖市\",\"lang\":\"zh\"},{\"field\":\"D\",\"label\":\"区/县/旗\",\"lang\":\"zh\"}]}");
+ map.put("CO", "{\"name\":\"COLOMBIA\",\"fmt\":\"%N%n%O%n%A%n%C, %S, %Z\",\"require\":\"AS\",\"state_name_type\":\"department\",\"label_overrides\":[{\"field\":\"LL\",\"label\":\"Vereda\"},{\"field\":\"A3\",\"label\":\"Corregimiento\"},{\"field\":\"A2\",\"label\":\"Municipio\"}]}");
+ map.put("CR", "{\"name\":\"COSTA RICA\",\"fmt\":\"%N%n%O%n%A%n%S, %C%n%Z\",\"require\":\"ACS\"}");
+ map.put("CU", "{\"name\":\"CUBA\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%C %S%n%Z\"}");
+ map.put("CV", "{\"name\":\"CAPE VERDE\",\"lang\":\"pt\",\"languages\":\"pt\",\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\",\"state_name_type\":\"island\"}");
+ map.put("CW", "{\"name\":\"CURACAO\"}");
+ map.put("CX", "{\"name\":\"CHRISTMAS ISLAND\",\"fmt\":\"%O%n%N%n%A%n%C %S %Z\",\"upper\":\"CS\"}");
+ map.put("CY", "{\"name\":\"CYPRUS\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("CZ", "{\"name\":\"CZECH REP.\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\",\"label_overrides\":[{\"field\":\"NH\",\"label\":\"Obecní část\",\"lang\":\"cs\"},{\"field\":\"NH\",\"label\":\"Obecný časť\",\"lang\":\"sk\"},{\"field\":\"BI\",\"label\":\"Descriptive No.\"},{\"field\":\"BI\",\"label\":\"Popisné číslo\",\"lang\":\"cs\"},{\"field\":\"BI\",\"label\":\"Súpisné číslo\",\"lang\":\"sk\"},{\"field\":\"SN\",\"label\":\"Orientation No.\"},{\"field\":\"SN\",\"label\":\"Orientační číslo\",\"lang\":\"cs\"},{\"field\":\"SN\",\"label\":\"Orientačné číslo\",\"lang\":\"sk\"},{\"field\":\"S1\",\"label\":\"City District\"},{\"field\":\"S1\",\"label\":\"Městská část\",\"lang\":\"cs\"},{\"field\":\"S1\",\"label\":\"Mestská časť\",\"lang\":\"sk\"}]}");
+ map.put("DE", "{\"name\":\"GERMANY\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("DJ", "{\"name\":\"DJIBOUTI\"}");
+ map.put("DK", "{\"name\":\"DENMARK\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("DM", "{\"name\":\"DOMINICA\"}");
+ map.put("DO", "{\"name\":\"DOMINICAN REP.\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("DZ", "{\"name\":\"ALGERIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("EC", "{\"name\":\"ECUADOR\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C\",\"upper\":\"CZ\"}");
+ map.put("EE", "{\"name\":\"ESTONIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"label_overrides\":[{\"field\":\"C\",\"label\":\"Linn/vald\",\"lang\":\"et\"},{\"field\":\"C\",\"label\":\"City/Parish\",\"lang\":\"en\"}]}");
+ map.put("EG", "{\"name\":\"EGYPT\",\"lang\":\"ar\",\"languages\":\"ar\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\",\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\"}");
+ map.put("EH", "{\"name\":\"WESTERN SAHARA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("ER", "{\"name\":\"ERITREA\"}");
+ map.put("ES", "{\"name\":\"SPAIN\",\"lang\":\"es\",\"languages\":\"es~ca~gl~eu\",\"fmt\":\"%N%n%O%n%A%n%Z %C %S\",\"require\":\"ACSZ\",\"upper\":\"CS\",\"width_overrides\":\"%S:S\"}");
+ map.put("ET", "{\"name\":\"ETHIOPIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("FI", "{\"name\":\"FINLAND\",\"fmt\":\"%O%n%N%n%A%nFI-%Z %C\",\"require\":\"ACZ\",\"postprefix\":\"FI-\"}");
+ map.put("FJ", "{\"name\":\"FIJI\"}");
+ map.put("FK", "{\"name\":\"FALKLAND ISLANDS (MALVINAS)\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("FM", "{\"name\":\"MICRONESIA (Federated State of)\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("FO", "{\"name\":\"FAROE ISLANDS\",\"fmt\":\"%N%n%O%n%A%nFO%Z %C\",\"postprefix\":\"FO\"}");
+ map.put("FR", "{\"name\":\"FRANCE\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"CX\"}");
+ map.put("GA", "{\"name\":\"GABON\"}");
+ map.put("GB", "{\"name\":\"UNITED KINGDOM\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\",\"locality_name_type\":\"post_town\",\"label_overrides\":[{\"field\":\"LL\",\"message\":\"MSG_DEPENDENT_LOCALITY_LABEL\"},{\"field\":\"Z\",\"label\":\"Postcode\",\"lang\":\"en\"}]}");
+ map.put("GD", "{\"name\":\"GRENADA (WEST INDIES)\"}");
+ map.put("GE", "{\"name\":\"GEORGIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("GF", "{\"name\":\"FRENCH GUIANA\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("GG", "{\"name\":\"CHANNEL ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C%nGUERNSEY%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("GH", "{\"name\":\"GHANA\"}");
+ map.put("GI", "{\"name\":\"GIBRALTAR\",\"fmt\":\"%N%n%O%n%A%nGIBRALTAR%n%Z\",\"require\":\"A\"}");
+ map.put("GL", "{\"name\":\"GREENLAND\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("GM", "{\"name\":\"GAMBIA\"}");
+ map.put("GN", "{\"name\":\"GUINEA\",\"fmt\":\"%N%n%O%n%Z %A %C\"}");
+ map.put("GP", "{\"name\":\"GUADELOUPE\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("GQ", "{\"name\":\"EQUATORIAL GUINEA\"}");
+ map.put("GR", "{\"name\":\"GREECE\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("GS", "{\"name\":\"SOUTH GEORGIA\",\"fmt\":\"%N%n%O%n%A%n%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("GT", "{\"name\":\"GUATEMALA\",\"fmt\":\"%N%n%O%n%A%n%Z- %C\"}");
+ map.put("GU", "{\"name\":\"GUAM\",\"fmt\":\"%N%n%O%n%A%n%C %Z\",\"require\":\"ACZ\",\"upper\":\"ACNO\",\"zip_name_type\":\"zip\"}");
+ map.put("GW", "{\"name\":\"GUINEA-BISSAU\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("GY", "{\"name\":\"GUYANA\"}");
+ map.put("HK", "{\"name\":\"HONG KONG\",\"lang\":\"zh-Hant\",\"languages\":\"zh-Hant~en\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S\",\"fmt\":\"%S%n%C%n%A%n%O%n%N\",\"require\":\"AS\",\"upper\":\"S\",\"locality_name_type\":\"district\",\"state_name_type\":\"area\",\"width_overrides\":\"%S:S%C:L\",\"label_overrides\":[{\"field\":\"C\",\"label\":\"地区\",\"lang\":\"zh\"},{\"field\":\"C\",\"label\":\"地區\",\"lang\":\"zh-HK\"},{\"field\":\"C\",\"label\":\"地區\",\"lang\":\"zh-TW\"},{\"field\":\"CS\",\"label\":\"Flat / Room\",\"lang\":\"en\"},{\"field\":\"CS\",\"label\":\"單位編號\",\"lang\":\"zh-HK\"},{\"field\":\"BG\",\"label\":\"大廈名稱\",\"lang\":\"zh-HK\"}]}");
+ map.put("HM", "{\"name\":\"HEARD AND MCDONALD ISLANDS\",\"fmt\":\"%O%n%N%n%A%n%C %S %Z\",\"upper\":\"CS\"}");
+ map.put("HN", "{\"name\":\"HONDURAS\",\"fmt\":\"%N%n%O%n%A%n%C, %S%n%Z\",\"require\":\"ACS\"}");
+ map.put("HR", "{\"name\":\"CROATIA\",\"fmt\":\"%N%n%O%n%A%nHR-%Z %C\",\"postprefix\":\"HR-\"}");
+ map.put("HT", "{\"name\":\"HAITI\",\"fmt\":\"%N%n%O%n%A%nHT%Z %C\",\"postprefix\":\"HT\"}");
+ map.put("HU", "{\"name\":\"HUNGARY (Rep.)\",\"fmt\":\"%N%n%O%n%C%n%A%n%Z\",\"require\":\"ACZ\",\"upper\":\"ACNO\"}");
+ map.put("ID", "{\"name\":\"INDONESIA\",\"lang\":\"id\",\"languages\":\"id\",\"fmt\":\"%N%n%O%n%A%n%C%n%S %Z\",\"require\":\"AS\",\"label_overrides\":[{\"field\":\"A7\",\"label\":\"RT\"},{\"field\":\"A6\",\"label\":\"RW\"},{\"field\":\"A5\",\"label\":\"Dusun/Banjar\"},{\"field\":\"BI\",\"label\":\"Blok\"},{\"field\":\"A4\",\"message\":\"MSG_VILLAGE\"},{\"field\":\"A3\",\"label\":\"Kecamatan\"},{\"field\":\"S1\",\"label\":\"Pasar\"}]}");
+ map.put("IE", "{\"name\":\"IRELAND\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%D%n%C%n%S %Z\",\"sublocality_name_type\":\"townland\",\"state_name_type\":\"county\",\"zip_name_type\":\"eircode\",\"label_overrides\":[{\"field\":\"S\",\"label\":\"郡\",\"lang\":\"zh\"}]}");
+ map.put("IL", "{\"name\":\"ISRAEL\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("IM", "{\"name\":\"ISLE OF MAN\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("IN", "{\"name\":\"INDIA\",\"lang\":\"en\",\"languages\":\"en~hi\",\"fmt\":\"%N%n%O%n%A%n%C %Z%n%S\",\"require\":\"ACSZ\",\"state_name_type\":\"state\",\"zip_name_type\":\"pin\",\"label_overrides\":[{\"field\":\"S1\",\"label\":\"Sublocality 1\"},{\"field\":\"S2\",\"label\":\"Sublocality 2\"},{\"field\":\"S3\",\"label\":\"Sublocality 3\"},{\"field\":\"S4\",\"label\":\"Sublocality 4\"}]}");
+ map.put("IO", "{\"name\":\"BRITISH INDIAN OCEAN TERRITORY\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("IQ", "{\"name\":\"IRAQ\",\"fmt\":\"%O%n%N%n%A%n%C, %S%n%Z\",\"require\":\"ACS\",\"upper\":\"CS\"}");
+ map.put("IR", "{\"name\":\"IRAN\",\"lang\":\"fa\",\"languages\":\"fa\",\"fmt\":\"%O%n%N%n%S%n%C, %D%n%A%n%Z\",\"sublocality_name_type\":\"neighborhood\"}");
+ map.put("IS", "{\"name\":\"ICELAND\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("IT", "{\"name\":\"ITALY\",\"lang\":\"it\",\"languages\":\"it\",\"fmt\":\"%N%n%O%n%A%n%Z %C %S\",\"require\":\"ACSZ\",\"upper\":\"CS\",\"width_overrides\":\"%S:S\"}");
+ map.put("JE", "{\"name\":\"CHANNEL ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C%nJERSEY%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("JM", "{\"name\":\"JAMAICA\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%C%n%S %X\",\"require\":\"ACS\",\"state_name_type\":\"parish\"}");
+ map.put("JO", "{\"name\":\"JORDAN\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("JP", "{\"name\":\"JAPAN\",\"lang\":\"ja\",\"languages\":\"ja\",\"lfmt\":\"%N%n%O%n%A, %S%n%Z\",\"fmt\":\"〒%Z%n%S%n%A%n%O%n%N\",\"require\":\"ASZ\",\"upper\":\"S\",\"state_name_type\":\"prefecture\",\"width_overrides\":\"%S:S\",\"label_overrides\":[{\"field\":\"JED\",\"label\":\"Edaban\"},{\"field\":\"JED\",\"label\":\"枝番\",\"lang\":\"ja\"},{\"field\":\"JCH\",\"label\":\"Banchi\"},{\"field\":\"JCH\",\"label\":\"番地\",\"lang\":\"ja\"},{\"field\":\"JGA\",\"label\":\"Gaiku\"},{\"field\":\"JGA\",\"label\":\"街区\",\"lang\":\"ja\"},{\"field\":\"JKO\",\"label\":\"Koaza\"},{\"field\":\"JKO\",\"label\":\"小字\",\"lang\":\"ja\"},{\"field\":\"JOO\",\"label\":\"Ōaza\"},{\"field\":\"JOO\",\"label\":\"大字\",\"lang\":\"ja\"},{\"field\":\"JSS\",\"label\":\"Ku\"},{\"field\":\"JSS\",\"label\":\"区\",\"lang\":\"ja\"},{\"field\":\"JSH\",\"label\":\"Shi\"},{\"field\":\"JSH\",\"label\":\"市\",\"lang\":\"ja\"},{\"field\":\"JGN\",\"label\":\"Gun\"},{\"field\":\"JGN\",\"label\":\"郡\",\"lang\":\"ja\"}]}");
+ map.put("KE", "{\"name\":\"KENYA\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("KG", "{\"name\":\"KYRGYZSTAN\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("KH", "{\"name\":\"CAMBODIA\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("KI", "{\"name\":\"KIRIBATI\",\"fmt\":\"%N%n%O%n%A%n%S%n%C\",\"upper\":\"ACNOS\",\"state_name_type\":\"island\"}");
+ map.put("KM", "{\"name\":\"COMOROS\",\"upper\":\"AC\"}");
+ map.put("KN", "{\"name\":\"SAINT KITTS AND NEVIS\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%C, %S\",\"require\":\"ACS\",\"state_name_type\":\"island\"}");
+ map.put("KP", "{\"name\":\"NORTH KOREA\",\"lang\":\"ko\",\"languages\":\"ko\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S, %Z\",\"fmt\":\"%Z%n%S%n%C%n%A%n%O%n%N\"}");
+ map.put("KR", "{\"name\":\"SOUTH KOREA\",\"lang\":\"ko\",\"languages\":\"ko\",\"lfmt\":\"%N%n%O%n%A%n%D%n%C%n%S%n%Z\",\"fmt\":\"%S %C%D%n%A%n%O%n%N%n%Z\",\"require\":\"ACSZ\",\"upper\":\"Z\",\"sublocality_name_type\":\"district\",\"state_name_type\":\"do_si\",\"label_overrides\":[{\"field\":\"BI\",\"message\":\"MSG_STREET_NUMBER\"},{\"field\":\"S2\",\"message\":\"MSG_NEIGHBORHOOD\"},{\"field\":\"S4\",\"message\":\"MSG_STREET_NAME\"}]}");
+ map.put("KW", "{\"name\":\"KUWAIT\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("KY", "{\"name\":\"CAYMAN ISLANDS\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%S %Z\",\"require\":\"AS\",\"state_name_type\":\"island\"}");
+ map.put("KZ", "{\"name\":\"KAZAKHSTAN\",\"fmt\":\"%Z%n%S%n%C%n%A%n%O%n%N\"}");
+ map.put("LA", "{\"name\":\"LAO (PEOPLE'S DEM. REP.)\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("LB", "{\"name\":\"LEBANON\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("LC", "{\"name\":\"SAINT LUCIA\"}");
+ map.put("LI", "{\"name\":\"LIECHTENSTEIN\",\"fmt\":\"%O%n%N%n%A%nFL-%Z %C\",\"require\":\"ACZ\",\"postprefix\":\"FL-\"}");
+ map.put("LK", "{\"name\":\"SRI LANKA\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("LR", "{\"name\":\"LIBERIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("LS", "{\"name\":\"LESOTHO\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("LT", "{\"name\":\"LITHUANIA\",\"fmt\":\"%O%n%N%n%A%nLT-%Z %C\",\"postprefix\":\"LT-\"}");
+ map.put("LU", "{\"name\":\"LUXEMBOURG\",\"fmt\":\"%O%n%N%n%A%nL-%Z %C\",\"require\":\"ACZ\",\"postprefix\":\"L-\"}");
+ map.put("LV", "{\"name\":\"LATVIA\",\"fmt\":\"%N%n%O%n%A%n%C, %Z\"}");
+ map.put("LY", "{\"name\":\"LIBYA\"}");
+ map.put("MA", "{\"name\":\"MOROCCO\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("MC", "{\"name\":\"MONACO\",\"fmt\":\"%N%n%O%n%A%nMC-%Z %C %X\",\"postprefix\":\"MC-\"}");
+ map.put("MD", "{\"name\":\"Rep. MOLDOVA\",\"fmt\":\"%N%n%O%n%A%nMD-%Z %C\",\"postprefix\":\"MD-\"}");
+ map.put("ME", "{\"name\":\"MONTENEGRO\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("MF", "{\"name\":\"SAINT MARTIN\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("MG", "{\"name\":\"MADAGASCAR\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("MH", "{\"name\":\"MARSHALL ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("MK", "{\"name\":\"MACEDONIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("ML", "{\"name\":\"MALI\"}");
+ map.put("MM", "{\"name\":\"MYANMAR\",\"fmt\":\"%N%n%O%n%A%n%C, %Z\"}");
+ map.put("MN", "{\"name\":\"MONGOLIA\",\"fmt\":\"%N%n%O%n%A%n%C%n%S %Z\"}");
+ map.put("MO", "{\"name\":\"MACAO\",\"lang\":\"zh-Hant\",\"languages\":\"zh-Hant\",\"lfmt\":\"%N%n%O%n%A\",\"fmt\":\"%A%n%O%n%N\",\"require\":\"A\"}");
+ map.put("MP", "{\"name\":\"NORTHERN MARIANA ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("MQ", "{\"name\":\"MARTINIQUE\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("MR", "{\"name\":\"MAURITANIA\",\"upper\":\"AC\"}");
+ map.put("MS", "{\"name\":\"MONTSERRAT\"}");
+ map.put("MT", "{\"name\":\"MALTA\",\"fmt\":\"%N%n%O%n%A%n%C %Z\",\"upper\":\"CZ\"}");
+ map.put("MU", "{\"name\":\"MAURITIUS\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C\",\"upper\":\"CZ\"}");
+ map.put("MV", "{\"name\":\"MALDIVES\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("MW", "{\"name\":\"MALAWI\",\"fmt\":\"%N%n%O%n%A%n%C %X\"}");
+ map.put("MX", "{\"name\":\"MEXICO\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%D%n%Z %C, %S\",\"require\":\"ACZ\",\"upper\":\"CSZ\",\"sublocality_name_type\":\"neighborhood\",\"state_name_type\":\"state\",\"width_overrides\":\"%S:S\",\"label_overrides\":[{\"field\":\"S1\",\"label\":\"Delegación\"},{\"field\":\"S2\",\"label\":\"Supermanzana\"},{\"field\":\"S3\",\"label\":\"Manzana\"},{\"field\":\"LP\",\"label\":\"Lote\"}]}");
+ map.put("MY", "{\"name\":\"MALAYSIA\",\"lang\":\"ms\",\"languages\":\"ms\",\"fmt\":\"%N%n%O%n%A%n%D%n%Z %C%n%S\",\"require\":\"ACZ\",\"upper\":\"CS\",\"sublocality_name_type\":\"village_township\",\"state_name_type\":\"state\"}");
+ map.put("MZ", "{\"name\":\"MOZAMBIQUE\",\"lang\":\"pt\",\"languages\":\"pt\",\"fmt\":\"%N%n%O%n%A%n%Z %C%S\"}");
+ map.put("NA", "{\"name\":\"NAMIBIA\"}");
+ map.put("NC", "{\"name\":\"NEW CALEDONIA\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("NE", "{\"name\":\"NIGER\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("NF", "{\"name\":\"NORFOLK ISLAND\",\"fmt\":\"%O%n%N%n%A%n%C %S %Z\",\"upper\":\"CS\"}");
+ map.put("NG", "{\"name\":\"NIGERIA\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%D%n%C %Z%n%S\",\"upper\":\"CS\",\"state_name_type\":\"state\",\"label_overrides\":[{\"field\":\"D\",\"label\":\"Local government area\",\"lang\":\"en\"}]}");
+ map.put("NI", "{\"name\":\"NICARAGUA\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C, %S\",\"upper\":\"CS\",\"state_name_type\":\"department\"}");
+ map.put("NL", "{\"name\":\"NETHERLANDS\",\"fmt\":\"%O%n%N%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("NO", "{\"name\":\"NORWAY\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\",\"locality_name_type\":\"post_town\"}");
+ map.put("NP", "{\"name\":\"NEPAL\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("NR", "{\"name\":\"NAURU CENTRAL PACIFIC\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%S\",\"require\":\"AS\",\"state_name_type\":\"district\"}");
+ map.put("NU", "{\"name\":\"NIUE\"}");
+ map.put("NZ", "{\"name\":\"NEW ZEALAND\",\"fmt\":\"%N%n%O%n%A%n%D%n%C %Z\",\"require\":\"ACZ\"}");
+ map.put("OM", "{\"name\":\"OMAN\",\"fmt\":\"%N%n%O%n%A%n%Z%n%C\"}");
+ map.put("PA", "{\"name\":\"PANAMA (REP.)\",\"fmt\":\"%N%n%O%n%A%n%C%n%S\",\"upper\":\"CS\"}");
+ map.put("PE", "{\"name\":\"PERU\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%C %Z%n%S\",\"locality_name_type\":\"district\"}");
+ map.put("PF", "{\"name\":\"FRENCH POLYNESIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C %S\",\"require\":\"ACSZ\",\"upper\":\"CS\",\"state_name_type\":\"island\"}");
+ map.put("PG", "{\"name\":\"PAPUA NEW GUINEA\",\"fmt\":\"%N%n%O%n%A%n%C %Z %S\",\"require\":\"ACS\"}");
+ map.put("PH", "{\"name\":\"PHILIPPINES\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%D, %C%n%Z %S\"}");
+ map.put("PK", "{\"name\":\"PAKISTAN\",\"fmt\":\"%N%n%O%n%A%n%C-%Z\"}");
+ map.put("PL", "{\"name\":\"POLAND\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("PM", "{\"name\":\"ST. PIERRE AND MIQUELON\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("PN", "{\"name\":\"PITCAIRN\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("PR", "{\"name\":\"PUERTO RICO\",\"fmt\":\"%N%n%O%n%A%n%C PR %Z\",\"require\":\"ACZ\",\"upper\":\"ACNO\",\"zip_name_type\":\"zip\",\"postprefix\":\"PR \"}");
+ map.put("PS", "{\"name\":\"PALESTINIAN TERRITORY\"}");
+ map.put("PT", "{\"name\":\"PORTUGAL\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\"}");
+ map.put("PW", "{\"name\":\"PALAU\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("PY", "{\"name\":\"PARAGUAY\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("QA", "{\"name\":\"QATAR\",\"upper\":\"AC\"}");
+ map.put("RE", "{\"name\":\"REUNION\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("RO", "{\"name\":\"ROMANIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"upper\":\"AC\"}");
+ map.put("RS", "{\"name\":\"REPUBLIC OF SERBIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("RU", "{\"name\":\"RUSSIAN FEDERATION\",\"lang\":\"ru\",\"languages\":\"ru\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\",\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\",\"require\":\"ACSZ\",\"upper\":\"AC\",\"state_name_type\":\"oblast\",\"label_overrides\":[{\"field\":\"CS\",\"message\":\"MSG_OFFICE_UNIT_NUMBER\"}]}");
+ map.put("RW", "{\"name\":\"RWANDA\",\"upper\":\"AC\"}");
+ map.put("SA", "{\"name\":\"SAUDI ARABIA\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("SB", "{\"name\":\"SOLOMON ISLANDS\"}");
+ map.put("SC", "{\"name\":\"SEYCHELLES\",\"fmt\":\"%N%n%O%n%A%n%C%n%S\",\"upper\":\"S\",\"state_name_type\":\"island\"}");
+ map.put("SD", "{\"name\":\"SUDAN\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"locality_name_type\":\"district\"}");
+ map.put("SE", "{\"name\":\"SWEDEN\",\"fmt\":\"%O%n%N%n%A%nSE-%Z %C\",\"require\":\"ACZ\",\"locality_name_type\":\"post_town\",\"postprefix\":\"SE-\"}");
+ map.put("SG", "{\"name\":\"REP. OF SINGAPORE\",\"fmt\":\"%N%n%O%n%A%nSINGAPORE %Z\",\"require\":\"AZ\"}");
+ map.put("SH", "{\"name\":\"SAINT HELENA\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("SI", "{\"name\":\"SLOVENIA\",\"fmt\":\"%N%n%O%n%A%nSI-%Z %C\",\"postprefix\":\"SI-\"}");
+ map.put("SJ", "{\"name\":\"SVALBARD AND JAN MAYEN ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\",\"locality_name_type\":\"post_town\"}");
+ map.put("SK", "{\"name\":\"SLOVAKIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"ACZ\",\"label_overrides\":[{\"field\":\"NH\",\"label\":\"Obecní část\",\"lang\":\"cs\"},{\"field\":\"NH\",\"label\":\"Obecný časť\",\"lang\":\"sk\"},{\"field\":\"BI\",\"label\":\"Descriptive No.\"},{\"field\":\"BI\",\"label\":\"Popisné číslo\",\"lang\":\"cs\"},{\"field\":\"BI\",\"label\":\"Súpisné číslo\",\"lang\":\"sk\"},{\"field\":\"SN\",\"label\":\"Orientation No.\"},{\"field\":\"SN\",\"label\":\"Orientační číslo\",\"lang\":\"cs\"},{\"field\":\"SN\",\"label\":\"Orientačné číslo\",\"lang\":\"sk\"},{\"field\":\"S1\",\"label\":\"City District\"},{\"field\":\"S1\",\"label\":\"Městská část\",\"lang\":\"cs\"},{\"field\":\"S1\",\"label\":\"Mestská časť\",\"lang\":\"sk\"}]}");
+ map.put("SL", "{\"name\":\"SIERRA LEONE\"}");
+ map.put("SM", "{\"name\":\"SAN MARINO\",\"fmt\":\"%N%n%O%n%A%n%Z %C\",\"require\":\"AZ\"}");
+ map.put("SN", "{\"name\":\"SENEGAL\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("SO", "{\"name\":\"SOMALIA\",\"lang\":\"so\",\"languages\":\"so\",\"fmt\":\"%N%n%O%n%A%n%C, %S %Z\",\"require\":\"ACS\",\"upper\":\"ACS\"}");
+ map.put("SR", "{\"name\":\"SURINAME\",\"lang\":\"nl\",\"languages\":\"nl\",\"fmt\":\"%N%n%O%n%A%n%C%n%S\",\"upper\":\"AS\"}");
+ map.put("SS", "{\"name\":\"SOUTH SUDAN\"}");
+ map.put("ST", "{\"name\":\"SAO TOME AND PRINCIPE\"}");
+ map.put("SV", "{\"name\":\"EL SALVADOR\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%Z-%C%n%S\",\"require\":\"ACS\",\"upper\":\"CSZ\"}");
+ map.put("SX", "{\"name\":\"SINT MAARTEN\"}");
+ map.put("SY", "{\"name\":\"SYRIA\",\"locality_name_type\":\"district\"}");
+ map.put("SZ", "{\"name\":\"SWAZILAND\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"upper\":\"ACZ\"}");
+ map.put("TA", "{\"name\":\"TRISTAN DA CUNHA\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\"}");
+ map.put("TC", "{\"name\":\"TURKS AND CAICOS ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"ACZ\",\"upper\":\"CZ\"}");
+ map.put("TD", "{\"name\":\"CHAD\"}");
+ map.put("TF", "{\"name\":\"FRENCH SOUTHERN TERRITORIES\"}");
+ map.put("TG", "{\"name\":\"TOGO\"}");
+ map.put("TH", "{\"name\":\"THAILAND\",\"lang\":\"th\",\"languages\":\"th\",\"lfmt\":\"%N%n%O%n%A%n%D, %C%n%S %Z\",\"fmt\":\"%N%n%O%n%A%n%D %C%n%S %Z\",\"upper\":\"S\",\"label_overrides\":[{\"field\":\"C\",\"label\":\"Amphoe / Khet\"},{\"field\":\"C\",\"label\":\"อำเภอ/เขต\",\"lang\":\"th\"},{\"field\":\"C\",\"label\":\"アムプー/ケート\",\"lang\":\"ja\"},{\"field\":\"C\",\"label\":\"암프/켓\",\"lang\":\"ko\"},{\"field\":\"C\",\"label\":\"郡/区\",\"lang\":\"zh\"},{\"field\":\"C\",\"label\":\"郡/區\",\"lang\":\"zh-TW\"},{\"field\":\"C\",\"label\":\"郡/區\",\"lang\":\"zh-HK\"},{\"field\":\"D\",\"label\":\"Tambon / Khwaeng\"},{\"field\":\"D\",\"label\":\"ตำบล/แขวง\",\"lang\":\"th\"},{\"field\":\"D\",\"label\":\"タムボン/クウェーン\",\"lang\":\"ja\"},{\"field\":\"D\",\"label\":\"땀본/쾡\",\"lang\":\"ko\"},{\"field\":\"D\",\"label\":\"区/小区\",\"lang\":\"zh\"},{\"field\":\"D\",\"label\":\"區/小區\",\"lang\":\"zh-TW\"},{\"field\":\"D\",\"label\":\"區/小區\",\"lang\":\"zh-HK\"}]}");
+ map.put("TJ", "{\"name\":\"TAJIKISTAN\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("TK", "{\"name\":\"TOKELAU\"}");
+ map.put("TL", "{\"name\":\"TIMOR-LESTE\"}");
+ map.put("TM", "{\"name\":\"TURKMENISTAN\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("TN", "{\"name\":\"TUNISIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("TO", "{\"name\":\"TONGA\"}");
+ map.put("TR", "{\"name\":\"TURKEY\",\"lang\":\"tr\",\"languages\":\"tr\",\"fmt\":\"%N%n%O%n%A%n%Z %C/%S\",\"require\":\"ACZ\",\"locality_name_type\":\"district\",\"label_overrides\":[{\"field\":\"C\",\"label\":\"İlçe\",\"lang\":\"tr\"},{\"field\":\"S\",\"label\":\"İl\",\"lang\":\"tr\"},{\"field\":\"A4\",\"message\":\"MSG_NEIGHBORHOOD\"}]}");
+ map.put("TT", "{\"name\":\"TRINIDAD AND TOBAGO\"}");
+ map.put("TV", "{\"name\":\"TUVALU\",\"lang\":\"tyv\",\"languages\":\"tyv\",\"fmt\":\"%N%n%O%n%A%n%C%n%S\",\"upper\":\"ACS\",\"state_name_type\":\"island\"}");
+ map.put("TW", "{\"name\":\"TAIWAN\",\"lang\":\"zh-Hant\",\"languages\":\"zh-Hant\",\"lfmt\":\"%N%n%O%n%A%n%C, %S %Z\",\"fmt\":\"%Z%n%S%C%n%A%n%O%n%N\",\"require\":\"ACSZ\",\"state_name_type\":\"county\"}");
+ map.put("TZ", "{\"name\":\"TANZANIA (UNITED REP.)\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("UA", "{\"name\":\"UKRAINE\",\"lang\":\"uk\",\"languages\":\"uk\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\",\"fmt\":\"%N%n%O%n%A%n%C%n%S%n%Z\",\"require\":\"ACSZ\",\"state_name_type\":\"oblast\",\"label_overrides\":[{\"field\":\"CS\",\"message\":\"MSG_OFFICE_UNIT_NUMBER\"}]}");
+ map.put("UG", "{\"name\":\"UGANDA\"}");
+ map.put("UM", "{\"name\":\"UNITED STATES MINOR OUTLYING ISLANDS\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACS\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("US", "{\"name\":\"UNITED STATES\",\"lang\":\"en\",\"languages\":\"en\",\"fmt\":\"%N%n%O%n%A%n%C, %S %Z\",\"require\":\"ACSZ\",\"upper\":\"CS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\",\"width_overrides\":\"%S:S\"}");
+ map.put("UY", "{\"name\":\"URUGUAY\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%Z %C %S\",\"upper\":\"CS\"}");
+ map.put("UZ", "{\"name\":\"UZBEKISTAN\",\"fmt\":\"%N%n%O%n%A%n%Z %C%n%S\",\"upper\":\"CS\"}");
+ map.put("VA", "{\"name\":\"VATICAN\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("VC", "{\"name\":\"SAINT VINCENT AND THE GRENADINES (ANTILLES)\",\"fmt\":\"%N%n%O%n%A%n%C %Z\"}");
+ map.put("VE", "{\"name\":\"VENEZUELA\",\"lang\":\"es\",\"languages\":\"es\",\"fmt\":\"%N%n%O%n%A%n%C %Z, %S\",\"require\":\"ACS\",\"upper\":\"CS\",\"state_name_type\":\"state\"}");
+ map.put("VG", "{\"name\":\"VIRGIN ISLANDS (BRITISH)\",\"fmt\":\"%N%n%O%n%A%n%C%n%Z\",\"require\":\"A\"}");
+ map.put("VI", "{\"name\":\"VIRGIN ISLANDS (U.S.)\",\"fmt\":\"%N%n%O%n%A%n%C %S %Z\",\"require\":\"ACSZ\",\"upper\":\"ACNOS\",\"state_name_type\":\"state\",\"zip_name_type\":\"zip\"}");
+ map.put("VN", "{\"name\":\"VIET NAM\",\"lang\":\"vi\",\"languages\":\"vi\",\"lfmt\":\"%N%n%O%n%A%n%C%n%S %Z\",\"fmt\":\"%N%n%O%n%A%n%C%n%S %Z\",\"label_overrides\":[{\"field\":\"S1\",\"label\":\"Ward/Township/Commune\"},{\"field\":\"S1\",\"label\":\"Phường/Thị trấn/Xã\",\"lang\":\"vi\"}]}");
+ map.put("VU", "{\"name\":\"VANUATU\"}");
+ map.put("WF", "{\"name\":\"WALLIS AND FUTUNA ISLANDS\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("WS", "{\"name\":\"SAMOA\"}");
+ map.put("XK", "{\"name\":\"KOSOVO\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("YE", "{\"name\":\"YEMEN\"}");
+ map.put("YT", "{\"name\":\"MAYOTTE\",\"fmt\":\"%O%n%N%n%A%n%Z %C %X\",\"require\":\"ACZ\",\"upper\":\"ACX\"}");
+ map.put("ZA", "{\"name\":\"SOUTH AFRICA\",\"fmt\":\"%N%n%O%n%A%n%D%n%C%n%Z\",\"require\":\"ACZ\"}");
+ map.put("ZM", "{\"name\":\"ZAMBIA\",\"fmt\":\"%N%n%O%n%A%n%Z %C\"}");
+ map.put("ZW", "{\"name\":\"ZIMBABWE\"}");
+ map.put("ZZ", "{\"fmt\":\"%N%n%O%n%A%n%C\",\"require\":\"AC\",\"upper\":\"C\",\"sublocality_name_type\":\"suburb\",\"locality_name_type\":\"city\",\"state_name_type\":\"province\",\"zip_name_type\":\"postal\"}");
+ return Collections.unmodifiableMap(map);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/SimpleClientCacheManager.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/SimpleClientCacheManager.java
new file mode 100644
index 00000000000..4cbad684e4f
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/SimpleClientCacheManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+/**
+ * A simple implementation of ClientCacheManager which doesn't do any caching on its own.
+ */
+// This is an external class and part of the widget's public API.
+// TODO: Review public API for external classes and tidy JavaDoc.
+public final class SimpleClientCacheManager implements ClientCacheManager {
+ // URL to get public address data.
+ static final String PUBLIC_ADDRESS_SERVER = "https://chromium-i18n.appspot.com/ssl-address";
+
+ @Override
+ public String get(String key) {
+ return "";
+ }
+
+ @Override
+ public void put(String key, String data) {
+ }
+
+ @Override
+ public String getAddressServerUrl() {
+ return PUBLIC_ADDRESS_SERVER;
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardAddressVerifier.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardAddressVerifier.java
new file mode 100644
index 00000000000..8eb369301e1
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardAddressVerifier.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import static com.google.i18n.addressinput.common.AddressField.ADMIN_AREA;
+import static com.google.i18n.addressinput.common.AddressField.COUNTRY;
+import static com.google.i18n.addressinput.common.AddressField.DEPENDENT_LOCALITY;
+import static com.google.i18n.addressinput.common.AddressField.LOCALITY;
+import static com.google.i18n.addressinput.common.AddressField.ORGANIZATION;
+import static com.google.i18n.addressinput.common.AddressField.POSTAL_CODE;
+import static com.google.i18n.addressinput.common.AddressField.RECIPIENT;
+import static com.google.i18n.addressinput.common.AddressField.SORTING_CODE;
+import static com.google.i18n.addressinput.common.AddressField.STREET_ADDRESS;
+
+import com.google.i18n.addressinput.common.LookupKey.ScriptType;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Performs various consistency checks on an AddressData. This uses a {@link FieldVerifier} to check
+ * each field in the address.
+ */
+public final class StandardAddressVerifier {
+
+ private static final String LOCALE_DELIMITER = "--";
+
+ protected final FieldVerifier rootVerifier;
+
+ protected final Map<AddressField, List<AddressProblemType>> problemMap;
+
+ /**
+ * Uses the rootVerifier to perform the standard checks on the address fields, as defined in
+ * {@link StandardChecks}.
+ */
+ public StandardAddressVerifier(FieldVerifier rootVerifier) {
+ this(rootVerifier, StandardChecks.PROBLEM_MAP);
+ }
+
+ /**
+ * Uses the rootVerifier to perform the given checks on the address fields. A reference to
+ * problemMap is maintained. It is not modified by this class, and should not be modified
+ * subsequent to this call.
+ */
+ public StandardAddressVerifier(FieldVerifier rootVerifier,
+ Map<AddressField, List<AddressProblemType>> problemMap) {
+ this.rootVerifier = rootVerifier;
+ this.problemMap = problemMap;
+ }
+
+ /**
+ * Verifies the address, reporting problems to problems.
+ */
+ public void verify(AddressData address, AddressProblems problems) {
+ new Verifier(address, problems, new NotifyingListener()).run();
+ }
+
+ public void verifyAsync(
+ AddressData address, AddressProblems problems, DataLoadListener listener) {
+ Thread verifier = new Thread(new Verifier(address, problems, listener));
+ verifier.start();
+ }
+
+ /**
+ * Verifies only the specified fields in the address.
+ */
+ public void verifyFields(
+ AddressData address, AddressProblems problems, EnumSet<AddressField> addressFieldsToVerify) {
+ new Verifier(address, problems, new NotifyingListener(), addressFieldsToVerify).run();
+ }
+
+ private class Verifier implements Runnable {
+ private AddressData address;
+ private AddressProblems problems;
+ private DataLoadListener listener;
+ private EnumSet<AddressField> addressFieldsToVerify;
+
+ Verifier(AddressData address, AddressProblems problems, DataLoadListener listener) {
+ this(address, problems, listener, EnumSet.allOf(AddressField.class));
+ }
+
+ Verifier(
+ AddressData address, AddressProblems problems, DataLoadListener listener,
+ EnumSet<AddressField> addressFieldsToVerify) {
+ this.address = address;
+ this.problems = problems;
+ this.listener = listener;
+ this.addressFieldsToVerify = addressFieldsToVerify;
+ }
+
+ @Override
+ public void run() {
+ listener.dataLoadingBegin();
+
+ FieldVerifier v = rootVerifier;
+
+ ScriptType script = null;
+ if (address.getLanguageCode() != null) {
+ if (Util.isExplicitLatinScript(address.getLanguageCode())) {
+ script = ScriptType.LATIN;
+ } else {
+ script = ScriptType.LOCAL;
+ }
+ }
+
+ // The first four calls refine the verifier, so must come first, and in this
+ // order.
+ verifyFieldIfSelected(script, v, COUNTRY, address.getPostalCountry(), problems);
+ if (isFieldSelected(COUNTRY) && problems.isEmpty()) {
+ // Ensure we start with the right language country sub-key.
+ String countrySubKey = address.getPostalCountry();
+ if (address.getLanguageCode() != null && !address.getLanguageCode().equals("")) {
+ countrySubKey += (LOCALE_DELIMITER + address.getLanguageCode());
+ }
+ v = v.refineVerifier(countrySubKey);
+ verifyFieldIfSelected(script, v, ADMIN_AREA, address.getAdministrativeArea(), problems);
+ if (isFieldSelected(ADMIN_AREA) && problems.isEmpty()) {
+ v = v.refineVerifier(address.getAdministrativeArea());
+ verifyFieldIfSelected(script, v, LOCALITY, address.getLocality(), problems);
+ if (isFieldSelected(LOCALITY) && problems.isEmpty()) {
+ v = v.refineVerifier(address.getLocality());
+ verifyFieldIfSelected(
+ script, v, DEPENDENT_LOCALITY, address.getDependentLocality(), problems);
+ if (isFieldSelected(DEPENDENT_LOCALITY) && problems.isEmpty()) {
+ v = v.refineVerifier(address.getDependentLocality());
+ }
+ }
+ }
+ }
+
+ // This concatenation is for the purpose of validation only - the important part is to check
+ // we have at least one value filled in for lower-level components.
+ String street =
+ Util.joinAndSkipNulls("\n", address.getAddressLine1(),
+ address.getAddressLine2());
+
+ // Remaining calls don't change the field verifier.
+ verifyFieldIfSelected(script, v, POSTAL_CODE, address.getPostalCode(), problems);
+ verifyFieldIfSelected(script, v, STREET_ADDRESS, street, problems);
+ verifyFieldIfSelected(script, v, SORTING_CODE, address.getSortingCode(), problems);
+ verifyFieldIfSelected(script, v, ORGANIZATION, address.getOrganization(), problems);
+ verifyFieldIfSelected(script, v, RECIPIENT, address.getRecipient(), problems);
+
+ postVerify(v, address, problems);
+
+ listener.dataLoadingEnd();
+ }
+
+ /**
+ * Skips address fields that are not included in {@code addressFieldsToVerify}.
+ */
+ private boolean verifyFieldIfSelected(LookupKey.ScriptType script, FieldVerifier verifier,
+ AddressField field, String value, AddressProblems problems) {
+ if (!isFieldSelected(field)) {
+ return true;
+ }
+
+ return verifyField(script, verifier, field, value, problems);
+ }
+
+ private boolean isFieldSelected(AddressField field) {
+ return addressFieldsToVerify.contains(field);
+ }
+ }
+
+ /**
+ * Hook to perform any final processing using the final verifier. Default does no additional
+ * verification.
+ */
+ protected void postVerify(FieldVerifier verifier, AddressData address, AddressProblems problems) {
+ }
+
+ /**
+ * Hook called by verify with each verifiable field, in order. Override to provide pre- or
+ * post-checks for all fields.
+ */
+ protected boolean verifyField(LookupKey.ScriptType script, FieldVerifier verifier,
+ AddressField field, String value, AddressProblems problems) {
+ Iterator<AddressProblemType> iter = getProblemIterator(field);
+ while (iter.hasNext()) {
+ AddressProblemType prob = iter.next();
+ if (!verifyProblemField(script, verifier, prob, field, value, problems)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Hook for on-the-fly modification of the problem list. Override to change the problems to
+ * check for a particular field. Generally, changing the problemMap passed to the constructor
+ * is a better approach.
+ */
+ protected Iterator<AddressProblemType> getProblemIterator(AddressField field) {
+ List<AddressProblemType> list = problemMap.get(field);
+ if (list == null) {
+ list = Collections.emptyList();
+ }
+ return list.iterator();
+ }
+
+ /**
+ * Hook for adding special checks for particular problems and/or fields.
+ */
+ protected boolean verifyProblemField(LookupKey.ScriptType script, FieldVerifier verifier,
+ AddressProblemType problem, AddressField field, String datum, AddressProblems problems) {
+ return verifier.check(script, problem, field, datum, problems);
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardChecks.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardChecks.java
new file mode 100644
index 00000000000..84ad95b3a5e
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/StandardChecks.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import static com.google.i18n.addressinput.common.AddressField.ADMIN_AREA;
+import static com.google.i18n.addressinput.common.AddressField.COUNTRY;
+import static com.google.i18n.addressinput.common.AddressField.DEPENDENT_LOCALITY;
+import static com.google.i18n.addressinput.common.AddressField.LOCALITY;
+import static com.google.i18n.addressinput.common.AddressField.ORGANIZATION;
+import static com.google.i18n.addressinput.common.AddressField.POSTAL_CODE;
+import static com.google.i18n.addressinput.common.AddressField.RECIPIENT;
+import static com.google.i18n.addressinput.common.AddressField.SORTING_CODE;
+import static com.google.i18n.addressinput.common.AddressField.STREET_ADDRESS;
+import static com.google.i18n.addressinput.common.AddressProblemType.INVALID_FORMAT;
+import static com.google.i18n.addressinput.common.AddressProblemType.MISMATCHING_VALUE;
+import static com.google.i18n.addressinput.common.AddressProblemType.MISSING_REQUIRED_FIELD;
+import static com.google.i18n.addressinput.common.AddressProblemType.UNEXPECTED_FIELD;
+import static com.google.i18n.addressinput.common.AddressProblemType.UNKNOWN_VALUE;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Loader for a map defining the standard checks to perform on AddressFields.
+ */
+public final class StandardChecks {
+ private StandardChecks() {
+ }
+
+ public static final Map<AddressField, List<AddressProblemType>> PROBLEM_MAP;
+
+ static {
+ Map<AddressField, List<AddressProblemType>> map =
+ new HashMap<AddressField, List<AddressProblemType>>();
+
+ addToMap(map, COUNTRY, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD, UNKNOWN_VALUE);
+ addToMap(map, ADMIN_AREA, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD, UNKNOWN_VALUE);
+ addToMap(map, LOCALITY, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD, UNKNOWN_VALUE);
+ addToMap(map, DEPENDENT_LOCALITY, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD, UNKNOWN_VALUE);
+ addToMap(map, POSTAL_CODE, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD, INVALID_FORMAT,
+ MISMATCHING_VALUE);
+ addToMap(map, STREET_ADDRESS, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD);
+ addToMap(map, SORTING_CODE, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD);
+ addToMap(map, ORGANIZATION, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD);
+ addToMap(map, RECIPIENT, UNEXPECTED_FIELD, MISSING_REQUIRED_FIELD);
+
+ PROBLEM_MAP = Collections.unmodifiableMap(map);
+ }
+
+ private static void addToMap(Map<AddressField, List<AddressProblemType>> map, AddressField field,
+ AddressProblemType... problems) {
+ map.put(field, Collections.unmodifiableList(Arrays.asList(problems)));
+ }
+}
diff --git a/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/Util.java b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/Util.java
new file mode 100644
index 00000000000..c63d93e981d
--- /dev/null
+++ b/chromium/third_party/libaddressinput/src/common/src/main/java/com/google/i18n/addressinput/common/Util.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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.google.i18n.addressinput.common;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility functions used by the address widget.
+ */
+public final class Util {
+ /**
+ * This variable is in upper-case, since we convert the language code to upper case before doing
+ * string comparison.
+ */
+ private static final String LATIN_SCRIPT = "LATN";
+
+ /**
+ * Map of countries that have non-latin local names, with the language that their local names
+ * are in. We only list a country here if we have the appropriate data. Only language sub-tags
+ * are listed.
+ * TODO(user): Delete this: the information should be read from RegionDataConstants.java.
+ */
+ private static final Map<String, String> nonLatinLocalLanguageCountries =
+ new HashMap<String, String>();
+ static {
+ nonLatinLocalLanguageCountries.put("AE", "ar");
+ nonLatinLocalLanguageCountries.put("AM", "hy");
+ nonLatinLocalLanguageCountries.put("CN", "zh");
+ nonLatinLocalLanguageCountries.put("EG", "ar");
+ nonLatinLocalLanguageCountries.put("HK", "zh");
+ nonLatinLocalLanguageCountries.put("JP", "ja");
+ nonLatinLocalLanguageCountries.put("KP", "ko");
+ nonLatinLocalLanguageCountries.put("KR", "ko");
+ nonLatinLocalLanguageCountries.put("MO", "zh");
+ nonLatinLocalLanguageCountries.put("RU", "ru");
+ nonLatinLocalLanguageCountries.put("TH", "th");
+ nonLatinLocalLanguageCountries.put("TW", "zh");
+ nonLatinLocalLanguageCountries.put("UA", "uk");
+ nonLatinLocalLanguageCountries.put("VN", "vi");
+ }
+
+ /**
+ * Cannot instantiate this class - private constructor.
+ */
+ private Util() {
+ }
+
+ /**
+ * Returns true if the language code is explicitly marked to be in the latin script. For
+ * example, "zh-Latn" would return true, but "zh-TW", "en" and "zh" would all return false.
+ */
+ public static boolean isExplicitLatinScript(String languageCode) {
+ // Convert to upper-case for easier comparison.
+ languageCode = toUpperCaseLocaleIndependent(languageCode);
+ // Check to see if the language code contains a script modifier.
+ final Pattern languageCodePattern = Pattern.compile("\\w{2,3}[-_](\\w{4})");
+ Matcher m = languageCodePattern.matcher(languageCode);
+ if (m.lookingAt()) {
+ String script = m.group(1);
+ if (script.equals(LATIN_SCRIPT)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the language subtag of a language code. For example, returns "zh" if given "zh-Hans",
+ * "zh-CN" or other "zh" variants. If no language subtag can be found or the language tag is
+ * malformed, returns "und".
+ */
+ public static String getLanguageSubtag(String languageCode) {
+ final Pattern languageCodePattern = Pattern.compile("(\\w{2,3})(?:[-_]\\w{4})?(?:[-_]\\w{2})?");
+ Matcher m = languageCodePattern.matcher(languageCode);
+ if (m.matches()) {
+ return toLowerCaseLocaleIndependent(m.group(1));
+ }
+ return "und";
+ }
+
+ /**
+ * Trims the string. If the field is empty after trimming, returns null instead. Note that this
+ * only trims ASCII white-space.
+ */
+ static String trimToNull(String originalStr) {
+ if (originalStr == null) {
+ return null;
+ }
+ String trimmedString = originalStr.trim();
+ return (trimmedString.length() == 0) ? null : trimmedString;
+ }
+
+ /**
+ * Throws an exception if the object is null, with a generic error message.
+ */
+ static <T> T checkNotNull(T o) {
+ return checkNotNull(o, "This object should not be null.");
+ }
+
+ /**
+ * Throws an exception if the object is null, with the error message supplied.
+ */
+ static <T> T checkNotNull(T o, String message) {
+ if (o == null) {
+ throw new NullPointerException(message);
+ }
+ return o;
+ }
+
+ /**
+ * Joins input string with the given separator. If an input string is null, it will be skipped.
+ */
+ static String joinAndSkipNulls(String separator, String... strings) {
+ StringBuilder sb = null;
+ for (String s : strings) {
+ if (s != null) {
+ s = s.trim();
+ if (s.length() > 0) {
+ if (sb == null) {
+ sb = new StringBuilder(s);
+ } else {
+ sb.append(separator).append(s);
+ }
+ }
+ }
+ }
+ return sb == null ? null : sb.toString();
+ }
+
+ /**
+ * Builds a map of the lower-cased values of the keys, names and local names provided. Each name
+ * and local name is mapped to its respective key in the map.
+ *
+ * @throws IllegalStateException if the names or lnames array is greater than the keys array.
+ */
+ static Map<String, String> buildNameToKeyMap(String[] keys, String[] names, String[] lnames) {
+ if (keys == null) {
+ return null;
+ }
+
+ Map<String, String> nameToKeyMap = new HashMap<String, String>();
+
+ int keyLength = keys.length;
+ for (String k : keys) {
+ nameToKeyMap.put(toLowerCaseLocaleIndependent(k), k);
+ }
+ if (names != null) {
+ if (names.length > keyLength) {
+ throw new IllegalStateException("names length (" + names.length
+ + ") is greater than keys length (" + keys.length + ")");
+ }
+ for (int i = 0; i < keyLength; i++) {
+ // If we have less names than keys, we ignore all missing names. This happens
+ // generally because reg-ex splitting methods on different platforms (java, js etc)
+ // behave differently in the default case. Since missing names are fine, we opt to
+ // be more robust here.
+ if (i < names.length && names[i].length() > 0) {
+ nameToKeyMap.put(toLowerCaseLocaleIndependent(names[i]), keys[i]);
+ }
+ }
+ }
+ if (lnames != null) {
+ if (lnames.length > keyLength) {
+ throw new IllegalStateException("lnames length (" + lnames.length
+ + ") is greater than keys length (" + keys.length + ")");
+ }
+ for (int i = 0; i < keyLength; i++) {
+ if (i < lnames.length && lnames[i].length() > 0) {
+ nameToKeyMap.put(toLowerCaseLocaleIndependent(lnames[i]), keys[i]);
+ }
+ }
+ }
+ return nameToKeyMap;
+ }
+
+ /**
+ * Returns a language code that the widget can use when fetching data, based on a {@link
+ * java.util.Locale} language and the current selected country in the address widget. This
+ * method is necessary since we have to determine later whether a language is "local" or "latin"
+ * for certain countries.
+ *
+ * @param language the current user language
+ * @param currentCountry the current selected country
+ * @return a language code string in BCP-47 format (e.g. "en", "zh-Latn", "zh-Hans" or
+ * "en-US").
+ */
+ public static String getWidgetCompatibleLanguageCode(Locale language, String currentCountry) {
+ String country = toUpperCaseLocaleIndependent(currentCountry);
+ // Only do something if the country is one of those where we have names in the local
+ // language as well as in latin script.
+ if (nonLatinLocalLanguageCountries.containsKey(country)) {
+ String languageTag = language.getLanguage();
+ // Only do something if the language tag is _not_ the local language.
+ if (!languageTag.equals(nonLatinLocalLanguageCountries.get(country))) {
+ // Build up the language tag with the country and language specified, and add in the
+ // script-tag of "Latn" explicitly, since this is _not_ a local language. This means
+ // that we might create a language tag of "th-Latn", which is not what the actual
+ // language being used is, but it indicates that we prefer "Latn" names to whatever
+ // the local alternative was.
+ StringBuilder languageTagBuilder = new StringBuilder(languageTag);
+ languageTagBuilder.append("_latn");
+ if (language.getCountry().length() > 0) {
+ languageTagBuilder.append("_");
+ languageTagBuilder.append(language.getCountry());
+ }
+ return languageTagBuilder.toString();
+ }
+ }
+ return language.toString();
+ }
+
+ /**
+ * Converts all of the characters in this String to lower case using the rules of English. This is
+ * equivalent to calling toLowerCase(Locale.ENGLISH). Thus avoiding locale-sensitive case folding
+ * such as the Turkish i, which could mess e.g. with lookup keys and country codes.
+ */
+ public static String toLowerCaseLocaleIndependent(String value) {
+ return (value != null) ? value.toLowerCase(Locale.ENGLISH) : null;
+ }
+
+ /**
+ * Converts all of the characters in this String to upper case using the rules of English. This is
+ * equivalent to calling toUpperCase(Locale.ENGLISH). Thus avoiding locale-sensitive case folding
+ * such as the Turkish i, which could mess e.g. with lookup keys and country codes.
+ */
+ public static String toUpperCaseLocaleIndependent(String value) {
+ return (value != null) ? value.toUpperCase(Locale.ENGLISH) : null;
+ }
+}