一 前言
這兩天要研究類似白名單黑名單以及手勢自動接聽的一些功能,所以呢,自然而然的涉及到怎麼自動接聽/掛斷電話的功能了。
對於自動接聽這一塊,android4.1版本及其以上的版本和之前的版本處理邏輯不太一樣,因爲google增加了權限檢查...所以,按照以前的方法可能不能實現自動接聽了.
二 android低版本自動接聽/掛斷實現
1. copy android源代碼的ITelephony.aidl文件到自己的項目
爲什麼要copy這個文件到自己的項目中呢?這是因爲接聽/掛斷電話的方法在接口ITelephony.java裏面,而這個接口時隱藏的,也就是sdk開發是看不到這個接口的。
比如:
01 |
package com.android.internal.telephony; |
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. |
09 |
public interface
ITelephony extends
android.os.IInterface |
正如上面所說,這個接口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; |
3 |
parcelable NeighboringCellInfo; |
b. 使用ITelephony.java接口
上面一步完成之後,你就會在你的gen目錄下發現已經生成了ITelephony.java這個接口文件。這樣,我們就可以使用它了..
這裏的話,主要是利用反射機制來取得ITelephony對象,爲什麼要用反射呢?因爲 ITelephony對象是以一個系統服務的形式存在系統中的,跟ams,wms等等一樣。
一般通過ServiceManager來保存和獲取。但是ServiceManager同樣也是隱藏的,如:
02 |
public final
class ServiceManager { |
07 |
* Returns a reference to a service with the given name. |
09 |
* @param name the name of the service to get |
12 |
public
static IBinder getService(String name) { |
14 |
IBinder service = sCache.get(name); |
15 |
if
(service != null ) { |
18 |
return
getIServiceManager().getService(name); |
20 |
}
catch (RemoteException e) { |
21 |
Log.e(TAG,
"error in getService" , e); |
所以,我們首先要通過反射的機制拿到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
代碼:
02 |
* Implementation of the ITelephony interface. |
04 |
public class
PhoneInterfaceManager extends
ITelephony.Stub { |
07 |
private
PhoneInterfaceManager(PhoneGlobals app, Phone phone) { |
10 |
mCM = PhoneGlobals.getInstance().mCM; |
11 |
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE); |
12 |
mMainThreadHandler =
new MainThreadHandler(); |
16 |
private
void publish() { |
17 |
ServiceManager.addService( "phone" ,
this ); |
嗯,大家看到沒有,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)
所以,對於高版本的手機的話,我們要用另外的方法來處理,這也是下面要討論的問題。
三 高版本自動接聽電話
我們把整個代碼貼出來,然後一行行解釋
02 |
Method method = Class.forName( "android.os.ServiceManager" ) |
03 |
.getMethod( "getService" , String. class ); |
05 |
IBinder binder = (IBinder) method.invoke( null ,
new Object[]{TELEPHONY_SERVICE}); |
07 |
ITelephony telephony = ITelephony.Stub.asInterface(binder); |
09 |
telephony.answerRingingCall(); |
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); |
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 ); |
在上面的代碼裏面,最開始的代碼和第二點講解的時候,沒有什麼區別,主要看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(); |
05 |
protected
class MediaButtonBroadcastReceiver
extends BroadcastReceiver { |
07 |
public
void onReceive(Context context, Intent intent) { |
08 |
KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); |
11 |
&& (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) { |
13 |
boolean
consumed = PhoneUtils.handleHeadsetHook(phone, event); |
1 |
static boolean
handleHeadsetHook(Phone phone, KeyEvent event) { |
3 |
answerCall(phone.getRingingCall()); |
解釋下上面貼的代碼
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,從而達到了
我們的目的。