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的一个漏洞呢...

  • 相关阅读:
    Hibernate学习笔记(一)
    mysql内联接、左联接、右联接
    mysql表数据增删改查、子查询
    mysql建表时候的五种约束
    mysql数据库基本数据类型
    nginx uwsgi flask相关配置
    关于爬虫数据的解析器设计
    Redis 七月小说网的爬虫缓存设计
    MariaDB 数据库迁移
    React Relay 实现
  • 原文地址:https://www.cnblogs.com/laughingQing/p/4576476.html
Copyright © 2011-2022 走看看