3

I'm working on a personal tweak for iOS. I want to disconnect/connect a phone call before the phone would show anything. I'm hooking into the initWithAlertController: method of class SBUIFullscreenAlertAdapter. Everything is okay when I just show a message that shows the incoming phone number and its name, but when i try to answer the phone call or disconnect it programmatically, it will crash and go to safe mode.

Here is my code:

@interface SBUIFullscreenAlertAdapter
- (id)initWithAlertController:(id)arg1;
@end

@interface MPIncomingPhoneCallController
{
    struct __CTCall *_incomingCall;
}
- (id) incomingCallNumber;
- (void)stopRingingOrVibrating;
- (void)answerCall:(struct __CTCall *)arg1;
@end

%hook SBUIFullscreenAlertAdapter
- (id)initWithAlertController:(id)arg1
{
    MPIncomingPhoneCallController *phoneCall = (MPIncomingPhoneCallController*)arg1;
    [phoneCall stopRingingOrVibrating];
    if([phoneCall.incomingCallNumber isEqualToString:@"+98.........."]) {
        [phoneCall answerCall:_incomingCall];
    }
    %orig;
    return self;
}
%end

The error is that it says: "Use of undeclared identifier '_incomingCall'".

How can I solve the problem? Is there a way to use a private instance variable while hooking a method? Is there a function which returns a CTCallRef* of the incoming call? Is there some other way to accomplish this?

It should be obvious that I'm coding for jailbroken iOS devices, so there is no problem with the use of private frameworks.

jscs
  • 63,694
  • 13
  • 151
  • 195
Hamed
  • 297
  • 3
  • 21

2 Answers2

4

There is much better place to do that - MPTelephonyManager -(void)displayAlertForCall:(id)call. This method is located in IncomingCall.servicebundle binary, not in SpringBoard itself. This binary is being loaded at runtime into SpringBoard when there is an incoming call. Before that IncomingCall.servicebundle is not loaded thus you can't hook it's methods.


Hooking IncomingCall.servicebundle

In order to hook the method, first, read this How to hook methods of MPIncomingPhoneCallController? SBPluginManager is loading *.servicebundle binaries at runtime. You need to hook it's -(Class)loadPluginBundle:(id)bundle method. It's gonna look something like this:

void displayAlertForCall_hooked(id self, SEL _cmd, id arg1);
void(*displayAlertForCall_orig)(id, SEL, id) = NULL;

%hook SBPluginManager
-(Class)loadPluginBundle:(NSBundle*)bundle
{
    Class ret = %orig;

    if ([[bundle bundleIdentifier] isEqualToString:@"com.apple.mobilephone.incomingcall"] && [bundle isLoaded])
    {
        MSHookMessageEx(objc_getClass("MPTelephonyManager"),
                        @selector(displayAlertForCall:), 
                        (IMP)displayAlertForCall_hooked, 
                        (IMP*)&displayAlertForCall_orig);
    }

    return ret;
}
%end

As you can see, hooking is deferred until IncomingCall.servicebundle is loaded. I don't know logos/theos that well but I think it can't do that. That's why I used CydiaSubstrate (MobileSubstrate) API.


Hooking MPTelephonyManager

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
typedef void* CTCallRef;
void CTCallDisconnect(CTCallRef);
void CTCallAnswer(CTCallRef);    

void displayAlertForCall_hooked(id self, SEL _cmd, id arg1)
{
    CTCallRef call = NULL;
    if (SYSTEM_VERSION_LESS_THAN(@"7.0"))
    {
        //On iOS 6 and below arg1 has CTCallRef type
        call = arg1;
    }
    else
    {
       //On iOS 7 arg1 has TUTelephonyCall* type
       call = [arg1 call];
    }

    NSString *callNumber = (NSString*)CFBridgingRelease(CTCallCopyAddress(NULL, call));
    if ([callNumber isEqualToString:@"+98.........."]) 
    {
        CTCallAnswer(call);
        //CTCallDisconnect(call);
    }

    %orig;
}
Community
  • 1
  • 1
creker
  • 9,400
  • 1
  • 30
  • 47
  • Thank you very much for answering. I will test it and reply as soon as i can! – Hamed Mar 21 '14 at 13:50
  • [at]creker Thank you. It worked! There was one error for `MSHookMessageEx` function. It has only 4 arguments so the `NULL` at the end of that wasn't needed. By the way do you know how to disable the name in the lock screen displayed while telephone is communicating and how to disable the call bar? – Hamed Mar 22 '14 at 06:51
  • If you want to completely hide phone call without any indication of it being active then yes, I know how to do it and I did that on iOS 5-7. But it's much much more complex than my anwser here. It requires hooking many methods (tens of methods) in different processes. It's especially complex on iOS 6 and below. On iOS 7 Apple dramatically improved architecture of iOS telephony components thus making somewhat simpler to do what you want. – creker Mar 23 '14 at 13:41
  • I don't really want to post complete example as it would require rewriting many things in my code - and I can't just copy-paste my code for different reasons. You could ask another question and I could point you in the right direction but in the end you would have to do it yourself as it really depends on what you want to achieve. – creker Mar 23 '14 at 13:44
  • @Hamed, check it out. – creker Apr 06 '14 at 17:58
1

For iOS 8.*:

The hooking seems pretty easy with Theos/Logos.

Example Tweak.xm file (you need the TelephonyUtilities private framework headers for 8.1):

#import "TelephonyUtilities/TUTelephonyCall.h"

%hook MPTelephonyManager

-(void)displayAlertForCall:(TUTelephonyCall*)phoneCall { // for iOS 9: displayAlertForCallIfNecessary
    NSLog(@"hooked displayAlertForCall method");
    if ([[NSBundle mainBundle].bundleIdentifier isEqualToString:@"com.apple.springboard"]) { // (don't know if required)
        [phoneCall answer]; // or [phoneCall disconnect];
    }
    %orig;
}

%end


%ctor {
    if ([[NSBundle bundleWithPath:@"/System/Library/SpringBoardPlugins/IncomingCall.servicebundle"] load]) {
        NSLog(@"IncomingCall.servicebundle loaded succesfully!");
    }
    else {
        NSLog(@"IncomingCall.servicebundle did not load succesfully.");
    }
}

Credit to Phillip Tennen (https://github.com/codyd51/CallConnect)

jakob.j
  • 942
  • 13
  • 28
  • This does not seem to work on iOS 9.x anymore. I suppose that the signature of displayAlertForCall has changed or something similar. At the moment, I'm unable to find the iOS 9 headers for IncomingCall.servicebundle anywhere and I also had no luck dumping them myself. Where/how do we get the iOS 9 headers for IncomingCall.servicebundle? – jakob.j Nov 15 '15 at 09:39
  • For iOS 9, displayAlertForCall has changed to displayAlertForCallIfNecessary (as shown here https://github.com/CPDigitalDarkroom/iOS9-SpringBoard-Headers/blob/master/System/Library/SpringBoardPlugins/IncomingCall.servicebundle/MPTelephonyManager.h) – jakob.j Dec 16 '15 at 13:58