android 通话状态监听(自定义接听挂断按钮与通话界面,根据公司的业务逻辑可以实现自己的来电秀功能)

8,167 阅读6分钟

前言:

因为公司需求,要自定义一款来电秀的app当做周边产品来配合主营的app业务。 之前因为赶项目,没时间整理这块,现在项目告一段落了,现在回头看看感觉这个功能还是挺有意思的,比较有针对性。电话呼入或者呼出的时候,结合公司的业务显示出对应的界面还有挺nice的。然而网上关于这方面的文章比较少,还是挺有必要整理一下。 来电秀主要功能是监听通话(包括呼出和呼入电话)来实现自己想要的界面效果

正文:

** 实现手机电话状态的监听,主要依靠两个类:TelephoneManger和PhoneStateListener。 ** 今天主要用到的是PhoneStateListener来实现通话状态监听的功能,使用起来比较简单,步骤也比较明了

第一步:当然是要添加权限

    <uses-permission Android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />

第二步:自定义一个类,并继承PhoneStateListener类。

	class MyPhoneStateListener extends PhoneStateListener {

		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			super.onCallStateChanged(state, incomingNumber);
			
			}
		}
	}

他的state值有如下几种

    TelephonyManager.CALL_STATE_IDLE:  空闲状态
    TelephonyManager.CALL_STATE_RINGING:  响铃状态
    TelephonyManager.CALL_STATE_OFFHOOK:  通话状态

操蛋的是,state只有通话状态(CALL_STATE_OFFHOOK),但是并没有区分是呼入,还是呼出的。而且空闲状态(CALL_STATE_IDLE)也并没有区分是挂断空闲,还是没有通话的空闲。不过,还是有解决方法滴,这个稍后在说。

第三步:定义一个service,并将MyPhoneStateListener这个类放到service中,分别在onCreat和onDestroy调用开启监听和关闭监听的方法。

懒癌又犯了,直接上代码了,代码中的注释比较清晰,如果要是有什么不明白的,可以给我留言。^^ /** * 获取来电号码服务 / public class CallService extends Service { /* * 电话服务管理器 / private TelephonyManager telephonyManager; /* * 电话状态监听器 */ private MyPhoneStateListener myPhoneStateListener;

    	@Override
    	public IBinder onBind(Intent intent) {
    		return null;
    	}
    
    	@Override
    	public void onCreate() {
    
    		super.onCreate();
    		// 获取来电号码
    		LogUtils.e("state","开启服务");
    		getIncomingCall();
    	}
    
    	@Override
    	public void onDestroy() {
    		super.onDestroy();
    		// 不获取来电号码
    		LogUtils.e("state","关闭服务");
    		getIncomingCallCancel();
    	}
    
    	/**
    	 * 获取来电号码
    	 */
    	private void getIncomingCall() {
    		// 获取电话系统服务
    		telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    		myPhoneStateListener = new MyPhoneStateListener();
    		telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    	}
    
    	/**
    	 * 不获取来电号码
    	 */
    	private void getIncomingCallCancel() {
    		telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    	}
    
    	/**
    	 * 电话状态监听器
    	 */
    	class MyPhoneStateListener extends PhoneStateListener {
    
    		@Override
    		public void onCallStateChanged(int state, String incomingNumber) {
    			super.onCallStateChanged(state, incomingNumber);
    
    			incomingNumberFinal=incomingNumber;
    			switch (state) {
    				case TelephonyManager.CALL_STATE_RINGING://响铃
    					break;
    
    				case TelephonyManager.CALL_STATE_OFFHOOK://通话状态
    					break;
    
    				case TelephonyManager.CALL_STATE_IDLE://空闲状态
    					break;
    
    				default:
    					break;
    			}
    
    		}
    	}
    }
  • 接下来,我们来区分通话状态是呼入还是呼出的,以及空闲状态是挂断空闲,还是没有通话的空闲

    1.思考:要想区分呼入和呼出的状态,就要考虑呼入和呼出的状态的区别是什么。 2.回答:呼入和呼出的状态的区别就是一个是别人打进来的,一个是自己主动拨出去的(废话),那么呼入状态是不是就要有响铃呢,呼出状态没有响铃的呢。 **3.豁然开朗:**那么是不是区分出是否有响铃就能区分呼入和呼出呢,区分是否有响铃,只需要记录上一次的状态就可以了。 **4.联想:(不是广告)**空闲状态是挂断空闲还是没有通话的空闲,也可以判断上一次状态是不是空闲状态来区分。

    思考到此结束了,还是直接奉上代码,如果要是有什么不明白的,可以给我留言。

      /**
       * 获取来电号码服务
       */
      public class IncomingCallService extends Service {
      
          /**
           * 记录上一个电话状态
           */
      	private int lastCallState  = TelephonyManager.CALL_STATE_IDLE;
      
      	/**
      	 * 电话服务管理器
      	 */
      	private TelephonyManager telephonyManager;
      
      	/**
      	 * 电话状态监听器
      	 */
      	private MyPhoneStateListener myPhoneStateListener;
      
      	@Override
      	public IBinder onBind(Intent intent) {
      		return null;
      	}
      
      	@Override
      	public void onCreate() {
      
      		super.onCreate();
      		// 获取来电号码
      		LogUtils.e("state","开启服务");
      		getIncomingCall();
      	}
      
      	@Override
      	public void onDestroy() {
      		super.onDestroy();
      		// 不获取来电号码
      		LogUtils.e("state","关闭服务");
      		getIncomingCallCancel();
      	}
      
      	/**
      	 * 获取来电号码
      	 */
      	private void getIncomingCall() {
      		// 获取电话系统服务
      		telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
      		myPhoneStateListener = new MyPhoneStateListener();
      		telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
      	}
      
      	/**
      	 * 不获取来电号码
      	 */
      	private void getIncomingCallCancel() {
      		telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
      	}
      
      	/**
      	 * 电话状态监听器
      	 */
      	class MyPhoneStateListener extends PhoneStateListener {
      
      		@Override
      		public void onCallStateChanged(int state, String incomingNumber) {
      			super.onCallStateChanged(state, incomingNumber);
      
      			incomingNumberFinal=incomingNumber;
      			switch (state) {
      				case TelephonyManager.CALL_STATE_RINGING://响铃
      			        	//之前这里写的有误感谢@Bug_liu的细心阅读并指出问题
                                            lastCallState=TelephonyManager.CALL_STATE_RINGING
                                             //自定义来电界面
      					break;
      
      				case TelephonyManager.CALL_STATE_OFFHOOK://通话状态
      
      					//呼入电话
      					if (lastCallState==TelephonyManager.CALL_STATE_RINGING){
      					   
      					    //自定义呼入界面
      					
      					}
      					//呼出电话
      					else{
      					   
      					    //自定义呼出界面
      				
      					}
      					lastCallState = TelephonyManager.CALL_STATE_OFFHOOK;
      
      					break;
      
      				case TelephonyManager.CALL_STATE_IDLE://无状态
      				    //无通话
      					if (lastCallState==TelephonyManager.CALL_STATE_IDLE){
      						
      					}
      					//通话挂断
      					else{
      						lastCallState = TelephonyManager.CALL_STATE_IDLE;
      						//自定义通话挂断界面
      					}
      					break;
      
      				default:
      					break;
      			}
      
      		}
      	}
      }
    

第四步:挂断电话和接听电话

因为android4.0以上版本把接听电话事件设置为系统权限,所以原来接听电话的方法已经不管用了 系统挂断电话的方法需要ITelephony这个类里的方法,但是这个方法是系统私有的,不能直接调用,所以在自己的工程里创建一个ITelephony.aidl文件(直接上网下载一个就好),放到com.android.internal.telephony这个包下(自己创建一个包)。ITelephony需要依赖NeighboringCellInfo这个类,同样的方式创建一个NeighboringCellInfo.aidl文件(网上下载),放到android.telephony这个包下(自己创建一个包)。 准备工作做好了

首先就是挂断电话功能:

/**
 * 拒绝接听
 */
public static void rejectCall(Context context) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        try {
            Method method = Class.forName("android.os.ServiceManager")
                    .getMethod("getService", String.class);
            IBinder binder = (IBinder) method.invoke(null, new Object[]{Context.TELEPHONY_SERVICE});
            ITelephony telephony = ITelephony.Stub.asInterface(binder);
            telephony.endCall();
        } catch (NoSuchMethodException e) {
            Log.d("TAG", "", e);
        } catch (ClassNotFoundException e) {
            Log.d("TAG", "", e);
        } catch (Exception e) {

        }
    }else{
        TelephonyManager mTelMgr = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
        Class<TelephonyManager> c = TelephonyManager.class;
        try {
            Method getITelephonyMethod = c.getDeclaredMethod("getITelephony", (Class[]) null);
            getITelephonyMethod.setAccessible(true);
            ITelephony iTelephony = null;
            System.out.println("End call.");
            iTelephony = (ITelephony) getITelephonyMethod.invoke(mTelMgr, (Object[]) null);
            iTelephony.endCall();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Fail to answer ring call.");
        }
    }
}

接下来就是接听电话功能,因为android4.0以上版本把接听电话事件设置为系统权限,但是4.0以上版本有个方法,那就是可以通过耳机来接听电话的方法没有设置为系统权限(不知道算不算是一个bug)。所以可以模拟耳机接听电话的方式来接通电话,代码如下 注:部分手机(例如小米)用此方法无效,因为手机厂商修改了耳机接听电话的功能权限,比如小米手机,只能用小米特有的耳机接听,如果谁有解决的方法,请在下面留言,万万万分感谢^^

/**
 * 接听电话
 */
public static void acceptCall(Context context) {
    try {
        Method method = Class.forName("android.os.ServiceManager")
                .getMethod("getService", String.class);
        IBinder binder = (IBinder) method.invoke(null, new Object[]{Context.TELEPHONY_SERVICE});
        ITelephony telephony = ITelephony.Stub.asInterface(binder);
        telephony.answerRingingCall();
    } catch (Exception e) {
        LogUtils.e("TAG", "for version 4.1 or larger");
        acceptCall_4_1(context);
    }
}

/**
 * 4.1版本以上接听电话
 */
public static void acceptCall_4_1(Context context) {
    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    ;
    //模拟无线耳机的按键来接听电话
    // for HTC devices we need to broadcast a connected headset
    boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER)
            && !audioManager.isWiredHeadsetOn();
    if (broadcastConnected) {
        broadcastHeadsetConnected(context, false);
    }
    try {
        try {
            Runtime.getRuntime().exec("input keyevent " +
                    Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));
        } catch (IOException e) {
            // Runtime.exec(String) had an I/O problem, try to fall back
            String enforcedPerm = "android.permission.CALL_PRIVILEGED";
            Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                    Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
                            KeyEvent.KEYCODE_HEADSETHOOK));
            Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                    Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
                            KeyEvent.KEYCODE_HEADSETHOOK));
            context.sendOrderedBroadcast(btnDown, enforcedPerm);
            context.sendOrderedBroadcast(btnUp, enforcedPerm);
        }
    } finally {
        if (broadcastConnected) {
            broadcastHeadsetConnected(context, false);
        }
    }
}

private static void broadcastHeadsetConnected(Context context, boolean connected) {
    Intent i = new Intent(Intent.ACTION_HEADSET_PLUG);
    i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
    i.putExtra("state", connected ? 1 : 0);
    i.putExtra("name", "mysms");
    try {
        context.sendOrderedBroadcast(i, null);
    } catch (Exception e) {
    }
}

第五步:打开/关闭扬声器功能

android系统扬声器需要使用到音频管理器(AudioManager),具体实现方式如下:

添加权限:<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

代码如下(工作项目中没有用到扬声器,但是亲测一下,是有效的)

/**
 * 打开扬声器
 */
public static void openSpeaker(Context context) {
	try {
		AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
		audioManager.setMode(AudioManager.ROUTE_SPEAKER);
		currVolume = audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
		if (!audioManager.isSpeakerphoneOn()) {
			audioManager.setMode(AudioManager.MODE_IN_CALL);
			audioManager.setSpeakerphoneOn(true);
			audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
					audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL),
					AudioManager.STREAM_VOICE_CALL);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

public static void toggleSpeaker(Context context) {
	AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
	am.setMode(AudioManager.MODE_IN_CALL);
	am.setSpeakerphoneOn(!am.isSpeakerphoneOn());
}

/**
 * 关闭扬声器
 */
public static void closeSpeaker(Context context) {
	try {
		AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
		if (audioManager != null) {
			if (audioManager.isSpeakerphoneOn()) {
				audioManager.setSpeakerphoneOn(false);
				audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, currVolume,
						AudioManager.STREAM_VOICE_CALL);
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

结束语

谢谢大家的阅读,如果有不足之处请大家指出,斧正。