| @ -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())); | |||||
| } | |||||
| } | |||||