Commits

Anonymous committed af9df90

Update library to v2.2

Comments (0)

Files changed (6)

java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml

     <!-- Lithuania -->
     <!-- http://www.itu.int/oth/T020200007C/en -->
     <territory id="LT" countryCode="370" internationalPrefix="00"
-               nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG">
+               nationalPrefix="8" nationalPrefixFormattingRule="$NP $FG">
       <availableFormats>
         <!-- Two-digit area codes -->
         <numberFormat leadingDigits="37|4(?:1|5[45]|6[2-4])"

java/resources/com/google/i18n/phonenumbers/src/generated_files/PhoneNumberMetadataProto

Binary file modified.

java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java

 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
 import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
 import com.google.protobuf.MessageLite;
 
 import java.io.ByteArrayOutputStream;
   }
 
   /**
+   * Gets the length of the geographical area code from the national_number field of the PhoneNumber
+   * object passed in, so that clients could use it to split a national significant number into
+   * geographical area code and subscriber number. It works in such a way that the resultant
+   * subscriber number should be diallable, at least on some devices. An example of how this could
+   * be used:
+   *
+   * PhoneNumberUtil phoneUtil.PhoneNumberUtil.getInstance();
+   * PhoneNumber number = phoneUtil.parse("16502530000", RegionCode.US);
+   * String nationalSignificantNumber = PhoneNumberUtil.getNationalSignificantNumber(number);
+   * String areaCode;
+   * String subscriberNumber;
+   *
+   * int areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
+   * if (areaCodeLength > 0) {
+   *   areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
+   *   subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
+   * } else {
+   *   areaCode = "";
+   *   subscriberNumber = nationalSignificantNumber;
+   * }
+   *
+   * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
+   * using it for most purposes, but recommends using the more general national_number instead. Read
+   * the following carefully before deciding to use this method:
+   *
+   *  - geographical area codes change over time, and this method honors those changes; therefore,
+   *    it doesn't guarantee the stability of the result it produces.
+   *  - subscriber numbers may not be diallable from all devices (notably mobile devices, which
+   *    typically requires the full national_number to be dialled in most countries).
+   *  - most non-geographical numbers have no area codes.
+   *  - some geographical numbers have no area codes.
+   *
+   * @param number  the PhoneNumber object for which clients want to know the length of the area
+   *     code in the national_number field.
+   * @return  the length of area code of the PhoneNumber object passed in.
+   */
+  public int getLengthOfGeographicalAreaCode(PhoneNumber number) {
+    String regionCode = getRegionCodeForNumber(number);
+    if (regionCode == null || regionCode.equalsIgnoreCase("ZZ")) {
+      return 0;
+    }
+    PhoneMetadata metadata = getMetadataForRegion(regionCode);
+    // For NANPA countries, national prefix is the same as country code, but it is not stored in
+    // the metadata.
+    if (!metadata.hasNationalPrefix() && !isNANPACountry(regionCode)) {
+      return 0;
+    }
+
+    PhoneNumberType type = getNumberTypeHelper(String.valueOf(number.getNationalNumber()),
+                                               metadata);
+    // Most numbers other than the two types below have to be dialled in full.
+    if (type != PhoneNumberType.FIXED_LINE && type != PhoneNumberType.FIXED_LINE_OR_MOBILE) {
+      return 0;
+    }
+
+    PhoneNumber copiedProto;
+    if (number.hasExtension()) {
+      // We don't want to alter the proto given to us, but we don't want to include the extension
+      // when we format it, so we copy it and clear the extension here.
+      PhoneNumber.Builder protoBuilder = PhoneNumber.newBuilder();
+      protoBuilder.mergeFrom(number);
+      protoBuilder.clearExtension();
+      copiedProto = protoBuilder.build();
+    } else {
+      copiedProto = number;
+    }
+
+    String nationalSignificantNumber = format(copiedProto,
+                                              PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
+    Pattern nonDigitPattern = Pattern.compile("(\\D+)");
+    String[] numberGroups = nonDigitPattern.split(nationalSignificantNumber);
+    // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
+    // string (before the + symbol) and the second group will be the country code. The third group
+    // will be area code if it is not the last group.
+    if (numberGroups.length <= 3) {
+      return 0;
+    }
+    // Note all countries that use leading zero in national number don't use national prefix, so
+    // they won't have an area code, which means clients don't need to worry about appending the
+    // leading zero to the geographical area code they derive from the length we return here.
+    return numberGroups[2].length();
+  }  
+
+  /**
    * Normalizes a string of characters representing a phone number by replacing all characters found
    * in the accompanying map with the values therein, and stripping all other characters if
    * removeNonMatches is true.
    * @param removeNonMatches           indicates whether characters that are not able to be replaced
    *                                   should be stripped from the number. If this is false, they
    *                                   will be left unchanged in the number.
-   * @return the normalized string version of the phone number
+   * @return  the normalized string version of the phone number
    */
   private static String normalizeHelper(String number,
                                         Map<Character, Character> normalizationReplacements,
    *
    * @param number         the phone number to be formatted
    * @param numberFormat   the format the phone number should be formatted into
-   * @return the formatted phone number
+   * @return  the formatted phone number
    */
   public String format(PhoneNumber number, PhoneNumberFormat numberFormat) {
     int countryCode = number.getCountryCode();
-    String nationalSignificantNumber = getUnformattedNationalNumber(number);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
     if (numberFormat == PhoneNumberFormat.E164) {
       // Early exit for E164 case since no formatting of the national number needs to be applied.
       // Extensions are not formatted.
    * @param number                        the phone number to be formatted
    * @param numberFormat                  the format the phone number should be formatted into
    * @param userDefinedFormats            formatting rules specified by clients
-   * @return the formatted phone number
+   * @return  the formatted phone number
    */
   public String formatByPattern(PhoneNumber number,
                                 PhoneNumberFormat numberFormat,
     // share a country code is contained by only one country for performance reasons. For example,
     // for NANPA countries it will be contained in the metadata for US.
     String regionCode = getRegionCodeForCountryCode(countryCode);
-    String nationalSignificantNumber = getUnformattedNationalNumber(number);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
     if (!isValidRegionCode(regionCode, countryCode, nationalSignificantNumber)) {
       return nationalSignificantNumber;
     }
    * @param number               the phone number to be formatted
    * @param countryCallingFrom   the ISO 3166-1 two-letter country code that denotes the foreign
    *                             country where the call is being placed
-   * @return the formatted phone number
+   * @return  the formatted phone number
    */
   public String formatOutOfCountryCallingNumber(PhoneNumber number,
                                                 String countryCallingFrom) {
     } else {
       regionCode = getRegionCodeForCountryCode(countryCode);
     }
-    String nationalSignificantNumber = getUnformattedNationalNumber(number);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
     if (!isValidRegionCode(regionCode, countryCode, nationalSignificantNumber)) {
       return nationalSignificantNumber;
     }
                                formattedExtension);
   }
 
-  static String getUnformattedNationalNumber(PhoneNumber number) {
+  
+  /**
+   * Gets the national significant number of the a phone number. Note a national significant number
+   * doesn't contain a national prefix or any formatting.
+   *
+   * @param number  the PhoneNumber object for which the national significant number is needed
+   * @return  the national significant number of the PhoneNumber object passed in
+   */
+  public static String getNationalSignificantNumber(PhoneNumber number) {
     // The leading zero in the national (significant) number of an Italian phone number has a
     // special meaning. Unlike the rest of the world, it indicates the number is a landline
     // number. There have been plans to migrate landline numbers to start with the digit two since
    */
   public PhoneNumberType getNumberType(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
-    String nationalSignificantNumber = getUnformattedNationalNumber(number);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
     if (!isValidRegionCode(regionCode, number.getCountryCode(), nationalSignificantNumber)) {
       return PhoneNumberType.UNKNOWN;
     }
   public boolean isValidNumber(PhoneNumber number) {
     String regionCode = getRegionCodeForNumber(number);
     return isValidRegionCode(regionCode, number.getCountryCode(),
-                             getUnformattedNationalNumber(number))
+                             getNationalSignificantNumber(number))
            && isValidNumberForRegion(number, regionCode);
   }
 
     }
     PhoneMetadata metadata = getMetadataForRegion(regionCode);
     PhoneNumberDesc generalNumDesc = metadata.getGeneralDesc();
-    String nationalSignificantNumber = getUnformattedNationalNumber(number);
+    String nationalSignificantNumber = getNationalSignificantNumber(number);
 
     // For countries where we don't have metadata for PhoneNumberDesc, we treat any number passed
     // in as a valid number if its national significant number is between the minimum and maximum
       case NANPA_COUNTRY_CODE:
         // Override this and try the US case first, since it is more likely than other countries,
         // for performance reasons.
-        if (isValidNumberForRegion(number, "US")) {
+        String nationalNumber = getNationalSignificantNumber(number);
+        if (getNumberTypeHelper(nationalNumber,
+                                getMetadataForRegion("US")) != PhoneNumberType.UNKNOWN) {
           return "US";
         }
         Set<String> nanpaExceptUS = new HashSet<String>(nanpaCountries);
    * @return  a ValidationResult object which indicates whether the number is possible
    */
   public ValidationResult isPossibleNumberWithReason(PhoneNumber number) {
-    String nationalNumber = getUnformattedNationalNumber(number);
+    String nationalNumber = getNationalSignificantNumber(number);
     int countryCode = number.getCountryCode();
     // Note: For Russian Fed and NANPA numbers, we just use the rules from the default region (US or
     // Russia) since the getRegionCodeForNumber will not work if the number is possible but not
    * @param nationalNumber  a string buffer to store the national significant number in, in the case
    *     that a country code was extracted. The number is appended to any existing contents. If no
    *     country code was extracted, this will be left unchanged.
+   * @param storeCountryCodeSource  true if the country_code_source field of phoneNumber should be
+   *     populated.
+   * @param phoneNumber  the PhoneNumber.Builder object that needs to be populated with country code
+   *     and country code source. Note the country code is always populated, whereas country code
+   *     source is only populated when keepCountryCodeSource is true.
    * @return  the country code extracted or 0 if none could be extracted
    */
   @VisibleForTesting
   int maybeExtractCountryCode(String number, PhoneMetadata defaultRegionMetadata,
-                              StringBuffer nationalNumber)
+                              StringBuffer nationalNumber, boolean storeCountryCodeSource,
+                              PhoneNumber.Builder phoneNumber)
       throws NumberParseException {
+    if (number.length() == 0) {
+      return 0;
+    }    
     StringBuffer fullNumber = new StringBuffer(number);
     // Set the default prefix to be something that will never match.
     String possibleCountryIddPrefix = "NonMatch";
     if (defaultRegionMetadata != null) {
       possibleCountryIddPrefix = defaultRegionMetadata.getInternationalPrefix();
     }
-    if (maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix)) {
+    
+    CountryCodeSource countryCodeSource =
+        maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix);
+    if (storeCountryCodeSource) {
+      phoneNumber.setCountryCodeSource(countryCodeSource);
+    }
+    if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
       if (fullNumber.length() < MIN_LENGTH_FOR_NSN) {
         throw new NumberParseException(NumberParseException.ErrorType.TOO_SHORT_AFTER_IDD,
                                        "Phone number had an IDD, but after this was not "
       }
       int potentialCountryCode = extractCountryCode(fullNumber, nationalNumber);
       if (potentialCountryCode != 0) {
+        phoneNumber.setCountryCode(potentialCountryCode);
         return potentialCountryCode;
       }
 
               validNumberPattern);
           if (validNumberPattern.matcher(potentialNationalNumber).matches()) {
             nationalNumber.append(potentialNationalNumber);
+            if (storeCountryCodeSource) {
+              phoneNumber.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN);
+            }
+            phoneNumber.setCountryCode(defaultCountryCode);
             return defaultCountryCode;
           }
         }
       }
     }
     // No country code present.
+    phoneNumber.setCountryCode(0);
     return 0;
   }
 
    * Strips the IDD from the start of the number if present. Helper function used by
    * maybeStripInternationalPrefixAndNormalize.
    */
-  private boolean parsePrefixAsIdd(Pattern iddPattern,
-                                   StringBuffer number) {
+  private boolean parsePrefixAsIdd(Pattern iddPattern, StringBuffer number) {
     Matcher m = iddPattern.matcher(number);
     if (m.lookingAt()) {
       int matchEnd = m.end();
    *     dialing prefix from
    * @param possibleIddPrefix  the international direct dialing prefix from the country we
    *     think this number may be dialed in
-   * @return  true if an international dialing prefix could be removed from the number, otherwise
-   *     false if the number did not seem to be in international format
+   * @return  the corresponding CountryCodeSource if an international dialing prefix could be
+   *     removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number
+   *     did not seem to be in international format.
    */
   @VisibleForTesting
-  boolean maybeStripInternationalPrefixAndNormalize(StringBuffer number, String possibleIddPrefix) {
+  CountryCodeSource maybeStripInternationalPrefixAndNormalize(
+      StringBuffer number,
+      String possibleIddPrefix) {
     if (number.length() == 0) {
-      return false;
+      return CountryCodeSource.FROM_DEFAULT_COUNTRY;
     }
     if (number.charAt(0) == PLUS_SIGN) {
       number.deleteCharAt(0);
       // Can now normalize the rest of the number since we've consumed the "+" sign at the start.
       normalize(number);
-      return true;
+      return CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN;
     }
     // Attempt to parse the first digits as an international prefix.
     Pattern iddPattern = Pattern.compile(possibleIddPrefix);
     if (parsePrefixAsIdd(iddPattern, number)) {
       normalize(number);
-      return true;
+      return CountryCodeSource.FROM_NUMBER_WITH_IDD;
     }
     // If still not found, then try and normalize the number and then try again. This shouldn't be
     // done before, since non-numeric characters (+ and ~) may legally be in the international
     // prefix.
     normalize(number);
-    return parsePrefixAsIdd(iddPattern, number);
+    return parsePrefixAsIdd(iddPattern, number)
+        ? CountryCodeSource.FROM_NUMBER_WITH_IDD
+        : CountryCodeSource.FROM_DEFAULT_COUNTRY;
   }
 
   /**
 
   /**
    * Parses a string and returns it in proto buffer format. This method differs from parse() in that
-   * it always populates the raw_input field of the protocol buffer with numberToParse.
+   * it always populates the raw_input field of the protocol buffer with numberToParse as well as
+   * the country_code_source field.
    *
    * @param numberToParse     number that we are attempting to parse. This can contain formatting
    *                          such as +, ( and -, as well as a phone number extension.
     // been created, and just remove the prefix, rather than taking in a string and then outputting
     // a string buffer.
     int countryCode = maybeExtractCountryCode(nationalNumber.toString(), countryMetadata,
-                                              normalizedNationalNumber);
+                                              normalizedNationalNumber, keepRawInput, phoneNumber);
     if (countryCode != 0) {
       String phoneNumberRegion = getRegionCodeForCountryCode(countryCode);
-      countryMetadata = getMetadataForRegion(phoneNumberRegion);
+      if (!phoneNumberRegion.equals(defaultCountry)) {
+        countryMetadata = getMetadataForRegion(phoneNumberRegion);
+      }
     } else {
       // If no extracted country code, use the region supplied instead. The national number is just
       // the normalized version of the number we were given to parse.
       normalizedNationalNumber.append(nationalNumber);
       if (defaultCountry != null) {
         countryCode = countryMetadata.getCountryCode();
+        phoneNumber.setCountryCode(countryCode);
+      } else if (keepRawInput) {
+        phoneNumber.clearCountryCodeSource();
       }
     }
     if (normalizedNationalNumber.length() < MIN_LENGTH_FOR_NSN) {
                                      "The string supplied is too long to be a "
                                      + "phone number.");
     }
-    phoneNumber.setCountryCode(countryCode);
     if (isLeadingZeroCountry(countryCode) &&
         normalizedNationalNumber.charAt(0) == '0') {
       phoneNumber.setItalianLeadingZero(true);
     firstNumber.mergeFrom(firstNumberIn);
     PhoneNumber.Builder secondNumber = PhoneNumber.newBuilder();
     secondNumber.mergeFrom(secondNumberIn);
-    // First clear raw_input field and any empty-string extensions so that we can use the
-    // proto-buffer equality method.
+    // First clear raw_input and country_code_source field and any empty-string extensions so that
+    // we can use the proto-buffer equality method.
     firstNumber.clearRawInput();
+    firstNumber.clearCountryCodeSource();
     secondNumber.clearRawInput();
+    secondNumber.clearCountryCodeSource();
     if (firstNumber.hasExtension() &&
         firstNumber.getExtension().equals("")) {
         firstNumber.clearExtension();

java/src/com/google/i18n/phonenumbers/Phonenumber.java

       return defaultInstance;
     }
     
+    public enum CountryCodeSource
+        implements com.google.protobuf.Internal.EnumLite {
+      FROM_NUMBER_WITH_PLUS_SIGN(0, 1),
+      FROM_NUMBER_WITH_IDD(1, 5),
+      FROM_NUMBER_WITHOUT_PLUS_SIGN(2, 10),
+      FROM_DEFAULT_COUNTRY(3, 20),
+      ;
+      
+      
+      public final int getNumber() { return value; }
+      
+      public static CountryCodeSource valueOf(int value) {
+        switch (value) {
+          case 1: return FROM_NUMBER_WITH_PLUS_SIGN;
+          case 5: return FROM_NUMBER_WITH_IDD;
+          case 10: return FROM_NUMBER_WITHOUT_PLUS_SIGN;
+          case 20: return FROM_DEFAULT_COUNTRY;
+          default: return null;
+        }
+      }
+      
+      public static com.google.protobuf.Internal.EnumLiteMap<CountryCodeSource>
+          internalGetValueMap() {
+        return internalValueMap;
+      }
+      private static com.google.protobuf.Internal.EnumLiteMap<CountryCodeSource>
+          internalValueMap =
+            new com.google.protobuf.Internal.EnumLiteMap<CountryCodeSource>() {
+              public CountryCodeSource findValueByNumber(int number) {
+                return CountryCodeSource.valueOf(number)
+      ;        }
+            };
+      
+      private final int index;
+      private final int value;
+      private CountryCodeSource(int index, int value) {
+        this.index = index;
+        this.value = value;
+      }
+      
+      // @@protoc_insertion_point(enum_scope:i18n.phonenumbers.PhoneNumber.CountryCodeSource)
+    }
+    
     // required int32 country_code = 1;
     public static final int COUNTRY_CODE_FIELD_NUMBER = 1;
     private boolean hasCountryCode;
     private java.lang.String rawInput_ = "";
     public boolean hasRawInput() { return hasRawInput; }
     public java.lang.String getRawInput() { return rawInput_; }
-
+    
+    // optional .i18n.phonenumbers.PhoneNumber.CountryCodeSource country_code_source = 6;
+    public static final int COUNTRY_CODE_SOURCE_FIELD_NUMBER = 6;
+    private boolean hasCountryCodeSource;
+    private com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource countryCodeSource_;
+    public boolean hasCountryCodeSource() { return hasCountryCodeSource; }
+    public com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource getCountryCodeSource() { return countryCodeSource_; }
+    
     private void initFields() {
+      countryCodeSource_ = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN;
     }
     public final boolean isInitialized() {
       if (!hasCountryCode) return false;
       if (hasRawInput()) {
         output.writeString(5, getRawInput());
       }
+      if (hasCountryCodeSource()) {
+        output.writeEnum(6, getCountryCodeSource().getNumber());
+      }
     }
     
     private int memoizedSerializedSize = -1;
         size += com.google.protobuf.CodedOutputStream
           .computeStringSize(5, getRawInput());
       }
+      if (hasCountryCodeSource()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(6, getCountryCodeSource().getNumber());
+      }
       memoizedSerializedSize = size;
       return size;
     }
         if (other.hasRawInput()) {
           setRawInput(other.getRawInput());
         }
+        if (other.hasCountryCodeSource()) {
+          setCountryCodeSource(other.getCountryCodeSource());
+        }
         return this;
       }
       
               setRawInput(input.readString());
               break;
             }
+            case 48: {
+              int rawValue = input.readEnum();
+              com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource value = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.valueOf(rawValue);
+              if (value != null) {
+                setCountryCodeSource(value);
+              }
+              break;
+            }
           }
         }
       }
         result.rawInput_ = getDefaultInstance().getRawInput();
         return this;
       }
-
+      
+      // optional .i18n.phonenumbers.PhoneNumber.CountryCodeSource country_code_source = 6;
+      public boolean hasCountryCodeSource() {
+        return result.hasCountryCodeSource();
+      }
+      public com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource getCountryCodeSource() {
+        return result.getCountryCodeSource();
+      }
+      public Builder setCountryCodeSource(com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result.hasCountryCodeSource = true;
+        result.countryCodeSource_ = value;
+        return this;
+      }
+      public Builder clearCountryCodeSource() {
+        result.hasCountryCodeSource = false;
+        result.countryCodeSource_ = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN;
+        return this;
+      }
+      
       // @@protoc_insertion_point(builder_scope:i18n.phonenumbers.PhoneNumber)
     }
     

java/src/com/google/i18n/phonenumbers/phonenumber.proto

 // canonicalized by the library. For example, it could be used to store alphanumerical numbers
 // such as "1-800-GOOG-411".
   optional string raw_input = 5;
+
+// The source from which the country_code is derived. This is not set in the general parsing method,
+// but in the method that parses and keeps raw_input. New fields could be added upon request.
+  enum CountryCodeSource {
+    // The country_code is derived based on a phone number with a leading "+", e.g. the French
+    // number "+33 (0)1 42 68 53 00".
+    FROM_NUMBER_WITH_PLUS_SIGN = 1;
+
+    // The country_code is derived based on a phone number with a leading IDD, e.g. the French
+    // number "011 33 (0)1 42 68 53 00", as it is dialled from US.
+    FROM_NUMBER_WITH_IDD = 5;
+
+    // The country_code is derived based on a phone number without a leading "+", e.g. the French
+    // number "33 (0)1 42 68 53 00" when defaultCountry is supplied as France.
+    FROM_NUMBER_WITHOUT_PLUS_SIGN = 10;
+
+    // The country_code is derived NOT based on the phone number itself, but from the defaultCountry
+    // parameter provided in the parsing function by the clients. This happens mostly for numbers
+    // written in the national format (without country code). For example, this would be set when
+    // parsing the French number "(0)1 42 68 53 00", when defaultCountry is supplied as France.
+    FROM_DEFAULT_COUNTRY = 20;
+  }
+
+// The source from which the country_code is derived.
+  optional CountryCodeSource country_code_source = 6;
 }
 
 // Examples

java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java

 import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat;
 import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
 import com.google.protobuf.MessageLite;
 import junit.framework.TestCase;
 
     assertEquals("900([135]\\d{6}|9\\d{7})", metadata.getPremiumRate().getNationalNumberPattern());
   }
 
+  
+
   public void testGetInstanceLoadARMetadata() {
     PhoneMetadata metadata = phoneUtil.getMetadataForRegion("AR");
     assertEquals("AR", metadata.getId());
     assertEquals("$1 $2 $3 $4", metadata.getIntlNumberFormat(3).getFormat());
   }
 
+    public void testGetLengthOfGeographicalAreaCode() {
+    // Google MTV, which has area code "650".
+    PhoneNumber usNumber1 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(6502530000L).build();
+    assertEquals(3, phoneUtil.getLengthOfGeographicalAreaCode(usNumber1));
+
+    // A North America toll-free number, which has no area code.
+    PhoneNumber usNumber2 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8002530000L).build();
+    assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(usNumber2));
+
+    // An invalid US number (1 digit shorter), which has no area code.
+    PhoneNumber usNumber3 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(650253000L).build();
+    assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(usNumber3));
+
+    // Google London, which has area code "20".
+    PhoneNumber ukNumber1 =
+        PhoneNumber.newBuilder().setCountryCode(44).setNationalNumber(2070313000L).build();
+    assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(ukNumber1));
+
+    // A UK mobile phone, which has no area code.
+    PhoneNumber ukNumber2 =
+        PhoneNumber.newBuilder().setCountryCode(44).setNationalNumber(7123456789L).build();
+    assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(ukNumber2));
+
+    // Google Buenos Aires, which has area code "11".
+    PhoneNumber arNumber =
+        PhoneNumber.newBuilder().setCountryCode(54).setNationalNumber(1155303000L).build();
+    assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(arNumber));
+
+    // Google Sydney, which has area code "2".
+    PhoneNumber auNumber =
+        PhoneNumber.newBuilder().setCountryCode(61).setNationalNumber(293744000L).build();
+    assertEquals(1, phoneUtil.getLengthOfGeographicalAreaCode(auNumber));
+
+    // Google Singapore. Singapore has no area code and no national prefix.
+    PhoneNumber sgNumber =
+        PhoneNumber.newBuilder().setCountryCode(65).setNationalNumber(65218000L).build();
+    assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(sgNumber));
+  }
+
+  public void testGetNationalSignificantNumber() {
+    PhoneNumber usNumber =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(6502530000L).build();
+    assertEquals("6502530000", PhoneNumberUtil.getNationalSignificantNumber(usNumber));
+
+    // An Italian mobile number.
+    PhoneNumber itNumber1 =
+        PhoneNumber.newBuilder().setCountryCode(39).setNationalNumber(312345678L).build();
+    assertEquals("312345678", PhoneNumberUtil.getNationalSignificantNumber(itNumber1));
+
+    // An Italian fixed line number.
+    PhoneNumber itNumber2 =
+        PhoneNumber.newBuilder().setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true).build();
+    assertEquals("0236618300", PhoneNumberUtil.getNationalSignificantNumber(itNumber2));
+  }
+
   public void testGetExampleNumber() throws IOException {
     PhoneNumber deNumber =
         PhoneNumber.newBuilder().setCountryCode(49).setNationalNumber(30123456).build();
                  strippedNumber, numberToStrip.toString());
   }
 
-  public void testMaybeStripInternationalPrefix() {
+    public void testMaybeStripInternationalPrefix() {
     String internationalPrefix = "00[39]";
     StringBuffer numberToStrip = new StringBuffer("0034567700-3898003");
     // Note the dash is removed as part of the normalization.
     StringBuffer strippedNumber = new StringBuffer("45677003898003");
-    assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                           internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
     assertEquals("The number supplied was not stripped of its international prefix.",
                  strippedNumber.toString(), numberToStrip.toString());
-    // Now the number no longer starts with an IDD prefix, so it should now report false.
-    assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                            internationalPrefix));
+    // Now the number no longer starts with an IDD prefix, so it should now report
+    // FROM_DEFAULT_COUNTRY.
+    assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
 
     numberToStrip = new StringBuffer("00945677003898003");
-    assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                           internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
     assertEquals("The number supplied was not stripped of its international prefix.",
                  strippedNumber.toString(), numberToStrip.toString());
     // Test it works when the international prefix is broken up by spaces.
     numberToStrip = new StringBuffer("00 9 45677003898003");
-    assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                           internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
     assertEquals("The number supplied was not stripped of its international prefix.",
                  strippedNumber.toString(), numberToStrip.toString());
-    // Now the number no longer starts with an IDD prefix, so it should now report false.
-    assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                            internationalPrefix));
+    // Now the number no longer starts with an IDD prefix, so it should now report
+    // FROM_DEFAULT_COUNTRY.
+    assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
 
     // Test the + symbol is also recognised and stripped.
     numberToStrip = new StringBuffer("+45677003898003");
     strippedNumber = new StringBuffer("45677003898003");
-    assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                           internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
     assertEquals("The number supplied was not stripped of the plus symbol.",
                  strippedNumber.toString(), numberToStrip.toString());
 
     // If the number afterwards is a zero, we should not strip this - no country code begins with 0.
     numberToStrip = new StringBuffer("0090112-3123");
     strippedNumber = new StringBuffer("00901123123");
-    assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                            internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
     assertEquals("The number supplied had a 0 after the match so shouldn't be stripped.",
                  strippedNumber.toString(), numberToStrip.toString());
     // Here the 0 is separated by a space from the IDD.
     numberToStrip = new StringBuffer("009 0-112-3123");
-    assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
-                                                                            internationalPrefix));
+    assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY,
+                 phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip,
+                                                                     internationalPrefix));
   }
 
   public void testMaybeExtractCountryCode() {
+    PhoneNumber.Builder number = PhoneNumber.newBuilder();
     PhoneMetadata metadata = phoneUtil.getMetadataForRegion("US");
     // Note that for the US, the IDD is 011.
     try {
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Did not extract country code " + countryCode + " correctly.",
                    countryCode,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+      assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_NUMBER_WITH_IDD, number.getCountryCodeSource());
       // Should strip and normalize national significant number.
       assertEquals("Did not strip off the country code correctly.",
                    strippedNumber,
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
+    number.clear();
     try {
       String phoneNumber = "+6423456789";
       int countryCode = 64;
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Did not extract country code " + countryCode + " correctly.",
                    countryCode,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+      assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, number.getCountryCodeSource());      
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
+    number.clear();
     try {
       String phoneNumber = "2345-6789";
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Should not have extracted a country code - no international prefix present.",
                    0,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+    assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_DEFAULT_COUNTRY, number.getCountryCodeSource());
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
+    number.clear();
     try {
       String phoneNumber = "0119991123456789";
       StringBuffer numberToFill = new StringBuffer();
-      phoneUtil.maybeExtractCountryCode(phoneNumber, metadata,
-                                        numberToFill);
+      phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number);
       fail("Should have thrown an exception, no valid country code present.");
     } catch (NumberParseException e) {
       // Expected.
                    NumberParseException.ErrorType.INVALID_COUNTRY_CODE,
                    e.getErrorType());
     }
+    number.clear();
     try {
       String phoneNumber = "(1 610) 619 4466";
       int countryCode = 1;
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Should have extracted the country code of the region passed in",
                    countryCode,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+      assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN,
+                   number.getCountryCodeSource());
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
+    number.clear();
+    try {
+      String phoneNumber = "(1 610) 619 4466";
+      int countryCode = 1;
+      StringBuffer numberToFill = new StringBuffer();
+      assertEquals("Should have extracted the country code of the region passed in",
+                   countryCode,
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, false,
+                                                     number));
+      assertFalse("Should not contain CountryCodeSource.", number.hasCountryCodeSource());
+    } catch (NumberParseException e) {
+      fail("Should not have thrown an exception: " + e.toString());
+    }
+    number.clear();
     try {
       String phoneNumber = "(1 610) 619 446";
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Should not have extracted a country code - invalid number after extraction " +
                    "of uncertain country code.",
                    0,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, false,
+                                                     number));
+      assertFalse("Should not contain CountryCodeSource.", number.hasCountryCodeSource());
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
+    number.clear();
     try {
       String phoneNumber = "(1 610) 619 43 446";
       StringBuffer numberToFill = new StringBuffer();
       assertEquals("Should not have extracted a country code - invalid number both before and " +
                    "after extraction of uncertain country code.",
                    0,
-                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata,
-                                                     numberToFill));
+                   phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true,
+                                                     number));
+      assertEquals("Did not figure out CountryCodeSource correctly",
+                   CountryCodeSource.FROM_DEFAULT_COUNTRY, number.getCountryCodeSource());
     } catch (NumberParseException e) {
       fail("Should not have thrown an exception: " + e.toString());
     }
   }
 
   public void testParseAndKeepRaw() throws Exception {
-    PhoneNumber alphaNumericNumber =
+    PhoneNumber alphaNumericNumber1 =
         PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(180074935247L)
-            .setRawInput("1800 six-flags").build();
-    assertEquals(alphaNumericNumber,
+            .setRawInput("1800 six-flags")
+            .setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY).build();
+    assertEquals(alphaNumericNumber1,
                  phoneUtil.parseAndKeepRawInput("1800 six-flags", "US"));
+
+    PhoneNumber alphaNumericNumber2 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8007493524L)
+            .setRawInput("1800 six-flag")
+            .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN).build();
+    assertEquals(alphaNumericNumber2,
+                 phoneUtil.parseAndKeepRawInput("1800 six-flag", "US"));
+
+    PhoneNumber alphaNumericNumber3 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8007493524L)
+            .setRawInput("+1800 six-flag")
+            .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN).build();
+    assertEquals(alphaNumericNumber3,
+                 phoneUtil.parseAndKeepRawInput("+1800 six-flag", "CN"));
+
+    PhoneNumber alphaNumericNumber4 =
+        PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(18007493524L)
+            .setRawInput("1800 six-flag")
+            .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_IDD).build();
+    assertEquals(alphaNumericNumber4,
+                 phoneUtil.parseAndKeepRawInput("001800 six-flag", "NZ"));
   }
 
   public void testCountryWithNoNumberDesc() {
-    // Andorra is a country where we don't have PhoneNumberDesc info in the meta data.
+    // Andorra is a country where we don't have PhoneNumberDesc info in the metadata.
     PhoneNumber adNumber =
         PhoneNumber.newBuilder().setCountryCode(376).setNationalNumber(12345L).build();
     assertEquals("+376 12345", phoneUtil.format(adNumber,