Android自动接听和挂断电话实现原理
转自:http://bbs.51cto.com/thread-1078059-1.html
一 前言
这两天要研究类似白名单黑名单以及手势自动接听的一些功能,所以呢,自然而然的涉及到怎么自动接听/挂断电话的功能了。
对于自动接听这一块,android4.1版本及其以上的版本和之前的版本处理逻辑不太一样,因为google增加了权限检查...所以,按照以前的方法可能不能实现自动接听了.
二 android低版本自动接听/挂断实现
1. copy android源代码的ITelephony.aidl文件到自己的项目
为什么要copy这个文件到自己的项目中呢?这是因为接听/挂断电话的方法在接口ITelephony.java里面,而这个接口时隐藏的,也就是sdk开发是看不到这个接口的。
比如:
01
|
package com.android.internal.telephony;
|
02
|
/**
|
03
|
* Interface used to interact with the phone. Mostly this is used by the
|
04
|
* TelephonyManager class. A few places are still using this directly.
|
05
|
* Please clean them up if possible and use TelephonyManager insteadl.
|
06
|
*
|
07
|
* {@hide}
|
08
|
*/
|
09
|
public interface ITelephony extends android.os.IInterface
|
10
|
{
|
11
|
...
|
12
|
}
|
正如上面所说,这个接口ITelephony.java是隐藏的(@hide),它的包名时com.android.internal.telephony,所以,我们在我们的项目里面新建同样的一个包,
然后把系统的ITelephony.aidl拷贝过来.
由于ITelephony.aidl关联了NeighboringCellInfo.aidl,所以也一并拷贝过来。
不过要注意的是,NeighboringCellInfo.aidl所在的的包名是android.telephony;所以,你要新建一个包android.telephony,然后把NeighboringCellInfo.aidl放到
包android.telephony里面。
NeighboringCellInfo.aidl的定义:
1
|
package android.telephony;
|
2
|
3
|
parcelable NeighboringCellInfo;
|
b. 使用ITelephony.java接口
上面一步完成之后,你就会在你的gen目录下发现已经生成了ITelephony.java这个接口文件。这样,我们就可以使用它了..
这里的话,主要是利用反射机制来取得ITelephony对象,为什么要用反射呢?因为 ITelephony对象是以一个系统服务的形式存在系统中的,跟ams,wms等等一样。
一般通过ServiceManager来保存和获取。但是ServiceManager同样也是隐藏的,如:
01
|
/** <a href="http://home.51cto.com/index.php?s=/space/126010" target="_blank">@hide</a> */
|
02
|
public final class ServiceManager {
|
03
|
...
|
04
|
}
|
05
|
06
|
/**
|
07
|
* Returns a reference to a service with the given name.
|
08
|
*
|
09
|
* @param name the name of the service to get
|
10
|
* <a href="http://home.51cto.com/index.php?s=/space/34010" target="_blank">@return</a> a reference to the service, or <code>null</code> if the service doesn't exist
|
11
|
*/
|
12
|
public static IBinder getService(String name) {
|
13
|
try {
|
14
|
IBinder service = sCache.get(name);
|
15
|
if (service != null ) {
|
16
|
return service;
|
17
|
} else {
|
18
|
return getIServiceManager().getService(name);
|
19
|
}
|
20
|
} catch (RemoteException e) {
|
21
|
Log.e(TAG, "error in getService" , e);
|
22
|
}
|
23
|
return null ;
|
24
|
}
|
所以,我们首先要通过反射的机制拿到ServiceManager对象,然后调用ServiceManager.getService(String name)方法来取得ITelephony对象。这个name就是当时
addService()的时候使用的name...
ok... 那我们来看看反射出ServiceManager的代码怎么写。
1
|
Method method = Class.forName( "android.os.ServiceManager" )
|
2
|
.getMethod( "getService" , String. class );
|
3
|
IBinder binder = (IBinder) method.invoke( null , new Object[]{“phone”});
|
解释下上面的代码,Class.forName(String s)里面写的时ServiceManager类所在的完整包名和类型,这样就可以得到ServiceManager的Class对象。然后调用getMethod
方法,参数是getService,后面的String表示getService()方法的参数类型,也就是拿到了ServieManager的getService(String s)这个方法。
嗯...既然已经得到了getService(String name)方法,那么就调用它!把要传入的参数,也就是想得到的Service的名字传入,这里我们传入"phone"字符串,就可以返回一个
IBinder对象。
那,为什么要传入"phone"这个名字呢?
这是因为ITelephony.java 的实现类PhoneInterfaceManager.java在创建的时候,把自己添加进入了ServiceManager,然后使用的名字就是"phone"
如:
代码路径:
packages/apps/Phone/src/com/android/phone/PhoneInterfaceManager.java
代码:
01
|
/**
|
02
|
* Implementation of the ITelephony interface.
|
03
|
*/
|
04
|
public class PhoneInterfaceManager extends ITelephony.Stub {
|
05
|
....
|
06
|
|
07
|
private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
|
08
|
mApp = app;
|
09
|
mPhone = phone;
|
10
|
mCM = PhoneGlobals.getInstance().mCM;
|
11
|
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
|
12
|
mMainThreadHandler = new MainThreadHandler();
|
13
|
publish();
|
14
|
}
|
15
|
16
|
private void publish() {
|
17
|
ServiceManager.addService( "phone" , this );
|
18
|
}
|
19
|
}
|
嗯,大家看到没有,PhoneInterfaceManager实现了ITelephony接口,然后在publish的时候,调用了ServicerManager.addService(xxx),
把自己添加进入了ServiceManager,起的名字时"phone"。所以,我们只要调用getService("phone"),就可以拿到ITelephony的对象,也就是PhoneInterfaceManager对象。
c. 调用ITelephony.java的answerRingingCall()方法接听电话
代码:
1
|
ITelephony telephony = ITelephony.Stub.asInterface(binder);
|
2
|
telephony.answerRingingCall();
|
解释下上面两行代码:
第一行是把上面getService("phone")得到的IBinder对象binder转化成ITelephony对象,这是Binder机制的东西,就不讲了。大家只要记得Binder对象和具体对象和相互转换即可。
第二行是调用answerRingingCall()方法,这个方法调用之后,就会接通电话。
如果是挂断电话的话,就应该调用telephony.endCall()方法,这个相信大家也能理解的。
d. 配置应用程序权限
最后,我们还需要在AndroidManifest.xml里面配置下权限:
如下:
1
|
< uses-permission android:name = "android.permission.CALL_PHONE" />
|
2
|
< uses-permission android:name = "android.permission.MODIFY_PHONE_STATE" />
|
上面自动接听电话的代码在4.1以前版本运行是没有问题的,但是新版本的android对接听电话函数(挂断电话没问题),也就是answerRingingCall(),增加权限检查。只有系统进程才有权限执行这个方法,
其他应用程序调用的话,就抛出一个异常:
代码:
D/Sandy ( 9058): java.lang.SecurityException: Neither user 10125 nor current process has android.permission.MODIFY_PHONE_STATE.
D/Sandy ( 9058): at android.os.Parcel.readException(Parcel.java:1327)
D/Sandy ( 9058): at android.os.Parcel.readException(Parcel.java:1281)
D/Sandy ( 9058): at com.android.internal.telephony.ITelephony$Stub$Proxy.answerRingingCall(ITelephony.java:1019)
D/Sandy ( 9058): at com.example.hillrestproject.service.PhonePickupService.onPickUpEvent(PhonePickupService.java:180)
D/Sandy ( 9058): at com.hcrest.gestures.pickup.PickUpDetector.onSensorData(PickUpDetector.java:150)
D/Sandy ( 9058): at com.hcrest.android.sensors.SensorManagerAdapter$ListenerDelegate.onSensorChanged(SensorManagerAdapter.java:373)
D/Sandy ( 9058): at android.hardware.SensorManager$ListenerDelegate$1.handleMessage(SensorManager.java:635)
D/Sandy ( 9058): at android.os.Handler.dispatchMessage(Handler.java:99)
D/Sandy ( 9058): at android.os.Looper.loop(Looper.java:137)
D/Sandy ( 9058): at android.app.ActivityThread.main(ActivityThread.java:4507)
D/Sandy ( 9058): at java.lang.reflect.Method.invokeNative(Native Method)
D/Sandy ( 9058): at java.lang.reflect.Method.invoke(Method.java:511)
D/Sandy ( 9058): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
D/Sandy ( 9058): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
D/Sandy ( 9058): at dalvik.system.NativeStart.main(Native Method)
所以,对于高版本的手机的话,我们要用另外的方法来处理,这也是下面要讨论的问题。
三 高版本自动接听电话
我们把整个代码贴出来,然后一行行解释
01
|
try {
|
02
|
Method method = Class.forName( "android.os.ServiceManager" )
|
03
|
.getMethod( "getService" , String. class );
|
04
|
|
05
|
IBinder binder = (IBinder) method.invoke( null , new Object[]{TELEPHONY_SERVICE});
|
06
|
|
07
|
ITelephony telephony = ITelephony.Stub.asInterface(binder);
|
08
|
|
09
|
telephony.answerRingingCall();
|
10
|
|
11
|
} catch (NoSuchMethodException e) {
|
12
|
Log.d( "Sandy" , "" , e);
|
13
|
} catch (ClassNotFoundException e) {
|
14
|
Log.d( "Sandy" , "" , e);
|
15
|
} catch (Exception e) {
|
16
|
Log.d( "Sandy" , "" , e);
|
17
|
try {
|
18
|
Log.e( "Sandy" , "for version 4.1 or larger" );
|
19
|
Intent intent = new Intent( "android.intent.action.MEDIA_BUTTON" );
|
20
|
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
|
21
|
intent.putExtra( "android.intent.extra.KEY_EVENT" ,keyEvent);
|
22
|
sendOrderedBroadcast(intent, "android.permission.CALL_PRIVILEGED" );
|
23
|
} catch (Exception e2) {
|
24
|
Log.d( "Sandy" , "" , e2);
|
25
|
Intent meidaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
26
|
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
|
27
|
meidaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT,keyEvent);
|
28
|
sendOrderedBroadcast(meidaButtonIntent, null );
|
29
|
}
|
30
|
}
|
在上面的代码里面,最开始的代码和第二点讲解的时候,没有什么区别,主要看Exception发生之后,try{}里面的代码,也就是:
1
|
Log.e( "Sandy" , "for version 4.1 or larger" );
|
2
|
Intent intent = new Intent( "android.intent.action.MEDIA_BUTTON" );
|
3
|
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
|
4
|
intent.putExtra( "android.intent.extra.KEY_EVENT" ,keyEvent);
|
5
|
sendOrderedBroadcast(intent, "android.permission.CALL_PRIVILEGED" );
|
这里其实就是发送了一个广播就完事了,这个广播的action是"android.intent.action.MEDIA_BUTTON",然后还有一个参数---keyEvent
那么,这个广播有什么用呢?为什么可以自动接听电话呢?关于这一点,我们要看看这个广播的接受者怎么处理这个广播的。
代码路径:
packages/apps/Phone/src/com/android/phone/PhoneGlobals.java
代码:
1
|
IntentFilter mediaButtonIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
|
2
|
mediaButtonIntentFilter.setPriority( 1 );
|
3
|
registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter);
|
/
01
|
/ Broadcast receiver purely for ACTION_MEDIA_BUTTON broadcasts
|
02
|
private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver();
|
03
|
04
|
05
|
protected class MediaButtonBroadcastReceiver extends BroadcastReceiver {
|
06
|
<a href= "http://home.51cto.com/index.php?s=/space/5017954" target= "_blank" > @Override </a>
|
07
|
public void onReceive(Context context, Intent intent) {
|
08
|
KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
09
|
|
10
|
if ((event != null )
|
11
|
&& (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
|
12
|
|
13
|
boolean consumed = PhoneUtils.handleHeadsetHook(phone, event);
|
14
|
.....
|
15
|
} else {
|
16
|
....
|
17
|
}
|
18
|
}
|
19
|
}
|
1
|
static boolean handleHeadsetHook(Phone phone, KeyEvent event) {
|
2
|
...
|
3
|
answerCall(phone.getRingingCall());
|
4
|
...
|
5
|
}
|
解释下上面贴的代码
1. PhoneGlobals注册一个广播接收器,action就是上面我们发送的广播“android.intent.action.MEDIA_BUTTON”
ok..这个广播接收器接受到广播之后,就会执行onReceive()方法,也就是MediaButtonBroadcastReceiver的onReceive()方法。
在onReceive()方法里面,它会判断按下的keyEvent,如果是KeyEvent.KEYCODE_HEADSETHOOK的话,就会执行PhoneUtils.handleHeadsetHook(xxx)方法
在handleHeadsetHook(xxx)里面,会调用answerCall(phone.getRingingCall)方法,也就是接听电话了...
那么,就有个疑问,为什么android会提供这个MediaButtonBroadcastReceiver广播接收器呢?
其实,这个广播接收器是为了监听耳机上接听电话那个按钮的,耳机上有个按钮,来电时只要按一下,就可以接听电话,也就是会调用我们这个MediaButtonBroadcastReceiver
广播接收器。
那,这就给我们提供了方便之门,做自动接听程序的时候,尽管google已经增加了权限检查,但是我们通过绕过去的方式,利用MediaButtonBroadcastReceiver,从而达到了
我们的目的。
这算不算Android的一个漏洞呢...
Android自动接听和挂断电话实现原理相关推荐
- android自动接听和挂断电话
实现android自动接听和挂断电话功能.代码如下: 添加权限 <uses-permission android:name="android.permission.CALL_PHONE ...
- Android拨打、接听、挂断电话操作
Android2.3之前的系统可以通过反射机制调用ITelephone的方法来挂断电话,因为Android2.3以后增加了对permission android.permission.MODIFY_ ...
- Android 5.1 Phone 挂断电话流程分析
写在前面的话 本文主要分析Android挂断电话的流程,研究的代码是Android 5.1的,以CDMA为例,GSM同理. 挂断电话主要分两种情况: 本地主动挂断电话 \color{red}{本地主动 ...
- Xamarin.Android实现自动拨打及挂断电话的功能
目录 1.说明 2.具体实现 2.1 效果 2.2 原理说明 2.3 具体代码实现 2.3.1 layout 2.3.3 广播接收器 2.3.3 拨打电话的Activity 2.3.4 主界面Acti ...
- Android高版本无法挂断电话问题
今天接到一个之前的功能,现在要拿出来看一看能不能用.打电话接到挂断,在网上搜了半天,自己的代码看了半天也就两种. 第一种 try {Method method = Class.forName(&quo ...
- Android通过AIDL实现接听电话、挂断电话操作 | 拨打电话
Android中拨打电话功能是很轻松的只需要几行代码即可,如下: 首先还是得加上权限这个东西(或者动态申请) <!--拨打电话--> <uses-permission android ...
- android挂断电话广播,android实现接通和挂断电话
android实现接通和挂断电话 发布时间:2020-08-21 01:52:02 来源:脚本之家 阅读:230 作者:WillenWu 本文实例为大家分享了android实现接通和挂断电话的具体代码 ...
- 自动接听或者挂断来电
如何帮助用户自动接听或者挂断来电. 源码 GitHub源码中用了MVP的模式,只是最简单的使用,如果不熟悉的话刚好可以学学,逻辑部分在IncomingPresenter类中. 以下是逻辑图 首先监听来 ...
- 挂断电话的实现(即类似于电话号码黑名单)
在文章的开头先列出以下需要注意的地方: 1.需要用到的权限如下: <uses-permission android:name="android.permission.READ_PHON ...
最新文章
- php lvs,LVS(四)LVS集群DR模式
- 输入字符串中含有该字符的个数
- Spring 和 Spring Boot 之间到底有啥区别?
- vmware通过vCerter Converter Standalone实现不同VC的V2V虚拟机迁移
- 小米11顶配版屏幕参数曝光:2K 120Hz高刷屏支持运动补偿
- oracle ignore_Oracle 数据库常用操作语句,速速收藏
- Java FileInputStream
- L1-055 谁是赢家 (10 分)—团体程序设计天梯赛
- 蚂蚁金服分布式链路跟踪组件 SOFATracer 总览 | 剖析
- ADS 2013 momentum仿真出错
- numpy矢量化运算
- 激活函数总结——2020.2.10
- linux解压缩命令 gz,linux的gz解压缩命令是什么
- Java计算当前时间,结合时区
- 2020年即将推出的智能手环APP定制开发
- 课程项目:大学程序设计相关大作业汇总参考及源码地址
- 互联网下半场,苏宁“拼购村”如何打造现象级模式
- 【Rust】argh:基于 derive 宏且对二进制体积进行优化的命令行解析工具
- 类的静态成员与静态成员函数
- 基于Java+SpringBoot+Vue前后端分离餐厅点餐管理系统设计和实现
热门文章
- 通过sql对比两个表的一致性
- 斯阔谷冬奥会首次使用计算机,第八届美国斯阔谷冬奥会_温哥华冬奥会_新浪体育_新浪网...
- AlphaCode:程序员的另类“内卷”?
- mysql 组合索引 in_mysql组合索引与字段顺序
- 短视频开发的基石:短视频源码
- ubuntu20.04 掉无线网卡驱动解决
- 小米8 SE官方原版ROM系统MIUI所有固件
- Spring Boot 2.x 事务处理(一篇长文让你读懂什么是隔离级别和传播行为)
- android课程设计计步器,数字计步器课程设计.docx
- html页面跳转返回不要刷新,javascript跳转与返回和刷新页面的实例代码