Android 来电悬浮框的探索
基于项目中需要,监听系统来电弹出自定义的悬浮框,综合其他博主共享的资源,成功在项目中实现了这一功能。本着达则兼济天下的崇高理想,分享出来,以供参阅。
根据需求我把整个功能分割了以下几个部分:
- 监听系统来电的广播
- 接收广播处理通话状态
- 悬浮框界面
- 接听功能实现
- 挂断功能实现
监听系统来电的广播
AndroidManifest.xml中注册监听系统来电的广播之前需要添加权限:
<uses-permission
android:name="android.permission.READ_PHONE_STATE">
</uses-permission>
注册广播:
<receiver android:name="com.softi.cs.ui.receiver.CSCallReceiver" >
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
<action android:name="call.cs.call.ending.action" />
</intent-filter>
</receiver>
接收广播处理通话状态
接收广播的类:CSCallReceiver .java
public class CSCallReceiver extends BroadcastReceiver {
private Context mContext;
private boolean incomingFlag=false;
private String incoming_number="";
private String TAG="CSCallReceiver";
private String phoneNumber;
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
TelephonyManager sTelephoneyManager = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
//如果是拨打电话
if(intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)){ //外呼不做处理
incomingFlag = false;
phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.i(TAG, "call OUT:"+phoneNumber);
}else if(intent.getAction().equals("call.cs.call.ending.action")){//处理挂断,在PhoneActivity接收广播 并延时4秒关闭界面,为了遮盖系统电话关闭页面
sendCloseBroadCast();
}else{
//如果是来电 ,开启悬浮页面。
TelephonyManager tm =
(TelephonyManager)context.getSystemService(Service.TELEPHONY_SERVICE);
switch (tm.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING:
incomingFlag = true;//标识当前是来电
incoming_number = intent.getStringExtra("incoming_number");
Intent myIntent =new Intent(context,PhoneActivity.class);
myIntent.putExtra("incoming_number", incoming_number);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
Log.i(TAG, "RINGING :"+ incoming_number);
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if(incomingFlag){
Log.i(TAG, "incoming ACCEPT :"+ incoming_number);
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if(incomingFlag){
Log.i(TAG, "incoming IDLE");
}
break;
}
}
}
private void sendCloseBroadCast() {
Intent intent = new Intent("com.chinasofti.rcs.finishcall");
mContext.sendBroadcast(intent);
}
}
悬浮框界面
收到来电广播,弹出悬浮框界面:PhoneActivity.java
public class PhoneActivity extends Activity implements OnClickListener {
private MyBroadcastReceiver mBroadcastReceiver;
public static int OVERLAY_PERMISSION_REQ_CODE = 1234;
private static final String LOG_TAG = "PhoneActivity";
private static View mView = null;
private static WindowManager mWindowManager = null;
private static Context mContext = null;
public static Boolean isShown = false;
TelephonyManager telMgr;
private Button declinButton;
private Button answerButton;
private static final String MANUFACTURER_HTC = "HTC";
private AudioManager audioManager;
private ImageView incomingThumb;
private TextView callName;
private String phoneNumber;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
if (what == 0) {
hidePopupWindow();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_cs_main);
Intent intent = getIntent();
phoneNumber = intent.getStringExtra("incoming_number");
mBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.chinasofti.rcs.finishcall");
registerReceiver(mBroadcastReceiver, intentFilter);
telMgr = (TelephonyManager) this.getSystemService(Service.TELEPHONY_SERVICE);
audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
askForPermission();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mBroadcastReceiver);
}
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(LOG_TAG, "MyBroadcastReceiver onReceive ");
handler.sendEmptyMessageDelayed(0, 4000);
}
}
/**
* 请求用户给予悬浮窗的权限
*/
public void askForPermission() {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(PhoneActivity.this, "当前无权限,请授权!", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
} else {
showPopupWindow();
}
}
/**
* 显示弹出框
*
* @param context
* @param view
*/
public void showPopupWindow() {
if (isShown) {
Log.i(LOG_TAG, "return cause already shown");
return;
}
isShown = true;
Log.i(LOG_TAG, "showPopupWindow");
// 获取应用的Context
mContext = this.getApplicationContext();
// 获取WindowManager
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mView = setUpView(this);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// 类型
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
// WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
// 设置flag
int flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
// int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
// | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
params.flags = flags;
// 不设置这个弹出框的透明遮罩显示为黑色
params.format = PixelFormat.TRANSLUCENT;
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
// 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
// 不设置这个flag的话,home页的划屏会有问题
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
mWindowManager.addView(mView, params);
Log.i(LOG_TAG, "add view");
}
/**
* 隐藏弹出框
*/
public void hidePopupWindow() {
Log.i(LOG_TAG, "hide " + isShown + ", " + mView);
if (isShown && null != mView) {
Log.i(LOG_TAG, "hidePopupWindow");
mWindowManager.removeView(mView);
isShown = false;
this.finish();
}
}
private View setUpView(Context context) {
Log.i(LOG_TAG, "setUp view");
View view = LayoutInflater.from(context).inflate(R.layout.layout_cs_incomming, null);
declinButton = (Button) view.findViewById(R.id.call_cs_decline);
answerButton = (Button) view.findViewById(R.id.call_cs_answer);
incomingThumb = (ImageView) view.findViewById(R.id.cs_incoming_thumb);
callName = (TextView)view.findViewById(R.id.cs_call_name);
declinButton.setOnClickListener(this);
answerButton.setOnClickListener(this);
//loadPhotoAndName();加载用户头像,和名称
return view;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.call_cs_decline:
telEndCall();
handler.sendEmptyMessageDelayed(0, 4000);
break;
case R.id.call_cs_answer:
acceptCall();
break;
default:
break;
}
}
private void telEndCall() {
Class<TelephonyManager> c = TelephonyManager.class;
try {
Method getITelephonyMethod = c.getDeclaredMethod("getITelephony", (Class[]) null);
getITelephonyMethod.setAccessible(true);
ITelephony iTelephony = null;
iTelephony = (ITelephony) getITelephonyMethod.invoke(telMgr, (Object[]) null);
iTelephony.endCall();
} catch (Exception e) {
Log.e(LOG_TAG, "Fail to answer ring call.", e);
}
}
public void acceptCall() {
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) {
Log.e(LOG_TAG, "for version 4.1 or larger");
acceptCall_4_1();
}
}
public void acceptCall_4_1() {
// 模拟无线耳机的按键来接听电话
// for HTC devices we need to broadcast a connected headset
boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER) && !audioManager.isWiredHeadsetOn();
if (broadcastConnected) {
broadcastHeadsetConnected(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));
this.sendOrderedBroadcast(btnDown, enforcedPerm);
this.sendOrderedBroadcast(btnUp, enforcedPerm);
}
} finally {
if (broadcastConnected) {
broadcastHeadsetConnected(false);
}
}
}
private void broadcastHeadsetConnected(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 {
this.sendOrderedBroadcast(i, null);
} catch (Exception e) {
}
}
/**
* 用户返回
*
* @param requestCode
* @param resultCode
* @param data
*/
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(PhoneActivity.this, "权限授予失败,无法开启悬浮窗", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(PhoneActivity.this, "权限授予成功!", Toast.LENGTH_SHORT).show();
showPopupWindow();
}
}
}
}
接听功能实现
接听和挂断功能来源于其他博主
public void acceptCall() {
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) {
Log.e(LOG_TAG, "for version 4.1 or larger");
acceptCall_4_1();
}
}
public void acceptCall_4_1() {
// 模拟无线耳机的按键来接听电话
// for HTC devices we need to broadcast a connected headset
boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER) && !audioManager.isWiredHeadsetOn();
if (broadcastConnected) {
broadcastHeadsetConnected(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));
this.sendOrderedBroadcast(btnDown, enforcedPerm);
this.sendOrderedBroadcast(btnUp, enforcedPerm);
}
} finally {
if (broadcastConnected) {
broadcastHeadsetConnected(false);
}
}
}
挂断功能实现
private void telEndCall() {
Class<TelephonyManager> c = TelephonyManager.class;
try {
Method getITelephonyMethod = c.getDeclaredMethod("getITelephony", (Class[]) null);
getITelephonyMethod.setAccessible(true);
ITelephony iTelephony = null;
iTelephony = (ITelephony) getITelephonyMethod.invoke(telMgr, (Object[]) null);
iTelephony.endCall();
} catch (Exception e) {
Log.e(LOG_TAG, "Fail to answer ring call.", e);
}
}