So for about 4 years in our enterprise environment we were happily using VOIP push notifications to remotely access user's tablets for maintenance and remote data repair purposes. Unlike regular APNS, VOIP push notifications would access the app even if weren't running. I should have figured apple would kill it sometime.
Is anyone aware of a private API to bypass the pushkit requirement to call reportNewIncomingCallWithUUID which brings about a full screen call UI, or another mechanism that I can't think of to access an app in the background even if killed - Notification Service Extensions won't work (I believe) because it will only work for screen messages. Thanks
If you are not going to publish it to the Apple store, and don't care about the use of private API (which can change anytime, breaking your code), you can use method swizzling to change the implementation of the function that's called by the system to crash the app.
In my case, I have a project with swift and objc interoperability. I did it this way:
PKPushRegistry+PKPushFix_m.h
with the contents of this gist.Other options are building it with Xcode 10, or manually overriding iOS SDK 13 from an Xcode 11 with a copy of iOS SDK 12.
Here's the content of the gist, in case it becomes unavailable in the future:
#import <objc/runtime.h>
#import <PushKit/PushKit.h>
@interface PKPushRegistry (Fix)
@end
@implementation PKPushRegistry (Fix)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(_terminateAppIfThereAreUnhandledVoIPPushes);
SEL swizzledSelector = @selector(doNotCrash);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
[super load];
});
}
#pragma mark - Method Swizzling
- (void)doNotCrash {
NSLog(@"Unhandled VoIP Push");
}
@end
This is brilliant - and uses the fabled swizzling - a technique I learned about for technicals but never actually used myself. Thanks a million. My only question is whether this bypasses the total push block that Apple threatens if pushes keep being received but don't call reportNewIncomingCallWithUUID?
@Nostradamus I guess the method
_terminateAppIfThereAreUnhandledVoIPPushes
is also responsible for this push block. From what I've experienced, this block would already happen after the 2nd or 3rd crash, and would only last for around 24 hours (uninstalling and installing the app would also reset the block). I didn't test this enough to be able to answer with certainty, as in my case the app needs to be distributed in the AppStore, but I believe that yes, it bypasses it.Duh - sorry, should have seen that!Thanks for the quick response. I've been setting the ring volume to zero programmatically and killing the call ASAP, which was usable but this will work much better. Thanks again.
Works fine, thank you!