Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / libaddressinput / src / java / src / com / android / i18n / addressinput / AddressWidget.java
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.i18n.addressinput;
18
19 import android.app.ProgressDialog;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.telephony.TelephonyManager;
23 import android.util.Log;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.AdapterView;
28 import android.widget.ArrayAdapter;
29 import android.widget.EditText;
30 import android.widget.LinearLayout;
31 import android.widget.LinearLayout.LayoutParams;
32 import android.widget.Spinner;
33 import android.widget.TextView;
34
35 import com.android.i18n.addressinput.AddressField.WidthType;
36 import com.android.i18n.addressinput.AddressUiComponent.UiComponent;
37 import com.android.i18n.addressinput.LookupKey.KeyType;
38 import com.android.i18n.addressinput.LookupKey.ScriptType;
39
40 import java.text.Collator;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.EnumMap;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
48
49 /**
50  * Address widget that lays out address fields, validate and format addresses according to local
51  * customs.
52  */
53 public class AddressWidget implements AdapterView.OnItemSelectedListener {
54   private Context context;
55
56   private ViewGroup rootView;
57
58   private LayoutInflater inflater;
59
60   private CacheData cacheData;
61
62   // A map for all address fields.
63   private final EnumMap<AddressField, AddressUiComponent> inputWidgets =
64       new EnumMap<AddressField, AddressUiComponent>(AddressField.class);
65
66   private FormController formController;
67
68   private FormatInterpreter formatInterpreter;
69
70   private FormOptions formOptions;
71
72   private StandardAddressVerifier verifier;
73
74   private ProgressDialog progressDialog;
75
76   private String currentRegion;
77
78   // The current language the widget uses in BCP47 format. It differs from the default locale of
79   // the phone in that it contains information on the script to use.
80   private String widgetLocale;
81
82   private ScriptType script;
83
84   // Possible labels that could be applied to the admin area field of the current country.
85   // Examples include "state", "province", "emirate", etc.
86   private static final Map<String, Integer> ADMIN_LABELS;
87   // Possible labels that could be applied to the locality (city) field of the current country.
88   // Examples include "city" or "district".
89   private static final Map<String, Integer> LOCALITY_LABELS;
90   // Possible labels that could be applied to the sublocality field of the current country.
91   // Examples include "suburb" or "neighborhood".
92   private static final Map<String, Integer> SUBLOCALITY_LABELS;
93
94   private static final FormOptions SHOW_ALL_FIELDS = new FormOptions.Builder().build();
95
96   // The appropriate label that should be applied to the zip code field of the current country.
97   private enum ZipLabel {
98     ZIP,
99     POSTAL
100   }
101
102   private ZipLabel zipLabel;
103
104   static {
105     Map<String, Integer> adminLabelMap = new HashMap<String, Integer>(15);
106     adminLabelMap.put("area", R.string.i18n_area);
107     adminLabelMap.put("county", R.string.i18n_county);
108     adminLabelMap.put("department", R.string.i18n_department);
109     adminLabelMap.put("district", R.string.i18n_district);
110     adminLabelMap.put("do_si", R.string.i18n_do_si);
111     adminLabelMap.put("emirate", R.string.i18n_emirate);
112     adminLabelMap.put("island", R.string.i18n_island);
113     adminLabelMap.put("oblast", R.string.i18n_oblast);
114     adminLabelMap.put("parish", R.string.i18n_parish);
115     adminLabelMap.put("prefecture", R.string.i18n_prefecture);
116     adminLabelMap.put("province", R.string.i18n_province);
117     adminLabelMap.put("state", R.string.i18n_state);
118     ADMIN_LABELS = Collections.unmodifiableMap(adminLabelMap);
119
120     Map<String, Integer> localityLabelMap = new HashMap<String, Integer>(2);
121     localityLabelMap.put("city", R.string.i18n_locality_label);
122     localityLabelMap.put("district", R.string.i18n_district);
123     localityLabelMap.put("post_town", R.string.i18n_post_town);
124     LOCALITY_LABELS = Collections.unmodifiableMap(localityLabelMap);
125
126     Map<String, Integer> sublocalityLabelMap = new HashMap<String, Integer>(2);
127     sublocalityLabelMap.put("suburb", R.string.i18n_suburb);
128     sublocalityLabelMap.put("district", R.string.i18n_district);
129     sublocalityLabelMap.put("neighborhood", R.string.i18n_neighborhood);
130     sublocalityLabelMap.put("village_township", R.string.i18n_village_township);
131     SUBLOCALITY_LABELS = Collections.unmodifiableMap(sublocalityLabelMap);
132   }
133
134   // Need handler for callbacks to the UI thread
135   final Handler handler = new Handler();
136
137   final Runnable updateMultipleFields = new Runnable() {
138     @Override
139     public void run() {
140       updateFields();
141     }
142   };
143
144   private class UpdateRunnable implements Runnable {
145     private AddressField myId;
146
147     public UpdateRunnable(AddressField id) {
148       myId = id;
149     }
150
151     @Override
152     public void run() {
153       updateInputWidget(myId);
154     }
155   }
156
157   private static class AddressSpinnerInfo {
158     private Spinner view;
159
160     private AddressField id;
161
162     private AddressField parentId;
163
164     private ArrayAdapter<String> adapter;
165
166     private List<RegionData> currentRegions;
167
168     @SuppressWarnings("unchecked")
169     public AddressSpinnerInfo(Spinner view, AddressField id, AddressField parentId) {
170       this.view = view;
171       this.id = id;
172       this.parentId = parentId;
173       this.adapter = (ArrayAdapter<String>) view.getAdapter();
174     }
175
176     public void setSpinnerList(List<RegionData> list, String defaultKey) {
177       currentRegions = list;
178       adapter.clear();
179       for (RegionData item : list) {
180         adapter.add(item.getDisplayName());
181       }
182       adapter.sort(Collator.getInstance(Locale.getDefault()));
183       if (defaultKey.length() == 0) {
184         view.setSelection(0);
185       } else {
186         int position = adapter.getPosition(defaultKey);
187         view.setSelection(position);
188       }
189     }
190
191     // Returns the region key of the currently selected region in the Spinner.
192     public String getRegionCode(int position) {
193       if (adapter.getCount() <= position) {
194         return "";
195       }
196       String value = adapter.getItem(position);
197       return getRegionDataKeyForValue(value);
198     }
199
200     // Returns the region key for the region value.
201     public String getRegionDataKeyForValue(String value) {
202       for (RegionData data : currentRegions) {
203         if (data.getDisplayName().endsWith(value)) {
204           return data.getKey();
205         }
206       }
207       return "";
208     }
209   }
210
211   private final ArrayList<AddressSpinnerInfo> spinners = new ArrayList<AddressSpinnerInfo>();
212
213   private AddressWidgetUiComponentProvider componentProvider;
214
215   /** TODO: Add region-dependent width types for address fields. */
216   private WidthType getFieldWidthType(AddressUiComponent field) {
217     return field.getId().getDefaulWidthType();
218   }
219
220   private void createView(ViewGroup rootView, AddressUiComponent field, String defaultKey,
221       boolean readOnly) {
222     @SuppressWarnings("deprecation")  // FILL_PARENT renamed MATCH_PARENT in API Level 8.
223         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
224             LayoutParams.WRAP_CONTENT);
225     String fieldText = field.getFieldName();
226     WidthType widthType = getFieldWidthType(field);
227
228     if (fieldText.length() > 0) {
229       TextView textView = componentProvider.createUiLabel(fieldText, widthType);
230       rootView.addView(textView, lp);
231     }
232     if (field.getUiType().equals(UiComponent.EDIT)) {
233       EditText editText = componentProvider.createUiTextField(widthType);
234       field.setView(editText);
235       editText.setEnabled(!readOnly);
236       rootView.addView(editText, lp);
237     } else if (field.getUiType().equals(UiComponent.SPINNER)) {
238       ArrayAdapter<String> adapter = componentProvider.createUiPickerAdapter(widthType);
239       Spinner spinner = componentProvider.createUiPickerSpinner(widthType);
240
241       field.setView(spinner);
242       rootView.addView(spinner, lp);
243       spinner.setAdapter(adapter);
244       AddressSpinnerInfo spinnerInfo =
245           new AddressSpinnerInfo(spinner, field.getId(), field.getParentId());
246       spinnerInfo.setSpinnerList(field.getCandidatesList(), defaultKey);
247
248       if (fieldText.length() > 0) {
249         spinner.setPrompt(fieldText);
250       }
251       spinner.setOnItemSelectedListener(this);
252       spinners.add(spinnerInfo);
253     }
254   }
255
256   /**
257    *  Associates each field with its corresponding AddressUiComponent.
258    */
259   private void buildFieldWidgets() {
260     AddressData data = new AddressData.Builder().setCountry(currentRegion).build();
261     LookupKey key = new LookupKey.Builder(LookupKey.KeyType.DATA).setAddressData(data).build();
262     AddressVerificationNodeData countryNode =
263         (new ClientData(cacheData)).getDefaultData(key.toString());
264
265     // Set up AddressField.ADMIN_AREA
266     AddressUiComponent adminAreaUi = new AddressUiComponent(AddressField.ADMIN_AREA);
267     adminAreaUi.setFieldName(getAdminAreaFieldName(countryNode));
268     inputWidgets.put(AddressField.ADMIN_AREA, adminAreaUi);
269
270     // Set up AddressField.LOCALITY
271     AddressUiComponent localityUi = new AddressUiComponent(AddressField.LOCALITY);
272     localityUi.setFieldName(getLocalityFieldName(countryNode));
273     inputWidgets.put(AddressField.LOCALITY, localityUi);
274
275     // Set up AddressField.DEPENDENT_LOCALITY
276     AddressUiComponent subLocalityUi = new AddressUiComponent(AddressField.DEPENDENT_LOCALITY);
277     subLocalityUi.setFieldName(getSublocalityFieldName(countryNode));
278     inputWidgets.put(AddressField.DEPENDENT_LOCALITY, subLocalityUi);
279
280     // Set up AddressField.ADDRESS_LINE_1
281     AddressUiComponent addressLine1Ui = new AddressUiComponent(AddressField.ADDRESS_LINE_1);
282     addressLine1Ui.setFieldName(context.getString(R.string.i18n_address_line1_label));
283     inputWidgets.put(AddressField.ADDRESS_LINE_1, addressLine1Ui);
284
285     // Set up AddressField.ADDRESS_LINE_2
286     AddressUiComponent addressLine2Ui = new AddressUiComponent(AddressField.ADDRESS_LINE_2);
287     addressLine2Ui.setFieldName("");
288     inputWidgets.put(AddressField.ADDRESS_LINE_2, addressLine2Ui);
289
290     // Set up AddressField.ORGANIZATION
291     AddressUiComponent organizationUi = new AddressUiComponent(AddressField.ORGANIZATION);
292     organizationUi.setFieldName(context.getString(R.string.i18n_organization_label));
293     inputWidgets.put(AddressField.ORGANIZATION, organizationUi);
294
295     // Set up AddressField.RECIPIENT
296     AddressUiComponent recipientUi = new AddressUiComponent(AddressField.RECIPIENT);
297     recipientUi.setFieldName(context.getString(R.string.i18n_recipient_label));
298     inputWidgets.put(AddressField.RECIPIENT, recipientUi);
299
300     // Set up AddressField.POSTAL_CODE
301     AddressUiComponent postalCodeUi = new AddressUiComponent(AddressField.POSTAL_CODE);
302     postalCodeUi.setFieldName(getZipFieldName(countryNode));
303     inputWidgets.put(AddressField.POSTAL_CODE, postalCodeUi);
304
305     // Set up AddressField.SORTING_CODE
306     AddressUiComponent sortingCodeUi = new AddressUiComponent(AddressField.SORTING_CODE);
307     sortingCodeUi.setFieldName("CEDEX");
308     inputWidgets.put(AddressField.SORTING_CODE, sortingCodeUi);
309   }
310
311   private void initializeDropDowns() {
312     AddressUiComponent adminAreaUi = inputWidgets.get(AddressField.ADMIN_AREA);
313     List<RegionData> adminAreaList = getRegionData(AddressField.COUNTRY);
314     adminAreaUi.initializeCandidatesList(adminAreaList);
315
316     AddressUiComponent localityUi = inputWidgets.get(AddressField.LOCALITY);
317     List<RegionData> localityList = getRegionData(AddressField.ADMIN_AREA);
318     localityUi.initializeCandidatesList(localityList);
319   }
320
321   // ZIP code is called postal code in some countries. This method returns the appropriate name
322   // for the given countryNode.
323   private String getZipFieldName(AddressVerificationNodeData countryNode) {
324     String zipName;
325     String zipType = countryNode.get(AddressDataKey.ZIP_NAME_TYPE);
326     if (zipType == null) {
327       zipLabel = ZipLabel.POSTAL;
328       zipName = context.getString(R.string.i18n_postal_code_label);
329     } else {
330       zipLabel = ZipLabel.ZIP;
331       zipName = context.getString(R.string.i18n_zip_code_label);
332     }
333     return zipName;
334   }
335
336   private String getLocalityFieldName(AddressVerificationNodeData countryNode) {
337     String localityLabelType = countryNode.get(AddressDataKey.LOCALITY_NAME_TYPE);
338     Integer result = LOCALITY_LABELS.get(localityLabelType);
339     if (result == null) {
340       // Fallback to city.
341       result = R.string.i18n_locality_label;
342     }
343     return context.getString(result);
344   }
345
346   private String getSublocalityFieldName(AddressVerificationNodeData countryNode) {
347     String sublocalityLabelType = countryNode.get(AddressDataKey.SUBLOCALITY_NAME_TYPE);
348     Integer result = SUBLOCALITY_LABELS.get(sublocalityLabelType);
349     if (result == null) {
350       // Fallback to suburb.
351       result = R.string.i18n_suburb;
352     }
353     return context.getString(result);
354   }
355
356   private String getAdminAreaFieldName(AddressVerificationNodeData countryNode) {
357     String adminLabelType = countryNode.get(AddressDataKey.STATE_NAME_TYPE);
358     Integer result = ADMIN_LABELS.get(adminLabelType);
359     if (result == null) {
360       // Fallback to province.
361       result = R.string.i18n_province;
362     }
363     return context.getString(result);
364   }
365
366   private void buildCountryListBox() {
367     // Set up AddressField.COUNTRY
368     AddressUiComponent countryUi = new AddressUiComponent(AddressField.COUNTRY);
369     countryUi.setFieldName(context.getString(R.string.i18n_country_or_region_label));
370     ArrayList<RegionData> countries = new ArrayList<RegionData>();
371     for (RegionData regionData : formController.getRegionData(new LookupKey.Builder(
372         KeyType.DATA).build())) {
373       String regionKey = regionData.getKey();
374       // ZZ represents an unknown region code.
375       if (!regionKey.equals("ZZ")) {
376         String localCountryName = getLocalCountryName(regionKey);
377         RegionData country = new RegionData.Builder().setKey(regionKey).setName(
378             localCountryName).build();
379         countries.add(country);
380       }
381     }
382     countryUi.initializeCandidatesList(countries);
383     inputWidgets.put(AddressField.COUNTRY, countryUi);
384   }
385
386   private String getLocalCountryName(String regionCode) {
387     return (new Locale("", regionCode)).getDisplayCountry(Locale.getDefault());
388   }
389
390   private AddressSpinnerInfo findSpinnerByView(View view) {
391     for (AddressSpinnerInfo spinnerInfo : spinners) {
392       if (spinnerInfo.view == view) {
393         return spinnerInfo;
394       }
395     }
396     return null;
397   }
398
399   private void updateFields() {
400     removePreviousViews();
401     buildFieldWidgets();
402     initializeDropDowns();
403     layoutAddressFields();
404   }
405
406   private void removePreviousViews() {
407     if (rootView == null) {
408       return;
409     }
410     int childCount = rootView.getChildCount();
411     if (formOptions.isHidden(AddressField.COUNTRY)) {
412       if (childCount > 0) {
413         rootView.removeAllViews();
414       }
415     } else if (childCount > 2) {
416       // Keep the TextView and Spinner for Country and remove everything else.
417       rootView.removeViews(2, rootView.getChildCount() - 2);
418     }
419   }
420
421   private void layoutAddressFields() {
422     for (AddressField field : formatInterpreter.getAddressFieldOrder(script,
423           currentRegion)) {
424       if (!formOptions.isHidden(field)) {
425         createView(rootView, inputWidgets.get(field), "", formOptions.isReadonly(field));
426       }
427     }
428   }
429
430   private void updateChildNodes(AdapterView<?> parent, int position) {
431     AddressSpinnerInfo spinnerInfo = findSpinnerByView(parent);
432     if (spinnerInfo == null) {
433       return;
434     }
435
436     // Find all the child spinners, if any, that depend on this one.
437     final AddressField myId = spinnerInfo.id;
438     if (myId != AddressField.COUNTRY && myId != AddressField.ADMIN_AREA
439         && myId != AddressField.LOCALITY) {
440       // Only a change in the three AddressFields above will trigger a change in other
441       // AddressFields. Therefore, for all other AddressFields, we return immediately.
442       return;
443     }
444
445     String regionCode = spinnerInfo.getRegionCode(position);
446     if (myId == AddressField.COUNTRY) {
447       updateWidgetOnCountryChange(regionCode);
448       return;
449     }
450
451     formController.requestDataForAddress(getAddressData(), new DataLoadListener() {
452       @Override
453       public void dataLoadingBegin(){
454       }
455
456       @Override
457       public void dataLoadingEnd() {
458         Runnable updateChild = new UpdateRunnable(myId);
459         handler.post(updateChild);
460       }
461     });
462   }
463
464   public void updateWidgetOnCountryChange(String regionCode) {
465     if (currentRegion.equalsIgnoreCase(regionCode)) {
466       return;
467     }
468     currentRegion = regionCode;
469     formController.setCurrentCountry(currentRegion);
470     renderForm();
471   }
472
473   private void updateInputWidget(AddressField myId) {
474     for (AddressSpinnerInfo child : spinners) {
475       if (child.parentId == myId) {
476         List<RegionData> candidates = getRegionData(child.parentId);
477         child.setSpinnerList(candidates, "");
478       }
479     }
480   }
481
482   public void renderForm() {
483     setWidgetLocaleAndScript();
484     AddressData data = new AddressData.Builder().setCountry(currentRegion)
485         .setLanguageCode(widgetLocale).build();
486     formController.requestDataForAddress(data, new DataLoadListener() {
487       @Override
488       public void dataLoadingBegin() {
489         progressDialog = componentProvider.getUiActivityIndicatorView();
490         progressDialog.setMessage(context.getString(R.string.address_data_loading));
491         Log.d(this.toString(), "Progress dialog started.");
492       }
493       @Override
494       public void dataLoadingEnd() {
495         Log.d(this.toString(), "Data loading completed.");
496         progressDialog.dismiss();
497         Log.d(this.toString(), "Progress dialog stopped.");
498         handler.post(updateMultipleFields);
499       }
500     });
501   }
502
503   private void setWidgetLocaleAndScript() {
504     widgetLocale = Util.getWidgetCompatibleLanguageCode(Locale.getDefault(), currentRegion);
505     formController.setLanguageCode(widgetLocale);
506     script = Util.isExplicitLatinScript(widgetLocale)
507         ? ScriptType.LATIN
508         : ScriptType.LOCAL;
509   }
510
511   private List<RegionData> getRegionData(AddressField parentField) {
512     AddressData address = getAddressData();
513
514     // Removes language code from address if it is default. This address is used to build
515     // lookup key, which neglects default language. For example, instead of "data/US--en/CA",
516     // the right lookup key is "data/US/CA".
517     if (formController.isDefaultLanguage(address.getLanguageCode())) {
518       address = new AddressData.Builder(address).setLanguageCode(null).build();
519     }
520
521     LookupKey parentKey = formController.getDataKeyFor(address).getKeyForUpperLevelField(
522         parentField);
523     List<RegionData> candidates;
524     // Can't build a key with parent field, quit.
525     if (parentKey == null) {
526       Log.w(this.toString(), "Can't build key with parent field " + parentField + ". One of"
527           + " the ancestor fields might be empty");
528
529       // Removes candidates that exist from previous settings. For example, data/US has a
530       // list of candidates AB, BC, CA, etc, that list should be cleaned up when user updates
531       // the address by changing country to Channel Islands.
532       candidates = new ArrayList<RegionData>(1);
533     } else {
534       candidates = formController.getRegionData(parentKey);
535     }
536     return candidates;
537   }
538
539   /**
540    * Creates an AddressWidget to be attached to rootView for the specific context using the
541    * default UI component provider.
542    */
543   public AddressWidget(Context context, ViewGroup rootView, FormOptions formOptions,
544       ClientCacheManager cacheManager) {
545     this(context, rootView, formOptions, cacheManager,
546         new AddressWidgetUiComponentProvider(context));
547   }
548
549   /**
550    * Creates an AddressWidget to be attached to rootView for the specific context using UI
551    * component provided by the provider.
552    */
553   public AddressWidget(Context context, ViewGroup rootView, FormOptions formOptions,
554       ClientCacheManager cacheManager, AddressWidgetUiComponentProvider provider) {
555     componentProvider = provider;
556     currentRegion =
557         ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
558         .getSimCountryIso().toUpperCase(Locale.US);
559     if (currentRegion.length() == 0) {
560       currentRegion = "US";
561     }
562     init(context, rootView, formOptions, cacheManager);
563     renderForm();
564   }
565
566   /**
567    * Creates an AddressWidget to be attached to rootView for the specific context using the
568    * default UI component provider, and fill out the address form with savedAddress.
569    */
570   public AddressWidget(Context context, ViewGroup rootView, FormOptions formOptions,
571       ClientCacheManager cacheManager, AddressData savedAddress) {
572     this(context, rootView, formOptions, cacheManager, savedAddress,
573         new AddressWidgetUiComponentProvider(context));
574   }
575
576   /**
577    * Creates an AddressWidget to be attached to rootView for the specific context using UI
578    * component provided by the provider, and fill out the address form with savedAddress.
579    */
580   public AddressWidget(Context context, ViewGroup rootView, FormOptions formOptions,
581       ClientCacheManager cacheManager, AddressData savedAddress,
582       AddressWidgetUiComponentProvider provider) {
583     componentProvider = provider;
584     currentRegion = savedAddress.getPostalCountry();
585     // Postal country must be 2 letter country code. Otherwise default to US.
586     if (currentRegion == null || currentRegion.length() != 2) {
587       currentRegion = "US";
588     }
589     init(context, rootView, formOptions, cacheManager);
590     renderFormWithSavedAddress(savedAddress);
591   }
592
593   public void renderFormWithSavedAddress(AddressData savedAddress) {
594     setWidgetLocaleAndScript();
595     removePreviousViews();
596     buildFieldWidgets();
597     layoutAddressFields();
598     initializeFieldsWithAddress(savedAddress);
599   }
600
601   private void initializeFieldsWithAddress(AddressData savedAddress) {
602     for (AddressField field : formatInterpreter.getAddressFieldOrder(script, currentRegion)) {
603       String value = savedAddress.getFieldValue(field);
604       if (value == null) {
605         value = "";
606       }
607       AddressUiComponent uiComponent = inputWidgets.get(field);
608       EditText view = (EditText) uiComponent.getView();
609       if (view != null) {
610         view.setText(value);
611       }
612     }
613   }
614
615   private void init(Context context, ViewGroup rootView, FormOptions formOptions,
616       ClientCacheManager cacheManager) {
617     this.context = context;
618     this.rootView = rootView;
619     this.formOptions = formOptions;
620     this.cacheData = new CacheData(cacheManager);
621     this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
622     this.formController =
623         new FormController(new ClientData(cacheData), widgetLocale, currentRegion);
624     this.formatInterpreter = new FormatInterpreter(formOptions);
625     this.verifier = new StandardAddressVerifier(new FieldVerifier(new ClientData(cacheData)));
626     if (!formOptions.isHidden(AddressField.COUNTRY)) {
627       buildCountryListBox();
628       createView(rootView, inputWidgets.get(AddressField.COUNTRY),
629           getLocalCountryName(currentRegion),
630           formOptions.isReadonly(AddressField.COUNTRY));
631     }
632   }
633
634   /**
635    * Sets address data server URL. Input URL cannot be null.
636    *
637    * @param url The service URL.
638    */
639   public void setUrl(String url) {
640     cacheData.setUrl(url);
641   }
642
643   /**
644    * Gets user input address in AddressData format.
645    */
646   public AddressData getAddressData() {
647     AddressData.Builder builder = new AddressData.Builder();
648     builder.setCountry(currentRegion);
649     for (AddressField field : formatInterpreter.getAddressFieldOrder(script,
650           currentRegion)) {
651       AddressUiComponent addressUiComponent = inputWidgets.get(field);
652       if (addressUiComponent != null) {
653         String value = addressUiComponent.getValue();
654         if (addressUiComponent.getUiType() == UiComponent.SPINNER) {
655           // For drop-downs, return the key of the region selected instead of the value.
656           View view = getViewForField(field);
657           AddressSpinnerInfo spinnerInfo = findSpinnerByView(view);
658           if (spinnerInfo != null) {
659             value = spinnerInfo.getRegionDataKeyForValue(value);
660           }
661         }
662         builder.set(field, value);
663       }
664     }
665     builder.setLanguageCode(widgetLocale);
666     return builder.build();
667   }
668
669   /**
670    * Gets the formatted address.
671    *
672    * This method does not validate addresses. Also, it will "normalize" the result strings by
673    * removing redundant spaces and empty lines.
674    *
675    * @return the formatted address
676    */
677   public List<String> getEnvelopeAddress() {
678     return formatInterpreter.getEnvelopeAddress(getAddressData());
679   }
680
681   /**
682    * Gets the formatted address based on the AddressData passed in.
683    */
684   public List<String> getEnvelopeAddress(AddressData address) {
685     return formatInterpreter.getEnvelopeAddress(address);
686   }
687
688   /**
689    * Gets the formatted address based on the AddressData passed in with none of the relevant
690    * fields hidden.
691    */
692   public static List<String> getFullEnvelopeAddress(AddressData address) {
693     return new FormatInterpreter(SHOW_ALL_FIELDS).getEnvelopeAddress(address);
694   }
695
696   /**
697    * Get problems found in the address data entered by the user.
698    */
699   public AddressProblems getAddressProblems() {
700     AddressProblems problems = new AddressProblems();
701     AddressData addressData = getAddressData();
702     verifier.verify(addressData, problems);
703     return problems;
704   }
705
706   /**
707    * Displays an appropriate error message for an AddressField with a problem.
708    *
709    * @return the View object representing the AddressField.
710    */
711   public View displayErrorMessageForField(AddressData address,
712       AddressField field, AddressProblemType problem) {
713     Log.d(this.toString(), "Display error message for the field: " + field.toString());
714     AddressUiComponent addressUiComponent = inputWidgets.get(field);
715     if (addressUiComponent != null && addressUiComponent.getUiType() == UiComponent.EDIT) {
716       EditText view = (EditText) addressUiComponent.getView();
717       view.setError(getErrorMessageForInvalidEntry(address, field, problem));
718       return view;
719     }
720     return null;
721   }
722
723   private String getErrorMessageForInvalidEntry(AddressData address, AddressField field,
724       AddressProblemType problem) {
725     switch (problem) {
726       case MISSING_REQUIRED_FIELD:
727         return context.getString(R.string.i18n_missing_required_field);
728       case UNKNOWN_VALUE:
729         String currentValue = address.getFieldValue(field);
730         return String.format(context.getString(R.string.unknown_entry), currentValue);
731       case UNRECOGNIZED_FORMAT:
732         // We only support this error type for the Postal Code field.
733         return (zipLabel == ZipLabel.POSTAL
734             ? context.getString(R.string.unrecognized_format_postal_code)
735             : context.getString(R.string.unrecognized_format_zip_code));
736       case MISMATCHING_VALUE:
737         // We only support this error type for the Postal Code field.
738         return (zipLabel == ZipLabel.POSTAL
739             ? context.getString(R.string.mismatching_value_postal_code)
740             : context.getString(R.string.mismatching_value_zip_code));
741     }
742     return "";
743   }
744
745   /**
746    * Clears all error messages in the UI.
747    */
748   public void clearErrorMessage() {
749     for (AddressField field : formatInterpreter.getAddressFieldOrder(script,
750           currentRegion)) {
751       AddressUiComponent addressUiComponent = inputWidgets.get(field);
752
753       if (addressUiComponent != null && addressUiComponent.getUiType() == UiComponent.EDIT) {
754         EditText view = (EditText) addressUiComponent.getView();
755         if (view != null) {
756           view.setError(null);
757         }
758       }
759     }
760   }
761
762   public View getViewForField(AddressField field) {
763     AddressUiComponent component = inputWidgets.get(field);
764     if (component == null) {
765       return null;
766     }
767     return component.getView();
768   }
769
770   @Override
771   public void onNothingSelected(AdapterView<?> arg0) {
772   }
773
774   @Override
775   public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
776     updateChildNodes(parent, position);
777   }
778 }