前段时间项目中用到了电话拦截,于是我就进行了一段很长时间的折腾。尽管研究的还不太清楚,但是还是把已经琢磨明白的东西总结出来,以便其他的朋友参考。
我们在项目中需要监听手机上的来电,如果电话号码是我们预先设定的电话号码就挂断这个电话,然后启动另外的程序。当然这中间有可能对方先挂断电话,这时也应该启动我们需要的程序。为此需要去研究手机上的电话基本操作,包括监听来电,得到电话号码,挂电话等。我们的需求还有另外一个要求就是挂掉电话的同时不同挂断已经存在的数据连接。
实现电话的基本操作在mobile上有两种方式:tapi和ril两种方式。tapi是最官方的方式,是基于ril的封装,各个厂家基本都实现了接口中的功能。ril方式更加底层,是wince上的一个接口,各个厂家实现的功能多少不一样,具体的接口差异需要去具体测试。但是ril方式能够控制更多的东西。
开始我们是采用tapi方式实现的,但是后来发现在个别手机不能很好的实现需求,于是才又采用ril方式针对个别手机重新实现。
开场白大概就写这么多吧,下面就结合代码介绍下我的实现。我的代码采用vc++实现的。下面的示例代码是从工程中抽取的一些片段,但是我也尽量把他们说明清楚。
1。tapi方式
需要包含的头文件是 #include "tapi.h",需要加载的库是
#pragma comment(lib,"cellcore.lib") //必须的
#pragma comment(lib,"Wininet.lib") //可能不是必须的,但是我当时还是一起加上去了,免得报错
操作的步骤有2步:初始化并且注册回调函数,接下来在回调函数中处理关心的消息。
第一步: 初始化并且注册回调函数举例如下:
DWORD LineHandleCount; //线路描述符
HLINEAPP LineApp; //线路应用程序句柄
if (lineInitialize(&LineApp,g_hInst,(LINECALLBACK)LineCallback,_T("myapp"),&LineHandleCount) == 0)
{
LineHandles = new HLINE[LineHandleCount];
//打开所有的电话线路
for(DWORD i = 0; i < LineHandleCount; i++)
{
int rc;
DWORD ver;
LINEEXTENSIONID extensionID;
if ( lineNegotiateAPIVersion( LineApp, i, 0x00010000, 0x00030000, &ver, &extensionID) == 0)
{
rc = lineOpen( LineApp,i,&LineHandles[i],ver,0,(DWORD)0,LINECALLPRIVILEGE_MONITOR|LINECALLPRIVILEGE_OWNER,
LINEMEDIAMODE_INTERACTIVEVOICE,NULL);
int j = 0;
}
}
}
LineCallback 是处理程序的回调函数,也是接下来的重点。
myapp 是应用程序的名字
第二步:回调处理
VOID CALLBACK LineCallback(DWORD hDevice, DWORD dwMsg, DWORD dwCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
TCHAR szPhoneNumber[30];
switch(dwMsg)
{
//来电消息
case LINE_CALLINFO:
{
switch(dwParam1)
{
//当前我们的手机电话线空闲
case LINECALLINFOSTATE_CALLERID:
{
LINECALLINFO *lpCallInfo;
lpCallInfo = (LINECALLINFO *)malloc(sizeof(LINECALLINFO)+1000);
memset(lpCallInfo, 0, sizeof(LINECALLINFO)+1000);
lpCallInfo->dwTotalSize = sizeof(LINECALLINFO)+1000;
//得到来电电话号码
while (1)
{
lineGetCallInfo( (HCALL)hDevice, lpCallInfo);
if (lpCallInfo->dwTotalSize < lpCallInfo->dwNeededSize)
lpCallInfo = (LINECALLINFO *)realloc(lpCallInfo,lpCallInfo->dwNeededSize);
else break;
}
lstrcpy(szPhoneNumber,(LPTSTR)((LPSTR)((DWORD)lpCallInfo+(DWORD)lpCallInfo->dwCallerIDOffset)));
//释放内存
free(lpCallInfo);
//获取设定的电话号码
//判断来电电话号码是否与设定的电话号码相同
if(!lstrcmp(szPhoneNumber,设定的电话号码))
{
//挂断电话
lineDrop((HCALL)hDevice,NULL,0);
// ::Sleep(10);
::Sleep(1000);
//判断进程是否存在,如果不存在就启动我们的进程
if(!IsExsit())
{
if(TRUE==::CreateProcess(CString("myapp"), NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL))
{
}
}
}
}
break;
//当前我们的手机电话线忙
case LINECALLINFOSTATE_BUSING://LINECALLINFOSTATE_NUMMONITORS
{
LINECALLINFO *lpCallInfo;
lpCallInfo = (LINECALLINFO *)malloc(sizeof(LINECALLINFO)+1000);
memset(lpCallInfo, 0, sizeof(LINECALLINFO)+1000);
lpCallInfo->dwTotalSize = sizeof(LINECALLINFO)+1000;
//得到来电电话号码
while (1)
{
lineGetCallInfo( (HCALL)hDevice, lpCallInfo);
if (lpCallInfo->dwTotalSize < lpCallInfo->dwNeededSize)
lpCallInfo = (LINECALLINFO *)realloc(lpCallInfo,lpCallInfo->dwNeededSize);
else break;
}
lstrcpy(szPhoneNumber,(LPTSTR)((LPSTR)((DWORD)lpCallInfo+(DWORD)lpCallInfo->dwCallerIDOffset)));
//释放内存
free(lpCallInfo);
//获取设定的电话号码
//判断来电电话号码是否与设定的电话号码相同
if(!lstrcmp(szPhoneNumber,设定的电话号码))
{
//挂断电话
lineDrop((HCALL)hDevice,NULL,0);
//判断进程是否存在,如果不存在就启动我们的进程
if(!IsExsit())
{
if(TRUE==::CreateProcess(CString("myapp"), NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL))
{
}
}
}
}
break;
}
}
}
}
2 ril方式
需要包含的头文件 #include "ril.h",需要导入的库文件 #pragma comment(lib, "ril.lib") ,不过这两个文件在标准的mobile sdk中不存在,需要从网上下载,在网上搜索下,很多地方可以下载。
ril方式的处理步骤和tapi类似,也是先初始化,之后在回调函数中作相关的处理。
第一步: 初始化
HRIL g_hRil; //ril的句柄
//回调函数声明
void CALLBACK RilNotifyProc(DWORD dwNotifyCode, const void* pData, DWORD dwDataSize, DWORD dwParam);
void CALLBACK RilResultProc(DWORD dwResultCode, HRESULT hrCommandID, const void* pData, DWORD dwDataSize, DWORD dwParam);
//初始化函数
HRESULT hr = RIL_Initialize(1, RilResultProc, RilNotifyProc, RIL_NCLASS_CALLCTRL/*RIL_NCLASS_ALL*/, NULL, &g_hRil);
第二步:处理回调函数
//我这里基本没有用到,不过也需要定义一个空函数
void CALLBACK RilResultProc(DWORD dwResultCode, HRESULT hrCommandID, const void* pData, DWORD dwDataSize, DWORD dwParam)
{
return;
}
//就是需要我们重点处理的回调函数,在这里进行我们全部的操作
void CALLBACK RilNotifyProc(DWORD dwNotifyCode, const void* pData, DWORD dwDataSize, DWORD dwParam)
{
HKEY hCallStateKey = NULL;
HRESULT hrHangup = 0;
RILRINGINFO *ringInfo;
RILREMOTEPARTYINFO *partyInfo;
RILCALLINFO *callInfo;
WCHAR wszAddress[MAXLENGTH_ADDRESS];
RILADDRESS raAddress;
switch(dwNotifyCode)
{
default:
break;
//case RIL_NOTIFY_DISCONNECT: //对方主动挂电话 ,数据线路挂断也会收到这个消息,在我适配的那几个手机上可以不处理这个消息,其他的手机没有测试过
case RIL_NOTIFY_CALLSTATECHANGED : //本地挂掉电话 ,电话状态变化
{
//如果我们需要启动的的进程不在
if(!IsExsit())
{
//如果已经确认该电话就是我们设定的电话号码,直接启动我们的程序
if (bOK)
{
//读取默认的 休息时间从注册表
#define DEFAULTSLEEPTIME 2
Sleep(DEFAULTSLEEPTIME*1000);
if(TRUE==::CreateProcess(CString("myapp"), NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL))
{
}
//找到来电的标志位置位
bOK = false;
}
else
{
//如果电话被对方挂断,还没来得及得到来电的电话号码,在这里再尝试获取一次电话号码
TCHAR TRecvNumber[128] ;
memset(TRecvNumber,0,sizeof(TRecvNumber));
ringInfo = (RILRINGINFO*)pData;
//通过读取 注册表 System\\State\\Phone 下的 Incoming Caller Number 键值得到来电。这个键值在来电时候来会存在,平时没有电话的时候这个键值不存在
RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\State\\Phone"), 0,KEY_READ,&hCallStateKey);
DWORD dwType = REG_SZ;
DWORD Len = 128;
long return2 = RegQueryValueEx(hCallStateKey,L"Incoming Caller Number",NULL,&dwType,(LPBYTE)TRecvNumber,&Len);
if (return2 != ERROR_SUCCESS)
{
}
RegCloseKey(hCallStateKey);
//读取预先设定的电话号码
//对这两个电话号码进行比较,如果相同启动我们需要的程序
if(lstrcmp(预先设定的电话号码,TRecvNumber) == 0)
{
if(TRUE==::CreateProcess(CString("myapp"), NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL))
{
}
}
}
}
}
break;
case RIL_NOTIFY_RING: //振铃,这个消息,有些手机在来电的振铃的过程中会不停地发送本消息,有些只发送一次
// case RIL_NOTIFY_CONNECT: //链接已经建立 电话接通和数据拨号成功都会有这个消息
{
//读取本次来电电话号码
//针对 v908 需要休眠一段时间以得到电话号码,现在暂时设定为 4秒
int nReadTime = 0;
nReadTime = 2;
for (int i = 0;i {
//获取来电电话号码
TCHAR TRecvNumber[128] ;
memset(TRecvNumber,0,sizeof(TRecvNumber));
ringInfo = (RILRINGINFO*)pData;
RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\State\\Phone"), 0,KEY_READ,&hCallStateKey);
DWORD dwType = REG_SZ;
DWORD Len = 128;
long return2 = RegQueryValueEx(hCallStateKey,L"Incoming Caller Number",NULL,&dwType,(LPBYTE)TRecvNumber,&Len);
if (return2 != ERROR_SUCCESS)
{
}
RegCloseKey(hCallStateKey);
//读取预先设定的电话号码
//如果两个电话号码相同
if(lstrcmp(预先设定的电话号码,TRecvNumber) == 0)
{
//吧标志位设置上,表示接收到该指定的电话号码的来电
bOK = true;
/*
//挂断来电
hrHangup = RIL_Hangup(g_hRil);
if (hrHangup > 0)
{
}
*/
//不采用 RIL_Hangup 方式挂断电话,理由是RIL_Hangup方式在有些手机上会挂断现有的数据连接
Sleep(2000);
//想系统内广播模拟键盘的挂机键消息,注意需要加2秒的延迟,等来电窗口弹出再发送消息,否则可能会挂断现有的数据连接
keybd_event(VK_TEND,0x73, KEYEVENTF_EXTENDEDKEY,0);
keybd_event(VK_TEND,0x73, KEYEVENTF_EXTENDEDKEY |KEYEVENTF_KEYUP,0);//模拟VK_TEND按键功能
return;
}
else
{
if (wcslen(TRecvNumber) == 0)
{
//如果没有读取到休息500毫秒,继续回去读
Sleep(500);
}
else
{
break;
}
}
}
break;
}
}
return;
}
电话操作时候重要的注意事情:
1。在2g的手机上,电话操作会断开数据连接,包括来电话,接电话,挂电话等。
2。在3g上电话和数据是2个通路。但是有些手机上用 lineDrop 方式挂断电话会同时挂断数据连接
3。在3g电话中,用RIL_Hangup在有些手机上也会挂断已经建立的数据连接
4。keybd_event 这个函数是面向系统内广播该消息。如果要模拟一个键盘按钮消息,需要执行2次该函数,第一次执行时发送该键被按下的消息,第二次执行该函数发送该键弹起的消息。
5。如果来电窗口还没有弹出在最前台,这时候就执行 keybd_event发送模拟的挂机键消息,在有些手机上也可能会挂断已经建立的数据连接。为了实现不挂断现有数据连接的同时挂断来电,可能的方式是加些延时,等来电窗口弹出之后再keybd_event发送模拟的挂机键消息。
6. 用ril方式获取电话号码网上说可以不用去读注册表,可以在回调函数中处理一个特定的消息,但是在我适配的几个手机这种方式不行,所以这里没有写上来。