Merge branch 'charlesmchen/swiftPhoneNumberUtil'

This commit is contained in:
Matthew Chen 2021-10-22 19:07:56 -03:00
commit b1a2393064
18 changed files with 512 additions and 211 deletions

2
Pods

@ -1 +1 @@
Subproject commit 174145d81c55573e85e5ab22a59dd824f04ca685
Subproject commit 8254c8fc47202f689fe18c4ccfee03a814390ea5

View File

@ -361,15 +361,15 @@ extension DeleteAccountConfirmationViewController: CountryCodeViewControllerDele
if let localE164 = PhoneNumber(fromE164: localNumber), let localCountryCode = localE164.getCountryCode()?.intValue {
callingCodeInt = localCountryCode
} else {
callingCodeInt = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(
callingCodeInt = phoneNumberUtil.getCountryCode(
forRegion: PhoneNumber.defaultCountryCode()
)?.intValue
).intValue
}
var callingCode: String?
if let callingCodeInt = callingCodeInt {
callingCode = COUNTRY_CODE_PREFIX + "\(callingCodeInt)"
countryCode = PhoneNumberUtil.sharedThreadLocal().probableCountryCode(forCallingCode: callingCode!)
countryCode = phoneNumberUtil.probableCountryCode(forCallingCode: callingCode!)
}
updateCountry(callingCode: callingCode, countryCode: countryCode)

View File

@ -28,7 +28,7 @@ public class OnboardingCountryState: NSObject {
countryCode = lastRegisteredCountryCode
}
let callingCodeNumber: NSNumber = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCodeNumber: NSNumber = phoneNumberUtil.getCountryCode(forRegion: countryCode)
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumber)"
var countryName = NSLocalizedString("UNKNOWN_COUNTRY_NAME", comment: "Label for unknown countries.")

View File

@ -353,8 +353,7 @@ public class OnboardingPhoneNumberViewController: OnboardingBaseViewController {
return nil
}
let callingCode = "\(COUNTRY_CODE_PREFIX)\(callingCodeNumeric)"
let countryCodes: [String] =
PhoneNumberUtil.sharedThreadLocal().countryCodes(fromCallingCode: callingCode)
let countryCodes: [String] = phoneNumberUtil.countryCodes(fromCallingCode: callingCode)
guard let countryCode = countryCodes.first else {
owsFailDebug("Could not resume re-registration; unknown countryCode.")
return nil

View File

@ -114,7 +114,8 @@ NS_ASSUME_NONNULL_BEGIN
id<PaymentsCurrencies> paymentsCurrencies = [PaymentsCurrenciesImpl new];
SpamChallengeResolver *spamChallengeResolver = [SpamChallengeResolver new];
AvatarBuilder *avatarBuilder = [AvatarBuilder new];
PhoneNumberUtil *phoneNumberUtil = [PhoneNumberUtil new];
[Environment setShared:[[Environment alloc] initWithIncomingContactSyncJobQueue:incomingContactSyncJobQueue
incomingGroupSyncJobQueue:incomingGroupSyncJobQueue
launchJobs:launchJobs
@ -173,7 +174,8 @@ NS_ASSUME_NONNULL_BEGIN
payments:payments
paymentsCurrencies:paymentsCurrencies
spamChallengeResolver:spamChallengeResolver
senderKeyStore:senderKeyStore]];
senderKeyStore:senderKeyStore
phoneNumberUtil:phoneNumberUtil]];
appSpecificSingletonBlock();

View File

@ -33,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN
withSpecifiedCountryCodeString:(NSString *)countryCodeString;
+ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber;
+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString;
- (NSURL *)toSystemDialerURL;
- (NSString *)toE164;
- (nullable NSNumber *)getCountryCode;

View File

@ -44,22 +44,20 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
OWSAssertDebug(text != nil);
OWSAssertDebug(regionCode != nil);
PhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal];
NSError *parseError = nil;
NBPhoneNumber *number = [phoneUtil parse:text defaultRegion:regionCode error:&parseError];
NBPhoneNumber *number = [self.phoneNumberUtil parse:text defaultRegion:regionCode error:&parseError];
if (parseError) {
OWSLogVerbose(@"parseError: %@", parseError);
return nil;
}
if (![phoneUtil.nbPhoneNumberUtil isPossibleNumber:number]) {
if (![self.phoneNumberUtil isPossibleNumber:number]) {
return nil;
}
NSError *toE164Error;
NSString *e164 = [phoneUtil format:number numberFormat:NBEPhoneNumberFormatE164 error:&toE164Error];
NSString *e164 = [self.phoneNumberUtil format:number numberFormat:NBEPhoneNumberFormatE164 error:&toE164Error];
if (toE164Error) {
OWSLogDebug(@"Issue while formatting number: %@", [toE164Error description]);
return nil;
@ -80,7 +78,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
NSString *_Nullable countryCode = nil;
#if TARGET_OS_IPHONE
countryCode = [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil countryCodeByCarrier];
countryCode = [self.phoneNumberUtil countryCodeByCarrier];
if ([countryCode isEqualToString:@"ZZ"]) {
countryCode = [locale objectForKey:NSLocaleCountryCode];
@ -212,11 +210,9 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
return formattedPhoneNumber;
}
+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString {
NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil;
NSString *regionCode =
[phoneUtil getRegionCodeForCountryCode:@([[countryCodeString substringFromIndex:1] integerValue])];
return regionCode;
+ (nullable NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString {
NSNumber *countryCallingCode = @([[countryCodeString substringFromIndex:1] integerValue]);
return [self.phoneNumberUtil getRegionCodeForCountryCode:countryCallingCode];
}
+ (nullable PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text {
@ -260,7 +256,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
if (localCallingCode != nil) {
NSString *localCallingCodePrefix = [NSString stringWithFormat:@"+%@", localCallingCode];
NSString *localCountryCode =
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:localCallingCodePrefix];
[self.phoneNumberUtil probableCountryCodeForCallingCode:localCallingCodePrefix];
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
NBPhoneMetaData *localNumberRegionMetadata = [helper getMetadataForRegion:localCountryCode];
@ -396,7 +392,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
// Italian phone number but use French region/language for their
// phone. They're likely to have both Italian and French contacts.
NSString *localCountryCode =
[PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:callingCodePrefix];
[self.phoneNumberUtil probableCountryCodeForCallingCode:callingCodePrefix];
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) {
tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:text], localCountryCode);
}
@ -587,9 +583,9 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
- (nullable NSString *)nationalNumber
{
NSError *error;
NSString *nationalNumber = [[PhoneNumberUtil sharedThreadLocal] format:self.phoneNumber
numberFormat:NBEPhoneNumberFormatNATIONAL
error:&error];
NSString *nationalNumber = [self.phoneNumberUtil format:self.phoneNumber
numberFormat:NBEPhoneNumberFormatNATIONAL
error:&error];
if (error) {
OWSLogVerbose(@"error parsing number into national format: %@", error);
return nil;
@ -600,7 +596,7 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
- (BOOL)isValid
{
return [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil isValidNumber:self.phoneNumber];
return [self.phoneNumberUtil isValidNumber:self.phoneNumber];
}
- (NSString *)description {

View File

@ -2,29 +2,22 @@
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
#import <SignalServiceKit/PhoneNumber.h>
#import <libPhoneNumber_iOS/NBPhoneNumberUtil.h>
NS_ASSUME_NONNULL_BEGIN
@class PhoneNumberUtilWrapper;
@interface PhoneNumberUtil : NSObject
@property (nonatomic, retain) NBPhoneNumberUtil *nbPhoneNumberUtil;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedThreadLocal;
// This property should only be accessed by Swift.
@property (nonatomic, readonly) PhoneNumberUtilWrapper *phoneNumberUtilWrapper;
+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString;
+ (NSString *)callingCodeFromCountryCode:(NSString *)countryCode;
+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode;
+ (NSArray *)countryCodesForSearchTerm:(nullable NSString *)searchTerm;
// Returns a list of country codes for a calling code in descending
// order of population.
- (NSArray<NSString *> *)countryCodesFromCallingCode:(NSString *)callingCode;
// Returns the most likely country code for a calling code based on population.
- (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode;
@ -33,12 +26,9 @@ NS_ASSUME_NONNULL_BEGIN
to:(NSString *)target
stickingRightward:(bool)preferHigh;
+ (NSString *)examplePhoneNumberForCountryCode:(NSString *)countryCode;
- (nullable NBPhoneNumber *)parse:(NSString *)numberToParse defaultRegion:(NSString *)defaultRegion error:(NSError **)error;
- (NSString *)format:(NBPhoneNumber *)phoneNumber
numberFormat:(NBEPhoneNumberFormat)numberFormat
error:(NSError **)error;
+ (nullable NBPhoneNumber *)getExampleNumberForType:(NSString *)regionCode
type:(NBEPhoneNumberType)type
nbPhoneNumberUtil:(NBPhoneNumberUtil *)nbPhoneNumberUtil;
@end

View File

@ -3,85 +3,26 @@
//
#import "PhoneNumberUtil.h"
#import "ContactsManagerProtocol.h"
#import "FunctionalUtil.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <libPhoneNumber_iOS/NBPhoneNumber.h>
NS_ASSUME_NONNULL_BEGIN
@interface PhoneNumberUtil ()
@property (nonatomic, readonly) NSMutableDictionary *countryCodesFromCallingCodeCache;
@property (nonatomic, readonly) AnyLRUCache *parsedPhoneNumberCache;
@end
#pragma mark -
@implementation PhoneNumberUtil
+ (PhoneNumberUtil *)sharedThreadLocal
{
NSString *key = PhoneNumberUtil.logTag;
PhoneNumberUtil *_Nullable threadLocal = NSThread.currentThread.threadDictionary[key];
if (!threadLocal) {
threadLocal = [PhoneNumberUtil new];
NSThread.currentThread.threadDictionary[key] = threadLocal;
}
return threadLocal;
}
- (instancetype)init {
self = [super init];
if (self) {
_nbPhoneNumberUtil = [[NBPhoneNumberUtil alloc] init];
_countryCodesFromCallingCodeCache = [NSMutableDictionary new];
_parsedPhoneNumberCache = [[AnyLRUCache alloc] initWithMaxSize:256 nseMaxSize:0 shouldEvacuateInBackground:NO];
_phoneNumberUtilWrapper = [PhoneNumberUtilWrapper new];
}
OWSSingletonAssert();
return self;
}
- (nullable NBPhoneNumber *)parse:(NSString *)numberToParse
defaultRegion:(NSString *)defaultRegion
error:(NSError **)error
{
NSString *hashKey = [NSString stringWithFormat:@"numberToParse:%@defaultRegion:%@", numberToParse, defaultRegion];
NBPhoneNumber *_Nullable result = (NBPhoneNumber *)[self.parsedPhoneNumberCache objectForKey:hashKey];
if (!result) {
result = [self.nbPhoneNumberUtil parse:numberToParse defaultRegion:defaultRegion error:error];
if (error && *error) {
OWSAssertDebug(!result);
return nil;
}
OWSAssertDebug(result);
if (result) {
[self.parsedPhoneNumberCache setObject:result forKey:hashKey];
} else {
[self.parsedPhoneNumberCache setObject:[NSNull null] forKey:hashKey];
}
}
if ([result class] == [NSNull class]) {
return nil;
} else {
return result;
}
}
- (NSString *)format:(NBPhoneNumber *)phoneNumber
numberFormat:(NBEPhoneNumberFormat)numberFormat
error:(NSError **)error
{
return [self.nbPhoneNumberUtil format:phoneNumber numberFormat:numberFormat error:error];
}
// country code -> country name
+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode
{
@ -102,82 +43,6 @@ NS_ASSUME_NONNULL_BEGIN
return countryName;
}
// country code -> calling code
+ (NSString *)callingCodeFromCountryCode:(NSString *)countryCode
{
if (countryCode.length < 1) {
return @"+0";
}
if ([countryCode isEqualToString:@"AQ"]) {
// Antarctica
return @"+672";
} else if ([countryCode isEqualToString:@"BV"]) {
// Bouvet Island
return @"+55";
} else if ([countryCode isEqualToString:@"IC"]) {
// Canary Islands
return @"+34";
} else if ([countryCode isEqualToString:@"EA"]) {
// Ceuta & Melilla
return @"+34";
} else if ([countryCode isEqualToString:@"CP"]) {
// Clipperton Island
//
// This country code should be filtered - it does not appear to have a calling code.
return nil;
} else if ([countryCode isEqualToString:@"DG"]) {
// Diego Garcia
return @"+246";
} else if ([countryCode isEqualToString:@"TF"]) {
// French Southern Territories
return @"+262";
} else if ([countryCode isEqualToString:@"HM"]) {
// Heard & McDonald Islands
return @"+672";
} else if ([countryCode isEqualToString:@"XK"]) {
// Kosovo
return @"+383";
} else if ([countryCode isEqualToString:@"PN"]) {
// Pitcairn Islands
return @"+64";
} else if ([countryCode isEqualToString:@"GS"]) {
// So. Georgia & So. Sandwich Isl.
return @"+500";
} else if ([countryCode isEqualToString:@"UM"]) {
// U.S. Outlying Islands
return @"+1";
}
NSString *callingCode =
[NSString stringWithFormat:@"%@%@",
COUNTRY_CODE_PREFIX,
[[[self sharedThreadLocal] nbPhoneNumberUtil] getCountryCodeForRegion:countryCode]];
return callingCode;
}
- (NSArray<NSString *> *)countryCodesFromCallingCode:(NSString *)callingCode
{
@synchronized(self)
{
OWSAssertDebug(callingCode.length > 0);
NSArray *result = self.countryCodesFromCallingCodeCache[callingCode];
if (!result) {
NSMutableArray *countryCodes = [NSMutableArray new];
for (NSString *countryCode in [PhoneNumberUtil countryCodesSortedByPopulationDescending]) {
NSString *callingCodeForCountryCode = [PhoneNumberUtil callingCodeFromCountryCode:countryCode];
if ([callingCode isEqualToString:callingCodeForCountryCode]) {
[countryCodes addObject:countryCode];
}
}
result = [countryCodes copy];
self.countryCodesFromCallingCodeCache[callingCode] = result;
}
return result;
}
}
- (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode
{
OWSAssertDebug(callingCode.length > 0);
@ -315,28 +180,16 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+ (NSString *)examplePhoneNumberForCountryCode:(NSString *)countryCode
+ (nullable NBPhoneNumber *)getExampleNumberForType:(NSString *)regionCode
type:(NBEPhoneNumberType)type
nbPhoneNumberUtil:(NBPhoneNumberUtil *)nbPhoneNumberUtil
{
PhoneNumberUtil *sharedUtil = [self sharedThreadLocal];
// Signal users are very likely using mobile devices, so prefer that kind of example.
NSError *error;
NBPhoneNumber *nbPhoneNumber =
[sharedUtil.nbPhoneNumberUtil getExampleNumberForType:countryCode type:NBEPhoneNumberTypeMOBILE error:&error];
OWSAssertDebug(!error);
if (!nbPhoneNumber) {
// For countries that with similar mobile and land lines, use "line or mobile"
// examples.
nbPhoneNumber = [sharedUtil.nbPhoneNumberUtil getExampleNumberForType:countryCode
type:NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE
error:&error];
OWSAssertDebug(!error);
NBPhoneNumber *_Nullable nbPhoneNumber = [nbPhoneNumberUtil getExampleNumberForType:regionCode type:type error:&error];
if (error != nil) {
OWSFailDebug(@"Error: %@", error);
}
NSString *result = (nbPhoneNumber
? [sharedUtil.nbPhoneNumberUtil format:nbPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:&error]
: nil);
OWSAssertDebug(!error);
return result;
return nbPhoneNumber;
}
@end

View File

@ -3,10 +3,247 @@
//
import Foundation
import libPhoneNumber_iOS
@objc
public class PhoneNumberUtilWrapper: NSObject {
fileprivate let nbPhoneNumberUtil = NBPhoneNumberUtil()
fileprivate var countryCodesFromCallingCodeCache = [String: [String]]()
fileprivate let parsedPhoneNumberCache = LRUCache<String, NBPhoneNumber>(maxSize: 256, nseMaxSize: 0, shouldEvacuateInBackground: false)
}
// MARK: -
fileprivate extension PhoneNumberUtilWrapper {
// country code -> calling code
func callingCode(fromCountryCode countryCode: String) -> String? {
guard let countryCode = countryCode.nilIfEmpty else {
return "+0"
}
if countryCode == "AQ" {
// Antarctica
return "+672"
} else if countryCode == "BV" {
// Bouvet Island
return "+55"
} else if countryCode == "IC" {
// Canary Islands
return "+34"
} else if countryCode == "EA" {
// Ceuta & Melilla
return "+34"
} else if countryCode == "CP" {
// Clipperton Island
//
// This country code should be filtered - it does not appear to have a calling code.
return nil
} else if countryCode == "DG" {
// Diego Garcia
return "+246"
} else if countryCode == "TF" {
// French Southern Territories
return "+262"
} else if countryCode == "HM" {
// Heard & McDonald Islands
return "+672"
} else if countryCode == "XK" {
// Kosovo
return "+383"
} else if countryCode == "PN" {
// Pitcairn Islands
return "+64"
} else if countryCode == "GS" {
// So. Georgia & So. Sandwich Isl.
return "+500"
} else if countryCode == "UM" {
// U.S. Outlying Islands
return "+1"
}
let countryCallingCode = getCountryCode(forRegion: countryCode)
let callingCode = COUNTRY_CODE_PREFIX + "\(countryCallingCode.intValue)"
return callingCode
}
// Returns a list of country codes for a calling code in descending
// order of population.
func countryCodes(fromCallingCode callingCode: String) -> [String] {
guard let callingCode = callingCode.nilIfEmpty else {
return []
}
if let cachedValue = countryCodesFromCallingCodeCache[callingCode] {
return cachedValue
}
let countryCodes: [String] = Self.countryCodesSortedByPopulationDescending.compactMap { (countryCode: String) -> String? in
let callingCodeForCountryCode: String? = self.callingCode(fromCountryCode: countryCode)
guard callingCode == callingCodeForCountryCode else {
return nil
}
return countryCode
}
countryCodesFromCallingCodeCache[callingCode] = countryCodes
return countryCodes
}
func format(phoneNumber: NBPhoneNumber, numberFormat: NBEPhoneNumberFormat) throws -> String {
try nbPhoneNumberUtil.format(phoneNumber, numberFormat: numberFormat)
}
func parse(_ numberToParse: String, defaultRegion: String) throws -> NBPhoneNumber {
let hashKey = "numberToParse:\(numberToParse), defaultRegion:\(defaultRegion)"
if let cachedValue = parsedPhoneNumberCache[hashKey] {
return cachedValue
}
let result = try nbPhoneNumberUtil.parse(numberToParse, defaultRegion: defaultRegion)
parsedPhoneNumberCache[hashKey] = result
return result
}
func examplePhoneNumber(forCountryCode countryCode: String) -> String? {
// Signal users are very likely using mobile devices, so prefer that kind of example.
do {
func findExamplePhoneNumber() -> NBPhoneNumber? {
if let nbPhoneNumber = PhoneNumberUtil.getExampleNumber(forType: countryCode,
type: .MOBILE,
nbPhoneNumberUtil: nbPhoneNumberUtil) {
return nbPhoneNumber
}
if let nbPhoneNumber = PhoneNumberUtil.getExampleNumber(forType: countryCode,
type: .FIXED_LINE_OR_MOBILE,
nbPhoneNumberUtil: nbPhoneNumberUtil) {
return nbPhoneNumber
}
return nil
}
guard let nbPhoneNumber = findExamplePhoneNumber() else {
if CurrentAppContext().isRunningTests {
Logger.warn("Could not find example phone number for: \(countryCode)")
} else {
owsFailDebug("Could not find example phone number for: \(countryCode)")
}
return nil
}
return try nbPhoneNumberUtil.format(nbPhoneNumber, numberFormat: .E164)
} catch {
if CurrentAppContext().isRunningTests {
Logger.warn("Error: \(error)")
} else {
owsFailDebug("Error: \(error)")
}
return nil
}
}
func isPossibleNumber(_ number: NBPhoneNumber) -> Bool {
nbPhoneNumberUtil.isPossibleNumber(number)
}
func countryCodeByCarrier() -> String {
nbPhoneNumberUtil.countryCodeByCarrier()
}
func getRegionCodeForCountryCode(_ countryCallingCode: NSNumber) -> String? {
nbPhoneNumberUtil.getRegionCode(forCountryCode: countryCallingCode)
}
func isValidNumber(_ number: NBPhoneNumber) -> Bool {
nbPhoneNumberUtil.isValidNumber(number)
}
func getCountryCode(forRegion regionCode: String) -> NSNumber {
nbPhoneNumberUtil.getCountryCode(forRegion: regionCode)
}
}
// MARK: -
@objc
extension PhoneNumberUtil {
public static let countryCodesSortedByPopulationDescending: [String] = [
private static let unfairLock = UnfairLock()
private var unfairLock: UnfairLock { Self.unfairLock }
@objc(callingCodeFromCountryCode:)
public static func callingCode(fromCountryCode countryCode: String) -> String? {
shared.callingCode(fromCountryCode: countryCode)
}
@objc(callingCodeFromCountryCode:)
public func callingCode(fromCountryCode countryCode: String) -> String? {
unfairLock.withLock {
phoneNumberUtilWrapper.callingCode(fromCountryCode: countryCode)
}
}
@objc(countryCodesFromCallingCode:)
public static func countryCodes(fromCallingCode callingCode: String) -> [String] {
shared.countryCodes(fromCallingCode: callingCode)
}
// Returns a list of country codes for a calling code in descending
// order of population.
@objc(countryCodesFromCallingCode:)
public func countryCodes(fromCallingCode callingCode: String) -> [String] {
unfairLock.withLock {
phoneNumberUtilWrapper.countryCodes(fromCallingCode: callingCode)
}
}
public func format(_ phoneNumber: NBPhoneNumber, numberFormat: NBEPhoneNumberFormat) throws -> String {
try unfairLock.withLock {
try phoneNumberUtilWrapper.format(phoneNumber: phoneNumber, numberFormat: numberFormat)
}
}
public func parse(_ numberToParse: String, defaultRegion: String) throws -> NBPhoneNumber {
try unfairLock.withLock {
try phoneNumberUtilWrapper.parse(numberToParse, defaultRegion: defaultRegion)
}
}
public func examplePhoneNumber(forCountryCode countryCode: String) -> String? {
unfairLock.withLock {
phoneNumberUtilWrapper.examplePhoneNumber(forCountryCode: countryCode)
}
}
public func isPossibleNumber(_ number: NBPhoneNumber) -> Bool {
unfairLock.withLock {
phoneNumberUtilWrapper.isPossibleNumber(number)
}
}
public func countryCodeByCarrier() -> String {
unfairLock.withLock {
phoneNumberUtilWrapper.countryCodeByCarrier()
}
}
public func getRegionCodeForCountryCode(_ countryCallingCode: NSNumber) -> String? {
unfairLock.withLock {
phoneNumberUtilWrapper.getRegionCodeForCountryCode(countryCallingCode)
}
}
public func isValidNumber(_ number: NBPhoneNumber) -> Bool {
unfairLock.withLock {
phoneNumberUtilWrapper.isValidNumber(number)
}
}
public func getCountryCode(forRegion regionCode: String) -> NSNumber {
unfairLock.withLock {
phoneNumberUtilWrapper.getCountryCode(forRegion: regionCode)
}
}
}
// MARK: -
fileprivate extension PhoneNumberUtilWrapper {
static let countryCodesSortedByPopulationDescending: [String] = [
"CN", // 1330044000
"IN", // 1173108018
"US", // 310232863

View File

@ -29,6 +29,7 @@ extern NSNotificationName const WarmCachesNotification;
@class OWSMessagePipelineSupervisor;
@class OWSOutgoingReceiptManager;
@class OWSReceiptManager;
@class PhoneNumberUtil;
@class SDSDatabaseStorage;
@class SSKPreKeyStore;
@class SSKPreferences;
@ -112,7 +113,8 @@ extern NSNotificationName const WarmCachesNotification;
payments:(id<Payments>)payments
paymentsCurrencies:(id<PaymentsCurrencies>)paymentsCurrencies
spamChallengeResolver:(SpamChallengeResolver *)spamResolver
senderKeyStore:(SenderKeyStore *)senderKeyStore NS_DESIGNATED_INITIALIZER;
senderKeyStore:(SenderKeyStore *)senderKeyStore
phoneNumberUtil:(PhoneNumberUtil *)phoneNumberUtil NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly, class) SSKEnvironment *shared;
@ -174,6 +176,7 @@ extern NSNotificationName const WarmCachesNotification;
@property (nonatomic, readonly) id<PaymentsCurrencies> paymentsCurrenciesRef;
@property (nonatomic, readonly) SpamChallengeResolver *spamChallengeResolverRef;
@property (nonatomic, readonly) SenderKeyStore *senderKeyStoreRef;
@property (nonatomic, readonly) PhoneNumberUtil *phoneNumberUtilRef;
// This property is configured after Environment is created.
@property (atomic, nullable) id<OWSCallMessageHandler> callMessageHandlerRef;

View File

@ -55,6 +55,7 @@ static SSKEnvironment *sharedSSKEnvironment;
@property (nonatomic) id<PaymentsCurrencies> paymentsCurrenciesRef;
@property (nonatomic) SpamChallengeResolver *spamChallengeResolverRef;
@property (nonatomic) SenderKeyStore *senderKeyStoreRef;
@property (nonatomic) PhoneNumberUtil *phoneNumberUtilRef;
@end
@ -114,6 +115,7 @@ static SSKEnvironment *sharedSSKEnvironment;
paymentsCurrencies:(id<PaymentsCurrencies>)paymentsCurrencies
spamChallengeResolver:(SpamChallengeResolver *)spamResolver
senderKeyStore:(SenderKeyStore *)senderKeyStore
phoneNumberUtil:(PhoneNumberUtil *)phoneNumberUtil
{
self = [super init];
if (!self) {
@ -169,7 +171,8 @@ static SSKEnvironment *sharedSSKEnvironment;
_paymentsCurrenciesRef = paymentsCurrencies;
_spamChallengeResolverRef = spamResolver;
_senderKeyStoreRef = senderKeyStore;
_phoneNumberUtilRef = phoneNumberUtil;
return self;
}

View File

@ -89,6 +89,7 @@ NS_ASSUME_NONNULL_BEGIN
id<PaymentsCurrencies> paymentsCurrencies = [MockPaymentsCurrencies new];
SpamChallengeResolver *spamChallengeResolver = [SpamChallengeResolver new];
SenderKeyStore *senderKeyStore = [SenderKeyStore new];
PhoneNumberUtil *phoneNumberUtil = [PhoneNumberUtil new];
self = [super initWithContactsManager:contactsManager
linkPreviewManager:linkPreviewManager
@ -138,7 +139,8 @@ NS_ASSUME_NONNULL_BEGIN
payments:payments
paymentsCurrencies:paymentsCurrencies
spamChallengeResolver:spamChallengeResolver
senderKeyStore:senderKeyStore];
senderKeyStore:senderKeyStore
phoneNumberUtil:phoneNumberUtil];
if (!self) {
return nil;

View File

@ -473,6 +473,14 @@ public extension NSObject {
static var appVersion: AppVersion {
AppVersion.shared()
}
var phoneNumberUtil: PhoneNumberUtil {
SSKEnvironment.shared.phoneNumberUtilRef
}
static var phoneNumberUtil: PhoneNumberUtil {
SSKEnvironment.shared.phoneNumberUtilRef
}
}
// MARK: - Obj-C Dependencies
@ -938,6 +946,14 @@ public extension Dependencies {
static var appVersion: AppVersion {
AppVersion.shared()
}
var phoneNumberUtil: PhoneNumberUtil {
SSKEnvironment.shared.phoneNumberUtilRef
}
static var phoneNumberUtil: PhoneNumberUtil {
SSKEnvironment.shared.phoneNumberUtilRef
}
}
// MARK: - Swift-only Dependencies
@ -1164,3 +1180,12 @@ public extension OWSDisappearingMessagesJob {
SSKEnvironment.shared.disappearingMessagesJobRef
}
}
// MARK: -
@objc
public extension PhoneNumberUtil {
static var shared: PhoneNumberUtil {
SSKEnvironment.shared.phoneNumberUtilRef
}
}

View File

@ -4,6 +4,7 @@
#import "PhoneNumberUtil.h"
#import "SSKBaseTestObjC.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>
@interface PhoneNumberUtilTest : SSKBaseTestObjC

View File

@ -0,0 +1,193 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import XCTest
import SignalServiceKit
class PhoneNumberUtilTestSwift: SSKBaseTestSwift {
func test_callingCodeFromCountryCode() {
XCTAssertEqual("+1", phoneNumberUtil.callingCode(fromCountryCode: "US"))
XCTAssertEqual("+598", phoneNumberUtil.callingCode(fromCountryCode: "UY"))
XCTAssertEqual("+0", phoneNumberUtil.callingCode(fromCountryCode: "QG"))
XCTAssertEqual("+0", phoneNumberUtil.callingCode(fromCountryCode: ""))
XCTAssertEqual("+0", phoneNumberUtil.callingCode(fromCountryCode: "+"))
XCTAssertEqual("+0", phoneNumberUtil.callingCode(fromCountryCode: "9"))
XCTAssertEqual("+1", phoneNumberUtil.callingCode(fromCountryCode: "US "))
}
func test_countryCodesFromCallingCode() {
// Order matters here.
XCTAssertEqual(["US", "CA", "DO", "PR", "JM", "TT", "BS", "BB", "LC", "GU", "VI", "GD", "VC", "AG", "DM", "BM", "AS", "MP", "KN", "KY", "SX", "VG", "TC", "AI", "MS", "UM"], phoneNumberUtil.countryCodes(fromCallingCode: "+1"))
XCTAssertEqual(["GB", "JE", "IM", "GG"], phoneNumberUtil.countryCodes(fromCallingCode: "+44"))
XCTAssertEqual(["UY"], phoneNumberUtil.countryCodes(fromCallingCode: "+598"))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: "+7945"))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: "+"))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: ""))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: " "))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: "a"))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: "++598"))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: "+1 "))
XCTAssertEqual([], phoneNumberUtil.countryCodes(fromCallingCode: " +1"))
}
func test_parse() {
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "US").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "GB").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "UY").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+441752395464", defaultRegion: "US").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+441752395464", defaultRegion: "GB").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+441752395464", defaultRegion: "UY").countryCode)
// Invalid regions.
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "UK").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+441752395464", defaultRegion: "UK").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "ZQ").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "99").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: "+").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+13334445555", defaultRegion: " ").countryCode)
// Invalid phone numbers.
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("+1333444555", defaultRegion: "UY").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+44175239546", defaultRegion: "UY").countryCode)
XCTAssertEqual(NSNumber(value: 44), try! phoneNumberUtil.parse("+441752395468 ", defaultRegion: "UY").countryCode)
XCTAssertEqual(NSNumber(value: 1), try! phoneNumberUtil.parse("++13334445555 ", defaultRegion: "UY").countryCode)
do {
_ = try phoneNumberUtil.parse("+9764", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse("", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse(" ", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse("a", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse("+", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse("9876543210987654321098765432109876543210", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
do {
_ = try phoneNumberUtil.parse("+9876543210987654321098765432109876543210", defaultRegion: "US")
XCTFail("Missing expected error.")
} catch {
// Error is expected.
}
}
func test_format() {
let phoneNumber1 = try! phoneNumberUtil.parse("+13334445555", defaultRegion: "US")
let phoneNumber2 = try! phoneNumberUtil.parse("+441752395464", defaultRegion: "US")
XCTAssertEqual("+13334445555", try! phoneNumberUtil.format(phoneNumber1, numberFormat: .E164))
XCTAssertEqual("(333) 444-5555", try! phoneNumberUtil.format(phoneNumber1, numberFormat: .NATIONAL))
XCTAssertEqual("+1 333-444-5555", try! phoneNumberUtil.format(phoneNumber1, numberFormat: .INTERNATIONAL))
XCTAssertEqual("tel:+1-333-444-5555", try! phoneNumberUtil.format(phoneNumber1, numberFormat: .RFC3966))
XCTAssertEqual("+441752395464", try! phoneNumberUtil.format(phoneNumber2, numberFormat: .E164))
XCTAssertEqual("01752 395464", try! phoneNumberUtil.format(phoneNumber2, numberFormat: .NATIONAL))
XCTAssertEqual("+44 1752 395464", try! phoneNumberUtil.format(phoneNumber2, numberFormat: .INTERNATIONAL))
XCTAssertEqual("tel:+44-1752-395464", try! phoneNumberUtil.format(phoneNumber2, numberFormat: .RFC3966))
}
func test_examplePhoneNumberForCountryCode() {
XCTAssertEqual("+12015550123", phoneNumberUtil.examplePhoneNumber(forCountryCode: "US"))
XCTAssertEqual("+447400123456", phoneNumberUtil.examplePhoneNumber(forCountryCode: "GB"))
XCTAssertEqual("+59894231234", phoneNumberUtil.examplePhoneNumber(forCountryCode: "UY"))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: "+1"))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: "44"))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: " "))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: ""))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: "UK "))
XCTAssertNil(phoneNumberUtil.examplePhoneNumber(forCountryCode: "UKK"))
}
func test_isPossibleNumber() {
let phoneNumber1 = try! phoneNumberUtil.parse("+13334445555", defaultRegion: "US")
let phoneNumber2 = try! phoneNumberUtil.parse("+441752395464", defaultRegion: "US")
// Invalid numbers.
let phoneNumber3 = try! phoneNumberUtil.parse("+44175239546", defaultRegion: "US")
let phoneNumber4 = try! phoneNumberUtil.parse("44", defaultRegion: "US")
XCTAssertEqual(true, phoneNumberUtil.isPossibleNumber(phoneNumber1))
XCTAssertEqual(true, phoneNumberUtil.isPossibleNumber(phoneNumber2))
XCTAssertEqual(true, phoneNumberUtil.isPossibleNumber(phoneNumber3))
XCTAssertEqual(false, phoneNumberUtil.isPossibleNumber(phoneNumber4))
}
func test_isValidNumber() {
let phoneNumber1 = try! phoneNumberUtil.parse("+12125556789", defaultRegion: "US")
let phoneNumber2 = try! phoneNumberUtil.parse("+441752395464", defaultRegion: "US")
let phoneNumber3 = try! phoneNumberUtil.parse("+12125556789", defaultRegion: "GB")
let phoneNumber4 = try! phoneNumberUtil.parse("+441752395464", defaultRegion: "GB")
// Invalid numbers.
let phoneNumber5 = try! phoneNumberUtil.parse("+44175239546", defaultRegion: "US")
let phoneNumber6 = try! phoneNumberUtil.parse("44", defaultRegion: "US")
let phoneNumber7 = try! phoneNumberUtil.parse("+13334445555", defaultRegion: "US")
let phoneNumber8 = try! phoneNumberUtil.parse("+44175239546", defaultRegion: "GB")
let phoneNumber9 = try! phoneNumberUtil.parse("44", defaultRegion: "GB")
let phoneNumber10 = try! phoneNumberUtil.parse("+13334445555", defaultRegion: "GB")
XCTAssertEqual(true, phoneNumberUtil.isValidNumber(phoneNumber1))
XCTAssertEqual(true, phoneNumberUtil.isValidNumber(phoneNumber2))
XCTAssertEqual(true, phoneNumberUtil.isValidNumber(phoneNumber3))
XCTAssertEqual(true, phoneNumberUtil.isValidNumber(phoneNumber4))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber5))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber6))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber7))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber8))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber9))
XCTAssertEqual(false, phoneNumberUtil.isValidNumber(phoneNumber10))
}
func test_countryCodeByCarrier() {
// This test might fail depending on how the test device/simulator is configured.
XCTAssertEqual("ZZ", phoneNumberUtil.countryCodeByCarrier())
}
func test_getRegionCodeForCountryCode() {
XCTAssertEqual("US", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: 1)))
XCTAssertEqual("GB", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: 44)))
XCTAssertEqual("UY", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: 598)))
XCTAssertEqual("ZZ", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: 0)))
XCTAssertEqual("ZZ", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: 99999)))
XCTAssertEqual("ZZ", phoneNumberUtil.getRegionCodeForCountryCode(NSNumber(value: -1)))
}
func test_getCountryCodeForRegion() {
XCTAssertEqual(NSNumber(value: 1), phoneNumberUtil.getCountryCode(forRegion: "US"))
XCTAssertEqual(NSNumber(value: 44), phoneNumberUtil.getCountryCode(forRegion: "GB"))
XCTAssertEqual(NSNumber(value: 598), phoneNumberUtil.getCountryCode(forRegion: "UY"))
// Invalid regions.
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: "UK"))
XCTAssertEqual(NSNumber(value: 1), phoneNumberUtil.getCountryCode(forRegion: "US "))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: " "))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: ""))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: "+1"))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: "ZZ"))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: "+"))
XCTAssertEqual(NSNumber(value: 0), phoneNumberUtil.getCountryCode(forRegion: "ZQ"))
}
}

View File

@ -275,15 +275,13 @@ extension FindByPhoneNumberViewController: CountryCodeViewControllerDelegate {
if let localE164 = PhoneNumber(fromE164: localNumber), let localCountryCode = localE164.getCountryCode()?.intValue {
callingCodeInt = localCountryCode
} else {
callingCodeInt = PhoneNumberUtil.sharedThreadLocal().nbPhoneNumberUtil.getCountryCode(
forRegion: PhoneNumber.defaultCountryCode()
)?.intValue
callingCodeInt = phoneNumberUtil.getCountryCode(forRegion: PhoneNumber.defaultCountryCode()).intValue
}
var callingCode: String?
if let callingCodeInt = callingCodeInt {
callingCode = COUNTRY_CODE_PREFIX + "\(callingCodeInt)"
countryCode = PhoneNumberUtil.sharedThreadLocal().probableCountryCode(forCallingCode: callingCode!)
countryCode = phoneNumberUtil.probableCountryCode(forCallingCode: callingCode!)
}
updateCountry(callingCode: callingCode, countryCode: countryCode)

View File

@ -8,6 +8,7 @@
#import <SignalMessaging/Environment.h>
#import <SignalServiceKit/PhoneNumber.h>
#import <SignalServiceKit/PhoneNumberUtil.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -183,7 +184,7 @@ NSString *const TappedStatusBarNotification = @"TappedStatusBarNotification";
OWSAssertDebug(countryCode.length > 0);
OWSAssertDebug(callingCode.length > 0);
NSString *examplePhoneNumber = [PhoneNumberUtil examplePhoneNumberForCountryCode:countryCode];
NSString *examplePhoneNumber = [self.phoneNumberUtil examplePhoneNumberForCountryCode:countryCode];
OWSAssertDebug(!examplePhoneNumber || [examplePhoneNumber hasPrefix:callingCode]);
if (examplePhoneNumber && [examplePhoneNumber hasPrefix:callingCode]) {
NSString *formattedPhoneNumber =