From 3d59efc2cd71b0382d223a731ce8259ccc284775 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 21 Oct 2021 16:18:42 -0300 Subject: [PATCH 1/6] Add PhoneNumberUtil singleton. --- SignalMessaging/environment/AppSetup.m | 6 +- .../src/Contacts/PhoneNumberUtil.h | 19 +-- .../src/Contacts/PhoneNumberUtil.m | 99 +------------ .../src/Contacts/PhoneNumberUtil.swift | 133 +++++++++++++++++- SignalServiceKit/src/SSKEnvironment.h | 5 +- SignalServiceKit/src/SSKEnvironment.m | 5 +- .../src/TestUtils/MockSSKEnvironment.m | 4 +- .../src/Util/Dependencies+SSK.swift | 25 ++++ 8 files changed, 180 insertions(+), 116 deletions(-) diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index 01458ba61e..ab15b9f9b4 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -114,7 +114,8 @@ NS_ASSUME_NONNULL_BEGIN id 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(); diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h index 56213df56d..bdf22529a5 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h @@ -7,24 +7,20 @@ NS_ASSUME_NONNULL_BEGIN +@class PhoneNumberUtilWrapper; +@class UnfairLock; + @interface PhoneNumberUtil : NSObject -@property (nonatomic, retain) NBPhoneNumberUtil *nbPhoneNumberUtil; - -+ (instancetype)new NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedThreadLocal; +// These properties should only be accessed by Swift. +@property (nonatomic, readonly) UnfairLock *unfairLock; +@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 *)countryCodesFromCallingCode:(NSString *)callingCode; // Returns the most likely country code for a calling code based on population. - (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode; @@ -36,9 +32,6 @@ NS_ASSUME_NONNULL_BEGIN + (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; @end diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m index d76d0fa5e9..4fd20740d3 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m @@ -12,7 +12,6 @@ NS_ASSUME_NONNULL_BEGIN @interface PhoneNumberUtil () -@property (nonatomic, readonly) NSMutableDictionary *countryCodesFromCallingCodeCache; @property (nonatomic, readonly) AnyLRUCache *parsedPhoneNumberCache; @end @@ -21,23 +20,12 @@ NS_ASSUME_NONNULL_BEGIN @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]; + _unfairLock = [UnfairLock new]; + _phoneNumberUtilWrapper = [PhoneNumberUtilWrapper new]; _parsedPhoneNumberCache = [[AnyLRUCache alloc] initWithMaxSize:256 nseMaxSize:0 shouldEvacuateInBackground:NO]; } @@ -75,13 +63,6 @@ NS_ASSUME_NONNULL_BEGIN } } -- (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 +83,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 *)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); diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift index e301687b44..fc85661727 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift @@ -4,9 +4,140 @@ import Foundation +@objc +public class PhoneNumberUtilWrapper: NSObject { + +// private let unfairLock = UnfairLock() + + fileprivate let nbPhoneNumberUtil = NBPhoneNumberUtil() + fileprivate var countryCodesFromCallingCodeCache = [String: [String]]() +// private let parsedPhoneNumberCache = LRUCache(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" + } + + guard let countryCallingCode = nbPhoneNumberUtil.getCountryCode(forRegion: countryCode) else { + owsFailDebug("Unknown value: \(countryCode).") + return nil + } + 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) + } +} + +// MARK: - + @objc extension PhoneNumberUtil { - public static let countryCodesSortedByPopulationDescending: [String] = [ + @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) + } + } +} + +// MARK: - + +fileprivate extension PhoneNumberUtilWrapper { + static let countryCodesSortedByPopulationDescending: [String] = [ "CN", // 1330044000 "IN", // 1173108018 "US", // 310232863 diff --git a/SignalServiceKit/src/SSKEnvironment.h b/SignalServiceKit/src/SSKEnvironment.h index 408ce1773f..025fb1f668 100644 --- a/SignalServiceKit/src/SSKEnvironment.h +++ b/SignalServiceKit/src/SSKEnvironment.h @@ -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 paymentsCurrencies:(id)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 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 callMessageHandlerRef; diff --git a/SignalServiceKit/src/SSKEnvironment.m b/SignalServiceKit/src/SSKEnvironment.m index 0e606235fd..06e0da50ae 100644 --- a/SignalServiceKit/src/SSKEnvironment.m +++ b/SignalServiceKit/src/SSKEnvironment.m @@ -55,6 +55,7 @@ static SSKEnvironment *sharedSSKEnvironment; @property (nonatomic) id paymentsCurrenciesRef; @property (nonatomic) SpamChallengeResolver *spamChallengeResolverRef; @property (nonatomic) SenderKeyStore *senderKeyStoreRef; +@property (nonatomic) PhoneNumberUtil *phoneNumberUtilRef; @end @@ -114,6 +115,7 @@ static SSKEnvironment *sharedSSKEnvironment; paymentsCurrencies:(id)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; } diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m index 4fa68d770c..182628f3ad 100644 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m +++ b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m @@ -89,6 +89,7 @@ NS_ASSUME_NONNULL_BEGIN id 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; diff --git a/SignalServiceKit/src/Util/Dependencies+SSK.swift b/SignalServiceKit/src/Util/Dependencies+SSK.swift index ad61ecc21d..549dea4139 100644 --- a/SignalServiceKit/src/Util/Dependencies+SSK.swift +++ b/SignalServiceKit/src/Util/Dependencies+SSK.swift @@ -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 + } +} From 8e5538d5d5302c7da2b20ee1e5d29a5fd89f72b9 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 21 Oct 2021 16:55:20 -0300 Subject: [PATCH 2/6] Add PhoneNumberUtil singleton. --- SignalServiceKit/src/Contacts/PhoneNumber.h | 2 +- SignalServiceKit/src/Contacts/PhoneNumber.m | 22 ++--- .../src/Contacts/PhoneNumberUtil.h | 7 +- .../src/Contacts/PhoneNumberUtil.m | 66 ++------------- .../src/Contacts/PhoneNumberUtil.swift | 84 ++++++++++++++++++- 5 files changed, 102 insertions(+), 79 deletions(-) diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.h b/SignalServiceKit/src/Contacts/PhoneNumber.h index bcd7f03917..5f49f63e8c 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.h +++ b/SignalServiceKit/src/Contacts/PhoneNumber.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN withSpecifiedCountryCodeString:(NSString *)countryCodeString; + (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber; -+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString; +//+ (nullable NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString; - (NSURL *)toSystemDialerURL; - (NSString *)toE164; diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m index 9b264be1ca..eb08923be2 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ b/SignalServiceKit/src/Contacts/PhoneNumber.m @@ -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,12 +210,10 @@ 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 { OWSAssertDebug(text != nil); @@ -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]; diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h index bdf22529a5..33b711cc5f 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h @@ -2,7 +2,6 @@ // Copyright (c) 2021 Open Whisper Systems. All rights reserved. // -#import #import NS_ASSUME_NONNULL_BEGIN @@ -29,9 +28,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; ++ (nullable NBPhoneNumber *)getExampleNumberForType:(NSString *)regionCode + type:(NBEPhoneNumberType)type + nbPhoneNumberUtil:(NBPhoneNumberUtil *)nbPhoneNumberUtil; @end diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m index 4fd20740d3..f8781eada5 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m @@ -10,14 +10,6 @@ NS_ASSUME_NONNULL_BEGIN -@interface PhoneNumberUtil () - -@property (nonatomic, readonly) AnyLRUCache *parsedPhoneNumberCache; - -@end - -#pragma mark - - @implementation PhoneNumberUtil - (instancetype)init { @@ -26,43 +18,11 @@ NS_ASSUME_NONNULL_BEGIN if (self) { _unfairLock = [UnfairLock new]; _phoneNumberUtilWrapper = [PhoneNumberUtilWrapper new]; - _parsedPhoneNumberCache = [[AnyLRUCache alloc] initWithMaxSize:256 nseMaxSize:0 shouldEvacuateInBackground:NO]; } 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; - } -} - // country code -> country name + (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode { @@ -220,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 diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift index fc85661727..a3ed301b62 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift @@ -3,6 +3,7 @@ // import Foundation +import libPhoneNumber_iOS @objc public class PhoneNumberUtilWrapper: NSObject { @@ -11,7 +12,7 @@ public class PhoneNumberUtilWrapper: NSObject { fileprivate let nbPhoneNumberUtil = NBPhoneNumberUtil() fileprivate var countryCodesFromCallingCodeCache = [String: [String]]() -// private let parsedPhoneNumberCache = LRUCache(maxSize: 256, nseMaxSize: 0, shouldEvacuateInBackground: false) + fileprivate let parsedPhoneNumberCache = LRUCache(maxSize: 256, nseMaxSize: 0, shouldEvacuateInBackground: false) } // MARK: - @@ -95,6 +96,55 @@ fileprivate extension PhoneNumberUtilWrapper { 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 { + owsFailDebug("Could not find example phone number for: \(countryCode)") + return nil + } + return try nbPhoneNumberUtil.format(nbPhoneNumber, numberFormat: .E164) + } catch { + owsFailDebug("Error: \(error)") + return nil + } + } + + func isPossibleNumber(_ number: NBPhoneNumber) -> Bool { + nbPhoneNumberUtil.isPossibleNumber(number) + } + + func countryCodeByCarrier() -> String { + nbPhoneNumberUtil.countryCodeByCarrier() + } + + func getRegionCodeForCountryCode(_ countryCallingCode: NSNumber) -> String? { + nbPhoneNumberUtil.getCountryCode(forRegion: countryCallingCode) + } } // MARK: - @@ -127,11 +177,41 @@ extension PhoneNumberUtil { } } - public func format(phoneNumber: NBPhoneNumber, numberFormat: NBEPhoneNumberFormat) throws -> String { + 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) + } + } } // MARK: - From 007a00a17b039378f5e7b1409680ae2f063a74a6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 21 Oct 2021 17:29:44 -0300 Subject: [PATCH 3/6] Add PhoneNumberUtil singleton. --- ...eteAccountConfirmationViewController.swift | 6 ++--- .../Registration/OnboardingController.swift | 2 +- .../OnboardingPhoneNumberViewController.swift | 3 +-- SignalServiceKit/src/Contacts/PhoneNumber.h | 2 -- SignalServiceKit/src/Contacts/PhoneNumber.m | 18 ++++++------- .../src/Contacts/PhoneNumberUtil.swift | 27 +++++++++++++++---- .../tests/Contacts/PhoneNumberUtilTest.m | 1 + .../FindByPhoneNumberViewController.swift | 6 ++--- .../ViewControllers/ViewControllerUtils.m | 3 ++- 9 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift b/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift index e77be17fed..dda2320f5a 100644 --- a/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Account/DeleteAccountConfirmationViewController.swift @@ -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) diff --git a/Signal/src/ViewControllers/Registration/OnboardingController.swift b/Signal/src/ViewControllers/Registration/OnboardingController.swift index db4d25dffa..fb12dc7671 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingController.swift @@ -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.") diff --git a/Signal/src/ViewControllers/Registration/OnboardingPhoneNumberViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingPhoneNumberViewController.swift index bd85053812..3b12b08fca 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingPhoneNumberViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingPhoneNumberViewController.swift @@ -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 diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.h b/SignalServiceKit/src/Contacts/PhoneNumber.h index 5f49f63e8c..fecd4712af 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.h +++ b/SignalServiceKit/src/Contacts/PhoneNumber.h @@ -33,8 +33,6 @@ NS_ASSUME_NONNULL_BEGIN withSpecifiedCountryCodeString:(NSString *)countryCodeString; + (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber; -//+ (nullable NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString; - - (NSURL *)toSystemDialerURL; - (NSString *)toE164; - (nullable NSNumber *)getCountryCode; diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m index eb08923be2..3d7e94ecf9 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ b/SignalServiceKit/src/Contacts/PhoneNumber.m @@ -210,10 +210,10 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN return formattedPhoneNumber; } -//+ (nullable NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString { -// NSNumber *countryCallingCode = @([[countryCodeString substringFromIndex:1] integerValue]); -// return [self.phoneNumberUtil getRegionCodeForCountryCode:countryCallingCode]; -//} ++ (nullable NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString { + NSNumber *countryCallingCode = @([[countryCodeString substringFromIndex:1] integerValue]); + return [self.phoneNumberUtil getRegionCodeForCountryCode:countryCallingCode]; +} + (nullable PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text { OWSAssertDebug(text != nil); @@ -392,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); } @@ -583,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; @@ -596,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 { diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift index a3ed301b62..80ebb5c27b 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift @@ -65,10 +65,7 @@ fileprivate extension PhoneNumberUtilWrapper { return "+1" } - guard let countryCallingCode = nbPhoneNumberUtil.getCountryCode(forRegion: countryCode) else { - owsFailDebug("Unknown value: \(countryCode).") - return nil - } + let countryCallingCode = getCountryCode(forRegion: countryCode) let callingCode = COUNTRY_CODE_PREFIX + "\(countryCallingCode.intValue)" return callingCode } @@ -143,7 +140,15 @@ fileprivate extension PhoneNumberUtilWrapper { } func getRegionCodeForCountryCode(_ countryCallingCode: NSNumber) -> String? { - nbPhoneNumberUtil.getCountryCode(forRegion: countryCallingCode) + nbPhoneNumberUtil.getRegionCode(forCountryCode: countryCallingCode) + } + + func isValidNumber(_ number: NBPhoneNumber) -> Bool { + nbPhoneNumberUtil.isValidNumber(number) + } + + func getCountryCode(forRegion regionCode: String) -> NSNumber { + nbPhoneNumberUtil.getCountryCode(forRegion: regionCode) } } @@ -212,6 +217,18 @@ extension PhoneNumberUtil { 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: - diff --git a/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.m b/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.m index 79e362328f..37cce5ab4f 100644 --- a/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.m +++ b/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.m @@ -4,6 +4,7 @@ #import "PhoneNumberUtil.h" #import "SSKBaseTestObjC.h" +#import @interface PhoneNumberUtilTest : SSKBaseTestObjC diff --git a/SignalUI/ViewControllers/FindByPhoneNumberViewController.swift b/SignalUI/ViewControllers/FindByPhoneNumberViewController.swift index ece33d80f3..3392d8da04 100644 --- a/SignalUI/ViewControllers/FindByPhoneNumberViewController.swift +++ b/SignalUI/ViewControllers/FindByPhoneNumberViewController.swift @@ -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) diff --git a/SignalUI/ViewControllers/ViewControllerUtils.m b/SignalUI/ViewControllers/ViewControllerUtils.m index a7673b2f27..cc59fa89dd 100644 --- a/SignalUI/ViewControllers/ViewControllerUtils.m +++ b/SignalUI/ViewControllers/ViewControllerUtils.m @@ -8,6 +8,7 @@ #import #import #import +#import 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 = From dade10ef84ede5381123824e3a4b974fa8bfcab3 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 22 Oct 2021 09:29:25 -0300 Subject: [PATCH 4/6] Add PhoneNumberUtil singleton. --- SignalServiceKit/src/Contacts/PhoneNumberUtil.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift index 80ebb5c27b..c068861854 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift @@ -8,8 +8,6 @@ import libPhoneNumber_iOS @objc public class PhoneNumberUtilWrapper: NSObject { -// private let unfairLock = UnfairLock() - fileprivate let nbPhoneNumberUtil = NBPhoneNumberUtil() fileprivate var countryCodesFromCallingCodeCache = [String: [String]]() fileprivate let parsedPhoneNumberCache = LRUCache(maxSize: 256, nseMaxSize: 0, shouldEvacuateInBackground: false) From 2d8dc4036c5a527311815cf62225e73763c25b4e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 22 Oct 2021 10:21:09 -0300 Subject: [PATCH 5/6] Add PhoneNumberUtilTestSwift. --- .../src/Contacts/PhoneNumberUtil.h | 4 +- .../src/Contacts/PhoneNumberUtil.m | 4 +- .../src/Contacts/PhoneNumberUtil.swift | 15 +- .../tests/Contacts/PhoneNumberUtilTest.swift | 193 ++++++++++++++++++ 4 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.swift diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h index 33b711cc5f..f8e2828fe0 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h @@ -7,12 +7,10 @@ NS_ASSUME_NONNULL_BEGIN @class PhoneNumberUtilWrapper; -@class UnfairLock; @interface PhoneNumberUtil : NSObject -// These properties should only be accessed by Swift. -@property (nonatomic, readonly) UnfairLock *unfairLock; +// This property should only be accessed by Swift. @property (nonatomic, readonly) PhoneNumberUtilWrapper *phoneNumberUtilWrapper; + (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString; diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m index f8781eada5..7c90ec5f85 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m @@ -3,7 +3,6 @@ // #import "PhoneNumberUtil.h" -#import "ContactsManagerProtocol.h" #import "FunctionalUtil.h" #import #import @@ -16,10 +15,11 @@ NS_ASSUME_NONNULL_BEGIN self = [super init]; if (self) { - _unfairLock = [UnfairLock new]; _phoneNumberUtilWrapper = [PhoneNumberUtilWrapper new]; } + OWSSingletonAssert(); + return self; } diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift index c068861854..080915450c 100644 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift +++ b/SignalServiceKit/src/Contacts/PhoneNumberUtil.swift @@ -119,12 +119,20 @@ fileprivate extension PhoneNumberUtilWrapper { return nil } guard let nbPhoneNumber = findExamplePhoneNumber() else { - owsFailDebug("Could not find example phone number for: \(countryCode)") + 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 { - owsFailDebug("Error: \(error)") + if CurrentAppContext().isRunningTests { + Logger.warn("Error: \(error)") + } else { + owsFailDebug("Error: \(error)") + } return nil } } @@ -154,6 +162,9 @@ fileprivate extension PhoneNumberUtilWrapper { @objc extension PhoneNumberUtil { + 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) diff --git a/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.swift b/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.swift new file mode 100644 index 0000000000..770b2cfa7a --- /dev/null +++ b/SignalServiceKit/tests/Contacts/PhoneNumberUtilTest.swift @@ -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")) + } +} From 01f63bcf694dbd5e385eb77d9cd81ce600d5bd9c Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 22 Oct 2021 19:07:33 -0300 Subject: [PATCH 6/6] Update Cocoapods. --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index 174145d81c..8254c8fc47 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 174145d81c55573e85e5ab22a59dd824f04ca685 +Subproject commit 8254c8fc47202f689fe18c4ccfee03a814390ea5