android低版本自動接聽/掛斷實現


一  前言

這兩天要研究類似白名單黑名單以及手勢自動接聽的一些功能,所以呢,自然而然的涉及到怎麼自動接聽/掛斷電話的功能了。
對於自動接聽這一塊,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,從而達到了
我們的目的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章