From 609b57c1e49133476f2e40b49488baa43210aba6 Mon Sep 17 00:00:00 2001 From: sauwming Date: Mon, 18 Mar 2024 12:08:11 +0800 Subject: [PATCH] Add Push Notification in ipjsua sample app --- .../ios/ipjsua.xcodeproj/project.pbxproj | 34 +++- .../src/pjsua/ios/ipjsua/ipjsua-Info.plist | 2 + .../src/pjsua/ios/ipjsua/ipjsua.entitlements | 8 + .../src/pjsua/ios/ipjsua/ipjsuaAppDelegate.h | 7 +- .../src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m | 158 ++++++++++++++++-- pjsip/src/pjsua-lib/pjsua_acc.c | 3 +- 6 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 pjsip-apps/src/pjsua/ios/ipjsua/ipjsua.entitlements diff --git a/pjsip-apps/src/pjsua/ios/ipjsua.xcodeproj/project.pbxproj b/pjsip-apps/src/pjsua/ios/ipjsua.xcodeproj/project.pbxproj index 4fe446aef..9a70996b7 100644 --- a/pjsip-apps/src/pjsua/ios/ipjsua.xcodeproj/project.pbxproj +++ b/pjsip-apps/src/pjsua/ios/ipjsua.xcodeproj/project.pbxproj @@ -42,6 +42,9 @@ 3AF0582816F050780046B835 /* ipjsuaViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3AF0582616F050780046B835 /* ipjsuaViewController_iPad.xib */; }; 3AF253001EFBD15E00213893 /* libyuv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF252FF1EFBD15E00213893 /* libyuv.a */; }; 3AF253021EFBD36E00213893 /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF253011EFBD36E00213893 /* VideoToolbox.framework */; }; + 3AF9B5422B8890880043987D /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF9B5412B8890880043987D /* PushKit.framework */; }; + 3AF9B5462B8890F40043987D /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF9B5442B8890F40043987D /* UserNotifications.framework */; }; + 3AF9B5482BA407AD0043987D /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AF9B5472BA407AD0043987D /* CallKit.framework */; }; 7485A6AF1F09AAE500122F1A /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7485A6AE1F09AAE500122F1A /* Reachability.m */; }; 7485A6B11F09B2D500122F1A /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7485A6B01F09B2D500122F1A /* SystemConfiguration.framework */; }; E5E991E61B67A45500017E67 /* libg7221codec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E991D41B67A45500017E67 /* libg7221codec.a */; }; @@ -105,6 +108,11 @@ 3AF0582716F050780046B835 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ipjsuaViewController_iPad.xib; sourceTree = ""; }; 3AF252FF1EFBD15E00213893 /* libyuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libyuv.a; sourceTree = ""; }; 3AF253011EFBD36E00213893 /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; + 3AF9B5402B88896D0043987D /* ipjsua.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ipjsua.entitlements; sourceTree = ""; }; + 3AF9B5412B8890880043987D /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; + 3AF9B5432B8890F40043987D /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; + 3AF9B5442B8890F40043987D /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; + 3AF9B5472BA407AD0043987D /* CallKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CallKit.framework; path = System/Library/Frameworks/CallKit.framework; sourceTree = SDKROOT; }; 7485A6AD1F09AAE500122F1A /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 7485A6AE1F09AAE500122F1A /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 7485A6B01F09B2D500122F1A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; @@ -138,6 +146,7 @@ 3AF253021EFBD36E00213893 /* VideoToolbox.framework in Frameworks */, E5E991EC1B67A45500017E67 /* libpjmedia-codec.a in Frameworks */, 3AA31FF818F3FB4C00112C3D /* CFNetwork.framework in Frameworks */, + 3AF9B5422B8890880043987D /* PushKit.framework in Frameworks */, E5E991E61B67A45500017E67 /* libg7221codec.a in Frameworks */, E5E991EB1B67A45500017E67 /* libpjmedia-audiodev.a in Frameworks */, 3AA31FFB18F3FB4C00112C3D /* CoreImage.framework in Frameworks */, @@ -152,8 +161,10 @@ E5E991EE1B67A45500017E67 /* libpjmedia.a in Frameworks */, E5E991EA1B67A45500017E67 /* libpjlib-util.a in Frameworks */, E5E991ED1B67A45500017E67 /* libpjmedia-videodev.a in Frameworks */, + 3AF9B5462B8890F40043987D /* UserNotifications.framework in Frameworks */, E5E991E81B67A45500017E67 /* libilbccodec.a in Frameworks */, 3A4E3B5B2B6205BA0016735C /* Network.framework in Frameworks */, + 3AF9B5482BA407AD0043987D /* CallKit.framework in Frameworks */, 3AA3200118F3FB4C00112C3D /* CoreGraphics.framework in Frameworks */, 3AA31FF918F3FB4C00112C3D /* CoreAudio.framework in Frameworks */, 3AA31FFD18F3FB4C00112C3D /* CoreVideo.framework in Frameworks */, @@ -211,6 +222,10 @@ 3AF0580716F050770046B835 /* Frameworks */ = { isa = PBXGroup; children = ( + 3AF9B5472BA407AD0043987D /* CallKit.framework */, + 3AF9B5442B8890F40043987D /* UserNotifications.framework */, + 3AF9B5432B8890F40043987D /* UserNotificationsUI.framework */, + 3AF9B5412B8890880043987D /* PushKit.framework */, 3A4E3B5A2B6205B90016735C /* Network.framework */, 3A4E3B522B61FEAB0016735C /* Metal.framework */, 3A4E3B532B61FEAB0016735C /* MetalKit.framework */, @@ -237,6 +252,7 @@ 3AF0580E16F050770046B835 /* ipjsua */ = { isa = PBXGroup; children = ( + 3AF9B5402B88896D0043987D /* ipjsua.entitlements */, 3A92068D16F1D1A100D49F96 /* pjsua */, 3AF0581716F050780046B835 /* ipjsuaAppDelegate.h */, 3AF0581816F050780046B835 /* ipjsuaAppDelegate.m */, @@ -513,7 +529,9 @@ 3AF0582C16F050780046B835 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_ENTITLEMENTS = ipjsua/ipjsua.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 93NHJQ455P; @@ -545,10 +563,11 @@ "$(PROJECT_DIR)", ); ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.teluu.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_BUNDLE_IDENTIFIER = "com.teluupush.--PRODUCT-NAME-rfc1034identifier-"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.teluupush.ipjsua; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Teluu Profile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "ipjsua Push"; VALID_ARCHS = arm64; WRAPPER_EXTENSION = app; }; @@ -557,7 +576,9 @@ 3AF0582D16F050780046B835 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_ENTITLEMENTS = ipjsua/ipjsua.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 93NHJQ455P; @@ -588,10 +609,11 @@ "$(PROJECT_DIR)", ); ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.teluu.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_BUNDLE_IDENTIFIER = "com.teluupush.--PRODUCT-NAME-rfc1034identifier-"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.teluupush.ipjsua; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Teluu Profile"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "ipjsua Push"; VALID_ARCHS = arm64; WRAPPER_EXTENSION = app; }; diff --git a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua-Info.plist b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua-Info.plist index e4aa56d77..1b8f68bb4 100644 --- a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua-Info.plist +++ b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua-Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion en + AppIdentifierPrefix + $(AppIdentifierPrefix) CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable diff --git a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua.entitlements b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsua.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.h b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.h index 7f01d2759..f4c5e6316 100644 --- a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.h +++ b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.h @@ -18,14 +18,19 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#import #import +#import @class ipjsuaViewController; -@interface ipjsuaAppDelegate : UIResponder +@interface ipjsuaAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; +@property (strong, nonatomic) PKPushRegistry *voipRegistry; +@property (strong, nonatomic) NSMutableString *token; + @property (strong, nonatomic) ipjsuaViewController *viewController; @end diff --git a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m index 1d56c8246..dec02b1fe 100644 --- a/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m +++ b/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m @@ -22,6 +22,7 @@ #import #import #import +#import #include "../../pjsua_app.h" #include "../../pjsua_app_config.h" @@ -33,6 +34,14 @@ #define THIS_FILE "ipjsuaAppDelegate.m" +/* Specify if we use push notification. */ +#define USE_PUSH_NOTIFICATION 1 + +/* Account details. */ +#define SIP_DOMAIN "sip.pjsip.org" +#define SIP_USER "test" +#define SIP_PASSWD "test" + #define KEEP_ALIVE_INTERVAL 600 ipjsuaAppDelegate *app; @@ -48,18 +57,18 @@ Reachability *internetReach; BOOL connectionRequired = [curReach connectionRequired]; switch (netStatus) { case NotReachable: - PJ_LOG(3,("", "Access Not Available..")); + NSLog(@"Access Not Available.."); connectionRequired= NO; break; case ReachableViaWiFi: - PJ_LOG(3,("", "Reachable WiFi..")); + NSLog(@"Reachable WiFi.."); break; case ReachableViaWWAN: - PJ_LOG(3,("", "Reachable WWAN..")); + NSLog(@"Reachable WWAN.."); break; } if (connectionRequired) { - PJ_LOG(3,("", "Connection Required")); + NSLog(@"Connection Required"); } } @@ -68,7 +77,7 @@ Reachability *internetReach; { Reachability* curReach = [note object]; NSParameterAssert([curReach isKindOfClass: [Reachability class]]); - PJ_LOG(3,("", "reachability changed..")); + NSLog(@"reachability changed.."); [self updateWithReachability: curReach]; if ([curReach currentReachabilityStatus] != NotReachable && @@ -76,10 +85,28 @@ Reachability *internetReach; { pjsua_ip_change_param param; pjsua_ip_change_param_default(¶m); - pjsua_handle_ip_change(¶m); +// pjsua_handle_ip_change(¶m); } } +- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type +{ + if ([credentials.token length] == 0) { + NSLog(@"voip token NULL"); + return; + } + + /* Get device token */ + const char *data = [credentials.token bytes]; + self.token = [NSMutableString string]; + for (NSUInteger i = 0; i < [credentials.token length]; i++) { + [self.token appendFormat:@"%02.2hhx", data[i]]; + } + NSLog(@"== VOIP Push Notification Token: %@ ==", self.token); + + /* Now start pjsua */ + [NSThread detachNewThreadSelector:@selector(pjsuaStart) toTarget:self withObject:nil]; +} void displayLog(const char *msg, int len) { @@ -171,6 +198,53 @@ static void pjsuaOnAppConfigCb(pjsua_app_config *cfg) object:[UIDevice currentDevice]]; }); + static char contact_uri_buf[1024]; + pjsua_acc_config cfg; + + pjsua_acc_config_default(&cfg); + cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN); + cfg.reg_uri = pj_str("sip:" SIP_DOMAIN ";transport=tcp"); + cfg.cred_count = 1; + cfg.cred_info[0].realm = pj_str(SIP_DOMAIN); + cfg.cred_info[0].scheme = pj_str("digest"); + cfg.cred_info[0].username = pj_str(SIP_USER); + cfg.cred_info[0].data = pj_str(SIP_PASSWD); + + /* If we have Push Notification token, put it in the registration + * Contact URI params. + */ + if ([self.token length]) { + /* According to RFC 8599: + * - pn-provider is the Push Notification Service provider. Here + * we use APNS (Apple Push Notification Service). + * - pn-param is composed of Team ID and Topic separated by + * a period (.). + * The Topic consists of the Bundle ID, the app's unique ID, + * and the app's service value ("voip" for VoIP apps), separated + * by a period (.). + * - pn-prid is the PN token. + */ + NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; + NSString *bundleID = infoDictionary[@"CFBundleIdentifier"]; + NSString *teamID = infoDictionary[@"AppIdentifierPrefix"]; + NSLog(@"Team ID from settings: %@", teamID); + + pj_ansi_snprintf(contact_uri_buf, sizeof(contact_uri_buf), + ";pn-provider=apns" + ";pn-param=%s%s.voip" + ";pn-prid=%s", + (teamID? [teamID UTF8String]: "93NHJQ455P."), + [bundleID UTF8String], + [self.token UTF8String]); + cfg.reg_contact_uri_params = pj_str(contact_uri_buf); + } + status = pjsua_acc_add(&cfg, PJ_TRUE, NULL); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + displayMsg(errmsg); + } + status = pjsua_app_run(PJ_TRUE); if (status != PJ_SUCCESS) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -187,6 +261,42 @@ static void pjsuaOnAppConfigCb(pjsua_app_config *cfg) restartArgc = 0; } +- (void)reportIncomingCall { + CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; + + /* Report the incoming call to the system using CallKit. */ + CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] + initWithLocalizedName:@"ipjsua"]; + CXProvider *provider = [[CXProvider alloc] + initWithConfiguration:configuration]; + + [provider reportNewIncomingCallWithUUID:[NSUUID UUID] update:callUpdate + completion:^(NSError * _Nullable error) + { + if (error) { + NSLog(@"Error reporting incoming call: %@", + error.localizedDescription); + } else { + NSLog(@"Incoming call reported successfully"); + } + }]; +} + +- (void)pushRegistry:(PKPushRegistry *)registry + didReceiveIncomingPushWithPayload:(PKPushPayload *)payload + forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion +{ + /* Handle incoming VoIP push notification. */ + NSLog(@"Receiving push notification"); + /* Re-register. */ + [self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:YES]; + /* Report the incoming call via CallKit. */ + [self reportIncomingCall]; + + /* Call the completion handler when you have finished processing the incoming call. */ + completion(); +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; @@ -198,7 +308,31 @@ static void pjsuaOnAppConfigCb(pjsua_app_config *cfg) } self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; - + +#if USE_PUSH_NOTIFICATION + /* Set up a push notification registry for Voice over IP (VoIP). */ + self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; + self.voipRegistry.delegate = self; + self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; + + /* Request permission for push notifications. */ + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = self; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | + UNAuthorizationOptionBadge | + UNAuthorizationOptionSound) + completionHandler:^(BOOL granted, NSError * _Nullable error) + { + NSLog(@"Notification request %sgranted", (granted ? "" : "not")); + + if (granted) { + dispatch_async(dispatch_get_main_queue(), ^{ + [application registerForRemoteNotifications]; + }); + } + }]; +#endif + /* Observe the kNetworkReachabilityChangedNotification. When that * notification is posted, the method "reachabilityChanged" will be called. */ @@ -212,8 +346,12 @@ static void pjsuaOnAppConfigCb(pjsua_app_config *cfg) app = self; - /* Start pjsua app thread */ +#if !USE_PUSH_NOTIFICATION + /* Start pjsua app thread immediately, otherwise we do it after push + * notification setup completes. + */ [NSThread detachNewThreadSelector:@selector(pjsuaStart) toTarget:self withObject:nil]; +#endif return YES; } @@ -275,10 +413,6 @@ static void pjsuaOnAppConfigCb(pjsua_app_config *cfg) pj_thread_register("ipjsua", a_thread_desc, &a_thread); } - /* Since iOS requires that the minimum keep alive interval is 600s, - * application needs to make sure that the account's registration - * timeout is long enough. - */ for (i = 0; i < (int)pjsua_acc_get_count(); ++i) { if (pjsua_acc_is_valid(i)) { pjsua_acc_set_registration(i, PJ_TRUE); diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 40d10765a..b9e5c6543 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -1597,7 +1597,8 @@ done: acc->cfg.reg_contact_params.slen + acc->cfg.reg_contact_uri_params.slen + (need_outbound? - (acc->rfc5626_instprm.slen + acc->rfc5626_regprm.slen): 0); + (acc->rfc5626_instprm.slen + acc->rfc5626_regprm.slen): 0) + + 5; /* allowance */ if (len > acc->contact.slen) { reg_contact.ptr = (char*) pj_pool_alloc(acc->pool, len);