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