zoukankan      html  css  js  c++  java
  • Android自动接听&挂断电话(包含怎么应对4.1以上版本的权限检

    一  前言

    这两天要研究类似白名单黑名单以及手势自动接听的一些功能,所以呢,自然而然的涉及到怎么自动接听/挂断电话的功能了。
    对于自动接听这一块,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的一个漏洞呢...

  • 相关阅读:
    SDUT 2143 图结构练习——最短路径 SPFA模板,方便以后用。。 Anti
    SDUT ACM 1002 Biorhythms 中国剩余定理 Anti
    nyist OJ 119 士兵杀敌(三) RMQ问题 Anti
    SDUT ACM 2157 Greatest Number Anti
    SDUT ACM 2622 最短路径 二维SPFA启蒙题。。 Anti
    二叉索引树 区间信息的维护与查询 Anti
    SDUT ACM 2600 子节点计数 Anti
    UVA 1428 Ping pong 二叉索引树标准用法 Anti
    2010圣诞Google首页效果
    Object
  • 原文地址:https://www.cnblogs.com/laughingQing/p/4576476.html
Copyright © 2011-2022 走看看