| @ -0,0 +1,29 @@ | |||
| /* | |||
| * Copyright (C) 2013 The Libphonenumber Authors | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||
| * you may not use this file except in compliance with the License. | |||
| * You may obtain a copy of the License at | |||
| * | |||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, software | |||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| * See the License for the specific language governing permissions and | |||
| * limitations under the License. | |||
| */ | |||
| #ifndef I18N_PHONENUMBERS_SHORT_METADATA_H_ | |||
| #define I18N_PHONENUMBERS_SHORT_METADATA_H_ | |||
| namespace i18n { | |||
| namespace phonenumbers { | |||
| int short_metadata_size(); | |||
| const void* short_metadata_get(); | |||
| } // namespace phonenumbers | |||
| } // namespace i18n | |||
| #endif // I18N_PHONENUMBERS_SHORT_METADATA_H_ | |||
| @ -0,0 +1,123 @@ | |||
| // Copyright (C) 2012 The Libphonenumber Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // Author: David Yonge-Mallo | |||
| #include "phonenumbers/shortnumberinfo.h" | |||
| #include <string.h> | |||
| #include <iterator> | |||
| #include <map> | |||
| #include "phonenumbers/base/memory/scoped_ptr.h" | |||
| #include "phonenumbers/default_logger.h" | |||
| #include "phonenumbers/phonemetadata.pb.h" | |||
| #include "phonenumbers/phonenumberutil.h" | |||
| #include "phonenumbers/regexp_adapter.h" | |||
| #include "phonenumbers/regexp_factory.h" | |||
| #include "phonenumbers/short_metadata.h" | |||
| namespace i18n { | |||
| namespace phonenumbers { | |||
| using std::cerr; | |||
| using std::endl; | |||
| using std::make_pair; | |||
| using std::map; | |||
| using std::string; | |||
| bool LoadCompiledInMetadata(PhoneMetadataCollection* metadata) { | |||
| if (!metadata->ParseFromArray(short_metadata_get(), short_metadata_size())) { | |||
| cerr << "Could not parse binary data." << endl; | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| ShortNumberInfo::ShortNumberInfo() | |||
| : phone_util_(*PhoneNumberUtil::GetInstance()), | |||
| region_to_short_metadata_map_(new map<string, PhoneMetadata>()) { | |||
| PhoneMetadataCollection metadata_collection; | |||
| if (!LoadCompiledInMetadata(&metadata_collection)) { | |||
| LOG(DFATAL) << "Could not parse compiled-in metadata."; | |||
| return; | |||
| } | |||
| for (RepeatedPtrField<PhoneMetadata>::const_iterator it = | |||
| metadata_collection.metadata().begin(); | |||
| it != metadata_collection.metadata().end(); | |||
| ++it) { | |||
| const string& region_code = it->id(); | |||
| region_to_short_metadata_map_->insert(make_pair(region_code, *it)); | |||
| } | |||
| } | |||
| // Returns a pointer to the phone metadata for the appropriate region or NULL | |||
| // if the region code is invalid or unknown. | |||
| const PhoneMetadata* ShortNumberInfo::GetMetadataForRegion( | |||
| const string& region_code) const { | |||
| map<string, PhoneMetadata>::const_iterator it = | |||
| region_to_short_metadata_map_->find(region_code); | |||
| if (it != region_to_short_metadata_map_->end()) { | |||
| return &it->second; | |||
| } | |||
| return NULL; | |||
| } | |||
| bool ShortNumberInfo::ConnectsToEmergencyNumber(const string& number, | |||
| const string& region_code) const { | |||
| return MatchesEmergencyNumberHelper(number, region_code, | |||
| true /* allows prefix match */); | |||
| } | |||
| bool ShortNumberInfo::IsEmergencyNumber(const string& number, | |||
| const string& region_code) const { | |||
| return MatchesEmergencyNumberHelper(number, region_code, | |||
| false /* doesn't allow prefix match */); | |||
| } | |||
| bool ShortNumberInfo::MatchesEmergencyNumberHelper(const string& number, | |||
| const string& region_code, bool allow_prefix_match) const { | |||
| string extracted_number; | |||
| phone_util_.ExtractPossibleNumber(number, &extracted_number); | |||
| if (phone_util_.StartsWithPlusCharsPattern(extracted_number)) { | |||
| // Returns false if the number starts with a plus sign. We don't believe | |||
| // dialing the country code before emergency numbers (e.g. +1911) works, | |||
| // but later, if that proves to work, we can add additional logic here to | |||
| // handle it. | |||
| return false; | |||
| } | |||
| const PhoneMetadata* metadata = GetMetadataForRegion(region_code); | |||
| if (!metadata || !metadata->has_emergency()) { | |||
| return false; | |||
| } | |||
| const scoped_ptr<const AbstractRegExpFactory> regexp_factory( | |||
| new RegExpFactory()); | |||
| const scoped_ptr<const RegExp> emergency_number_pattern( | |||
| regexp_factory->CreateRegExp( | |||
| metadata->emergency().national_number_pattern())); | |||
| phone_util_.NormalizeDigitsOnly(&extracted_number); | |||
| const scoped_ptr<RegExpInput> normalized_number_input( | |||
| regexp_factory->CreateInput(extracted_number)); | |||
| // In Brazil and Chile, emergency numbers don't work when additional digits | |||
| // are appended. | |||
| return (!allow_prefix_match || | |||
| region_code == "BR" || region_code == "CL") | |||
| ? emergency_number_pattern->FullMatch(extracted_number) | |||
| : emergency_number_pattern->Consume(normalized_number_input.get()); | |||
| } | |||
| } // namespace phonenumbers | |||
| } // namespace i18n | |||
| @ -0,0 +1,78 @@ | |||
| // Copyright (C) 2012 The Libphonenumber Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // Utility for international short phone numbers, such as short codes and | |||
| // emergency numbers. Note most commercial short numbers are not handled here, | |||
| // but by the phonenumberutil. | |||
| #ifndef I18N_PHONENUMBERS_SHORTNUMBERINFO_H_ | |||
| #define I18N_PHONENUMBERS_SHORTNUMBERINFO_H_ | |||
| #include <map> | |||
| #include <string> | |||
| #include "phonenumbers/base/basictypes.h" | |||
| #include "phonenumbers/base/memory/scoped_ptr.h" | |||
| #include "phonenumbers/phonemetadata.pb.h" | |||
| namespace i18n { | |||
| namespace phonenumbers { | |||
| using std::map; | |||
| using std::string; | |||
| class PhoneNumberUtil; | |||
| class ShortNumberInfo { | |||
| public: | |||
| ShortNumberInfo(); | |||
| // Returns true if the number might be used to connect to an emergency service | |||
| // in the given region. | |||
| // | |||
| // This method takes into account cases where the number might contain | |||
| // formatting, or might have additional digits appended (when it is okay to do | |||
| // that in the region specified). | |||
| bool ConnectsToEmergencyNumber(const string& number, | |||
| const string& region_code) const; | |||
| // Returns true if the number exactly matches an emergency service number in | |||
| // the given region. | |||
| // | |||
| // This method takes into account cases where the number might contain | |||
| // formatting, but doesn't allow additional digits to be appended. | |||
| bool IsEmergencyNumber(const string& number, | |||
| const string& region_code) const; | |||
| private: | |||
| const PhoneNumberUtil& phone_util_; | |||
| // A mapping from a RegionCode to the PhoneMetadata for that region. | |||
| scoped_ptr<map<string, PhoneMetadata> > | |||
| region_to_short_metadata_map_; | |||
| const i18n::phonenumbers::PhoneMetadata* GetMetadataForRegion( | |||
| const string& region_code) const; | |||
| bool MatchesEmergencyNumberHelper(const string& number, | |||
| const string& region_code, | |||
| bool allow_prefix_match) const; | |||
| DISALLOW_COPY_AND_ASSIGN(ShortNumberInfo); | |||
| }; | |||
| } // namespace phonenumbers | |||
| } // namespace i18n | |||
| #endif // I18N_PHONENUMBERS_SHORTNUMBERINFO_H_ | |||
| @ -0,0 +1,174 @@ | |||
| // Copyright (C) 2009 The Libphonenumber Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| // Author: David Yonge-Mallo | |||
| #include <gtest/gtest.h> | |||
| #include "phonenumbers/default_logger.h" | |||
| #include "phonenumbers/phonenumberutil.h" | |||
| #include "phonenumbers/shortnumberinfo.h" | |||
| #include "phonenumbers/test_util.h" | |||
| namespace i18n { | |||
| namespace phonenumbers { | |||
| class ShortNumberInfoTest : public testing::Test { | |||
| protected: | |||
| ShortNumberInfoTest() : short_info_() { | |||
| PhoneNumberUtil::GetInstance()->SetLogger(new StdoutLogger()); | |||
| } | |||
| const ShortNumberInfo short_info_; | |||
| private: | |||
| DISALLOW_COPY_AND_ASSIGN(ShortNumberInfoTest); | |||
| }; | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_US) { | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("911", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("112", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("999", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumberLongNumber_US) { | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("9116666666", | |||
| RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("1126666666", | |||
| RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("9996666666", | |||
| RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumberWithFormatting_US) { | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("9-1-1", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("1-1-2", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("9-9-9", | |||
| RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumberWithPlusSign_US) { | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("+911", RegionCode::US())); | |||
| // This hex sequence is the full-width plus sign U+FF0B. | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("\xEF\xBC\x8B" "911", | |||
| RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber(" +911", | |||
| RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("+112", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("+999", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_BR) { | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("911", RegionCode::BR())); | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("190", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("999", RegionCode::BR())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumberLongNumber_BR) { | |||
| // Brazilian emergency numbers don't work when additional digits are appended. | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("9111", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("1900", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("9996", RegionCode::BR())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_CL) { | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("131", RegionCode::CL())); | |||
| EXPECT_TRUE(short_info_.ConnectsToEmergencyNumber("133", RegionCode::CL())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumberLongNumber_CL) { | |||
| // Chilean emergency numbers don't work when additional digits are appended. | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("1313", RegionCode::CL())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("1330", RegionCode::CL())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_AO) { | |||
| // Angola doesn't have any metadata for emergency numbers in the test | |||
| // metadata. | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("911", RegionCode::AO())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("222123456", | |||
| RegionCode::AO())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("923123456", | |||
| RegionCode::AO())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, ConnectsToEmergencyNumber_ZW) { | |||
| // Zimbabwe doesn't have any metadata in the test metadata. | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("911", RegionCode::ZW())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("01312345", | |||
| RegionCode::ZW())); | |||
| EXPECT_FALSE(short_info_.ConnectsToEmergencyNumber("0711234567", | |||
| RegionCode::ZW())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumber_US) { | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("911", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("112", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("999", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumberLongNumber_US) { | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("9116666666", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("1126666666", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("9996666666", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumberWithFormatting_US) { | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("9-1-1", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("*911", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("1-1-2", RegionCode::US())); | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("*112", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("9-9-9", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("*999", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumberWithPlusSign_US) { | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("+911", RegionCode::US())); | |||
| // This hex sequence is the full-width plus sign U+FF0B. | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("\xEF\xBC\x8B" "911", | |||
| RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber(" +911", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("+112", RegionCode::US())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("+999", RegionCode::US())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumber_BR) { | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("911", RegionCode::BR())); | |||
| EXPECT_TRUE(short_info_.IsEmergencyNumber("190", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("999", RegionCode::BR())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, EmergencyNumberLongNumber_BR) { | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("9111", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("1900", RegionCode::BR())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("9996", RegionCode::BR())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumber_AO) { | |||
| // Angola doesn't have any metadata for emergency numbers in the test | |||
| // metadata. | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("911", RegionCode::AO())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("222123456", RegionCode::AO())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("923123456", RegionCode::AO())); | |||
| } | |||
| TEST_F(ShortNumberInfoTest, IsEmergencyNumber_ZW) { | |||
| // Zimbabwe doesn't have any metadata in the test metadata. | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("911", RegionCode::ZW())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("01312345", RegionCode::ZW())); | |||
| EXPECT_FALSE(short_info_.IsEmergencyNumber("0711234567", RegionCode::ZW())); | |||
| } | |||
| } // namespace phonenumbers | |||
| } // namespace i18n | |||
| @ -0,0 +1,350 @@ | |||
| /* | |||
| * Copyright (C) 2013 The Libphonenumber Authors | |||
| * | |||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||
| * you may not use this file except in compliance with the License. | |||
| * You may obtain a copy of the License at | |||
| * | |||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, software | |||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| * See the License for the specific language governing permissions and | |||
| * limitations under the License. | |||
| */ | |||
| package com.google.i18n.phonenumbers; | |||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; | |||
| import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; | |||
| import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; | |||
| import java.util.Collections; | |||
| import java.util.List; | |||
| import java.util.Set; | |||
| import java.util.logging.Level; | |||
| import java.util.logging.Logger; | |||
| import java.util.regex.Pattern; | |||
| /** | |||
| * Methods for getting information about short phone numbers, such as short codes and emergency | |||
| * numbers. Note that most commercial short numbers are not handled here, but by the | |||
| * {@link PhoneNumberUtil}. | |||
| * | |||
| * @author Shaopeng Jia | |||
| * @author David Yonge-Mallo | |||
| */ | |||
| public class ShortNumberInfo { | |||
| private static final Logger logger = Logger.getLogger(ShortNumberInfo.class.getName()); | |||
| private static final ShortNumberInfo INSTANCE = | |||
| new ShortNumberInfo(PhoneNumberUtil.getInstance()); | |||
| /** Cost categories of short numbers. */ | |||
| public enum ShortNumberCost { | |||
| TOLL_FREE, | |||
| STANDARD_RATE, | |||
| PREMIUM_RATE, | |||
| UNKNOWN_COST | |||
| } | |||
| /** Returns the singleton instance of the ShortNumberInfo. */ | |||
| public static ShortNumberInfo getInstance() { | |||
| return INSTANCE; | |||
| } | |||
| private final PhoneNumberUtil phoneUtil; | |||
| // @VisibleForTesting | |||
| ShortNumberInfo(PhoneNumberUtil util) { | |||
| phoneUtil = util; | |||
| } | |||
| /** | |||
| * Check whether a short number is a possible number, given the number in the form of a string, | |||
| * and the region where the number is dialed from. This provides a more lenient check than | |||
| * {@link #isValidShortNumber}. | |||
| * | |||
| * @param shortNumber the short number to check as a string | |||
| * @param regionDialingFrom the region from which the number is dialed | |||
| * @return whether the number is a possible short number | |||
| */ | |||
| public boolean isPossibleShortNumber(String shortNumber, String regionDialingFrom) { | |||
| PhoneMetadata phoneMetadata = | |||
| MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom); | |||
| if (phoneMetadata == null) { | |||
| return false; | |||
| } | |||
| PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc(); | |||
| return phoneUtil.isNumberPossibleForDesc(shortNumber, generalDesc); | |||
| } | |||
| /** | |||
| * Check whether a short number is a possible number. This provides a more lenient check than | |||
| * {@link #isValidShortNumber}. See {@link #isPossibleShortNumber(String, String)} for | |||
| * details. | |||
| * | |||
| * @param number the short number to check | |||
| * @return whether the number is a possible short number | |||
| */ | |||
| public boolean isPossibleShortNumber(PhoneNumber number) { | |||
| List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); | |||
| String shortNumber = phoneUtil.getNationalSignificantNumber(number); | |||
| String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); | |||
| if (regionCodes.size() > 1 && regionCode != null) { | |||
| // If a matching region had been found for the phone number from among two or more regions, | |||
| // then we have already implicitly verified its validity for that region. | |||
| return true; | |||
| } | |||
| return isPossibleShortNumber(shortNumber, regionCode); | |||
| } | |||
| /** | |||
| * Tests whether a short number matches a valid pattern. Note that this doesn't verify the number | |||
| * is actually in use, which is impossible to tell by just looking at the number itself. | |||
| * | |||
| * @param shortNumber the short number to check as a string | |||
| * @param regionDialingFrom the region from which the number is dialed | |||
| * @return whether the short number matches a valid pattern | |||
| */ | |||
| public boolean isValidShortNumber(String shortNumber, String regionDialingFrom) { | |||
| PhoneMetadata phoneMetadata = | |||
| MetadataManager.getShortNumberMetadataForRegion(regionDialingFrom); | |||
| if (phoneMetadata == null) { | |||
| return false; | |||
| } | |||
| PhoneNumberDesc generalDesc = phoneMetadata.getGeneralDesc(); | |||
| if (!generalDesc.hasNationalNumberPattern() || | |||
| !phoneUtil.isNumberMatchingDesc(shortNumber, generalDesc)) { | |||
| return false; | |||
| } | |||
| PhoneNumberDesc shortNumberDesc = phoneMetadata.getShortCode(); | |||
| if (!shortNumberDesc.hasNationalNumberPattern()) { | |||
| logger.log(Level.WARNING, "No short code national number pattern found for region: " + | |||
| regionDialingFrom); | |||
| return false; | |||
| } | |||
| return phoneUtil.isNumberMatchingDesc(shortNumber, shortNumberDesc); | |||
| } | |||
| /** | |||
| * Tests whether a short number matches a valid pattern. Note that this doesn't verify the number | |||
| * is actually in use, which is impossible to tell by just looking at the number itself. See | |||
| * {@link #isValidShortNumber(String, String)} for details. | |||
| * | |||
| * @param number the short number for which we want to test the validity | |||
| * @return whether the short number matches a valid pattern | |||
| */ | |||
| public boolean isValidShortNumber(PhoneNumber number) { | |||
| List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); | |||
| String shortNumber = phoneUtil.getNationalSignificantNumber(number); | |||
| String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); | |||
| if (regionCodes.size() > 1 && regionCode != null) { | |||
| // If a matching region had been found for the phone number from among two or more regions, | |||
| // then we have already implicitly verified its validity for that region. | |||
| return true; | |||
| } | |||
| return isValidShortNumber(shortNumber, regionCode); | |||
| } | |||
| /** | |||
| * Gets the expected cost category of a short number (however, nothing is implied about its | |||
| * validity). If it is important that the number is valid, then its validity must first be checked | |||
| * using {@link isValidShortNumber}. Note that emergency numbers are always considered toll-free. | |||
| * Example usage: | |||
| * <pre>{@code | |||
| * PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); | |||
| * ShortNumberInfo shortInfo = ShortNumberInfo.getInstance(); | |||
| * PhoneNumber number = phoneUtil.parse("110", "FR"); | |||
| * if (shortInfo.isValidShortNumber(number)) { | |||
| * ShortNumberInfo.ShortNumberCost cost = shortInfo.getExpectedCost(number); | |||
| * // Do something with the cost information here. | |||
| * }}</pre> | |||
| * | |||
| * @param number the short number for which we want to know the expected cost category | |||
| * @return the expected cost category of the short number. Returns UNKNOWN_COST if the number does | |||
| * not match a cost category. Note that an invalid number may match any cost category. | |||
| */ | |||
| public ShortNumberCost getExpectedCost(PhoneNumber number) { | |||
| List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); | |||
| String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); | |||
| // Note that regionCode may be null, in which case phoneMetadata will also be null. | |||
| PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| if (phoneMetadata == null) { | |||
| return ShortNumberCost.UNKNOWN_COST; | |||
| } | |||
| String nationalNumber = phoneUtil.getNationalSignificantNumber(number); | |||
| // The cost categories are tested in order of decreasing expense, since if for some reason the | |||
| // patterns overlap the most expensive matching cost category should be returned. | |||
| if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getPremiumRate())) { | |||
| return ShortNumberCost.PREMIUM_RATE; | |||
| } | |||
| if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getStandardRate())) { | |||
| return ShortNumberCost.STANDARD_RATE; | |||
| } | |||
| if (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getTollFree())) { | |||
| return ShortNumberCost.TOLL_FREE; | |||
| } | |||
| if (isEmergencyNumber(nationalNumber, regionCode)) { | |||
| // Emergency numbers are implicitly toll-free. | |||
| return ShortNumberCost.TOLL_FREE; | |||
| } | |||
| return ShortNumberCost.UNKNOWN_COST; | |||
| } | |||
| // Helper method to get the region code for a given phone number, from a list of possible region | |||
| // codes. If the list contains more than one region, the first region for which the number is | |||
| // valid is returned. | |||
| private String getRegionCodeForShortNumberFromRegionList(PhoneNumber number, | |||
| List<String> regionCodes) { | |||
| if (regionCodes.size() == 0) { | |||
| return null; | |||
| } else if (regionCodes.size() == 1) { | |||
| return regionCodes.get(0); | |||
| } | |||
| String nationalNumber = phoneUtil.getNationalSignificantNumber(number); | |||
| for (String regionCode : regionCodes) { | |||
| PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| if (phoneMetadata != null && | |||
| phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getShortCode())) { | |||
| // The number is valid for this region. | |||
| return regionCode; | |||
| } | |||
| } | |||
| return null; | |||
| } | |||
| /** | |||
| * Convenience method to get a list of what regions the library has metadata for. | |||
| */ | |||
| Set<String> getSupportedRegions() { | |||
| return Collections.unmodifiableSet(MetadataManager.getShortNumberMetadataSupportedRegions()); | |||
| } | |||
| /** | |||
| * Gets a valid short number for the specified region. | |||
| * | |||
| * @param regionCode the region for which an example short number is needed | |||
| * @return a valid short number for the specified region. Returns an empty string when the | |||
| * metadata does not contain such information. | |||
| */ | |||
| // @VisibleForTesting | |||
| String getExampleShortNumber(String regionCode) { | |||
| PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| if (phoneMetadata == null) { | |||
| return ""; | |||
| } | |||
| PhoneNumberDesc desc = phoneMetadata.getShortCode(); | |||
| if (desc.hasExampleNumber()) { | |||
| return desc.getExampleNumber(); | |||
| } | |||
| return ""; | |||
| } | |||
| /** | |||
| * Gets a valid short number for the specified cost category. | |||
| * | |||
| * @param regionCode the region for which an example short number is needed | |||
| * @param cost the cost category of number that is needed | |||
| * @return a valid short number for the specified region and cost category. Returns an empty | |||
| * string when the metadata does not contain such information, or the cost is UNKNOWN_COST. | |||
| */ | |||
| // @VisibleForTesting | |||
| String getExampleShortNumberForCost(String regionCode, ShortNumberCost cost) { | |||
| PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| if (phoneMetadata == null) { | |||
| return ""; | |||
| } | |||
| PhoneNumberDesc desc = null; | |||
| switch (cost) { | |||
| case TOLL_FREE: | |||
| desc = phoneMetadata.getTollFree(); | |||
| break; | |||
| case STANDARD_RATE: | |||
| desc = phoneMetadata.getStandardRate(); | |||
| break; | |||
| case PREMIUM_RATE: | |||
| desc = phoneMetadata.getPremiumRate(); | |||
| break; | |||
| default: | |||
| // UNKNOWN_COST numbers are computed by the process of elimination from the other cost | |||
| // categories. | |||
| } | |||
| if (desc != null && desc.hasExampleNumber()) { | |||
| return desc.getExampleNumber(); | |||
| } | |||
| return ""; | |||
| } | |||
| /** | |||
| * Returns true if the number might be used to connect to an emergency service in the given | |||
| * region. | |||
| * | |||
| * This method takes into account cases where the number might contain formatting, or might have | |||
| * additional digits appended (when it is okay to do that in the region specified). | |||
| * | |||
| * @param number the phone number to test | |||
| * @param regionCode the region where the phone number is being dialed | |||
| * @return whether the number might be used to connect to an emergency service in the given region | |||
| */ | |||
| public boolean connectsToEmergencyNumber(String number, String regionCode) { | |||
| return matchesEmergencyNumberHelper(number, regionCode, true /* allows prefix match */); | |||
| } | |||
| /** | |||
| * Returns true if the number exactly matches an emergency service number in the given region. | |||
| * | |||
| * This method takes into account cases where the number might contain formatting, but doesn't | |||
| * allow additional digits to be appended. | |||
| * | |||
| * @param number the phone number to test | |||
| * @param regionCode the region where the phone number is being dialed | |||
| * @return whether the number exactly matches an emergency services number in the given region | |||
| */ | |||
| public boolean isEmergencyNumber(String number, String regionCode) { | |||
| return matchesEmergencyNumberHelper(number, regionCode, false /* doesn't allow prefix match */); | |||
| } | |||
| private boolean matchesEmergencyNumberHelper(String number, String regionCode, | |||
| boolean allowPrefixMatch) { | |||
| number = PhoneNumberUtil.extractPossibleNumber(number); | |||
| if (PhoneNumberUtil.PLUS_CHARS_PATTERN.matcher(number).lookingAt()) { | |||
| // Returns false if the number starts with a plus sign. We don't believe dialing the country | |||
| // code before emergency numbers (e.g. +1911) works, but later, if that proves to work, we can | |||
| // add additional logic here to handle it. | |||
| return false; | |||
| } | |||
| PhoneMetadata metadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| if (metadata == null || !metadata.hasEmergency()) { | |||
| return false; | |||
| } | |||
| Pattern emergencyNumberPattern = | |||
| Pattern.compile(metadata.getEmergency().getNationalNumberPattern()); | |||
| String normalizedNumber = PhoneNumberUtil.normalizeDigitsOnly(number); | |||
| // In Brazil and Chile, emergency numbers don't work when additional digits are appended. | |||
| return (!allowPrefixMatch || regionCode == "BR" || regionCode == "CL") | |||
| ? emergencyNumberPattern.matcher(normalizedNumber).matches() | |||
| : emergencyNumberPattern.matcher(normalizedNumber).lookingAt(); | |||
| } | |||
| /** | |||
| * Given a valid short number, determines whether it is carrier-specific (however, nothing is | |||
| * implied about its validity). If it is important that the number is valid, then its validity | |||
| * must first be checked using {@link isValidShortNumber}. | |||
| * | |||
| * @param number the valid short number to check | |||
| * @return whether the short number is carrier-specific (assuming the input was a valid short | |||
| * number). | |||
| */ | |||
| public boolean isCarrierSpecific(PhoneNumber number) { | |||
| List<String> regionCodes = phoneUtil.getRegionCodesForCountryCode(number.getCountryCode()); | |||
| String regionCode = getRegionCodeForShortNumberFromRegionList(number, regionCodes); | |||
| String nationalNumber = phoneUtil.getNationalSignificantNumber(number); | |||
| PhoneMetadata phoneMetadata = MetadataManager.getShortNumberMetadataForRegion(regionCode); | |||
| return (phoneMetadata != null) && | |||
| (phoneUtil.isNumberMatchingDesc(nationalNumber, phoneMetadata.getCarrierSpecific())); | |||
| } | |||
| } | |||