前一篇文章已经提供了电话号码的归属地查询功能,现在要做的,就是在打电话和来电显示的时候,显示一个电话归属地提示框,就像这样:
感觉很简单是不是,No,这个还费了一点功夫,首先是监听来电、去电,同时这个提示框是可以拖动的,而且这个提示框可以自定义风格,可以设置它的颜色,是否显示等,所以,并不简单。
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe
首先创建一个服务:AddressService,然后在里面监听来电,代码如下:
//获取电话管理器
mTM = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
mPhoneListener = new PhoneListener(getApplicationContext());
mTM.listen(mPhoneListener, PhoneStateListener.LISTEN_CALL_STATE);//监听来电
首先获得电话管理器,然后通过调用它的 listen() 方法开始监听,第一个参数是我们的回调方法,第二个参数是我们要监听的事件。PhoneListener 是需要自己实现的一个类,因为我们需要重写其中的 onCallStateChanged() 方法,具体代码如下:
/** * 电话状态监听 * * Created by XWdoor on 2016/3/11 011 13:50. * 博客:http://blog.csdn.net/xwdoor */
public class PhoneListener extends PhoneStateListener {
private Context mContext;
private View mView;
private WindowManager mWM;
public PhoneListener(Context context) {
mContext = context;
}
// 电话状态发生变化
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state){
case TelephonyManager.CALL_STATE_RINGING://电话铃响
String address = AddressQuery.getAddress(mContext, incomingNumber);
showAddressBox(address);
break;
case TelephonyManager.CALL_STATE_OFFHOOK://电话摘机
break;
case TelephonyManager.CALL_STATE_IDLE://电话空闲
closeAddressBox();
break;
}
}
/** * 显示电话归属地提示框 * * @param address */
public void showAddressBox(String address){
Log.i(BaseActivity.TAG_LOG,"电话归属地-->"+address);
}
/** * 关闭电话归属地提示框 */
public void closeAddressBox(){
}
}
现在主要实现了代码逻辑,功能还没有做,先不用管它,可以打印一条日志。这样我们就可以监听来电了,并且在 showAddressBox() 方法中弹出提示框。
监听打电话与监听来电略有不同,需要监听广播。创建一个广播 CallReceiver,并传入 PhoneListener 对象,因为我们要用到归属地提示框的显示与关闭,代码如下:
/** * 打电话监听:去电监听 * * Created by XWdoor on 2016/3/11 011 13:44. * 博客:http://blog.csdn.net/xwdoor */
public class CallReceiver extends BroadcastReceiver {
private final PhoneListener mPhoneListener;
public CallReceiver(PhoneListener phoneListener) {
this.mPhoneListener = phoneListener;
}
@Override
public void onReceive(Context context, Intent intent) {
String number = getResultData();//获取电话号码
String address = AddressQuery.getAddress(context, number);
//ToastUtils.showToast(context, "去电地址-->" + address + ",去电号码-->" + number);
mPhoneListener.showAddressBox(address);
}
}
当然,去电监听是需要权限的:<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
创建完广播后,因为是否显示归属地提示框是可以进行设置的,所以该广播不能在清单文件中进行注册,那样就不能停止了,我们需要在代码中进行注册,我选择在服务 AddressService 中进行注册,代码如下:
//监听去电
mCallReceiver = new CallReceiver(mPhoneListener);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
registerReceiver(mCallReceiver,filter);
当然,在服务停止的时候,我们需要注销监听:
@Override
public void onDestroy() {
super.onDestroy();
//取消监听来电
mTM.listen(mPhoneListener,PhoneStateListener.LISTEN_NONE);
//取消监听去电
unregisterReceiver(mCallReceiver);
mCallReceiver = null;
}
在 activity_setting.xml 文件中增加一个 item,用于设置归属地服务是否开启:
<net.xwdoor.mobilesafe.view.SettingItemView android:id="@+id/siv_address" android:layout_width="match_parent" android:layout_height="wrap_content" xwdoor:stitle="电话归属地显示设置" xwdoor:desc_on="归属地显示已开启" xwdoor:desc_off="归属地显示已关闭"/>
后台代码如下:
/** * 初始化归属地设置 */
private void initAddress() {
final SettingItemView sivAddress = (SettingItemView) findViewById(R.id.siv_address);
// 根据服务是否运行来更新checkbox
boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this,
"net.xwdoor.mobilesafe.service.AddressService");
sivAddress.setChecked(serviceRunning);
sivAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sivAddress.setChecked(!sivAddress.isChecked());
Intent service = new Intent(SettingActivity.this, AddressService.class);
if(sivAddress.isChecked()){
startService(service);//开启电话归属地显示服务
}else {
stopService(service);//关闭电话归属地服务
}
}
});
}
首先判断该服务是否已经运行,然后设置 item 的状态,判断服务是否运行的代码如下所示。然后设置 item 的点击事件,根据用户的选择来动态启动或关闭归属地显示服务。
/** * 判断服务是否正在运行 * @param context * @param serviceName * @return */
public static boolean isServiceRunning(Context context, String serviceName) {
//获取活动管理器
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取当前正在运行的服务,最多返回100条记录
List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(100);
for(ActivityManager.RunningServiceInfo service : runningServices){
// 获取服务名称,并判断
if(serviceName.equals(service.service.getClassName())){
return true;
}
}
return false;
}
以上的工作就可以正常打印日志了,但是还没有具体的提示框出现,现在就来实现我们的提示框,实现原理参照系统的 Toast,因为只有它可以在另一个应用运行时进行显示,额,可以这么说,Toast 显示的时候,可以运行其他任何 app。首先创建一个布局文件,里面就只有一个 TextView,这里就不写了,相信写了那么多的 UI 布局,这个可以轻松搞定。
然后,我么就可以实现 PhoneListener 中的两个方法了:
/** * 显示电话归属地提示框 * 需要权限:android.permission.SYSTEM_ALERT_WINDOW * * @param address */
public void showAddressBox(String address){
Log.i(BaseActivity.TAG_LOG,"电话归属地-->"+address);
//窗口管理器, 系统最顶级的界面布局, 所有东西都展示在窗口上,activity,状态栏
mWM = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
//初始化布局参数
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_PHONE;// 提高类型级别,保证可以触摸移动
params.gravity = Gravity.CENTER;// 将重心设置在左上方位置,和屏幕坐标体系重合,方便修改窗口的位置
// 初始化布局
mView = View.inflate(mContext, R.layout.dialog_address_box, null);
TextView tvAddress = (TextView) mView.findViewById(R.id.tv_address);
tvAddress.setText(address);
// 给窗口添加布局
mWM.addView(mView, params);
}
/** * 关闭电话归属地提示框 */
public void closeAddressBox(){
if (mWM != null && mView != null) {
mWM.removeView(mView);// 电话挂断后,移除窗口布局
}
}
别看这么复杂,其中初始化布局参数那一段代码都是从系统 Toast 的源码中复制过来的,所以,呵呵。有了以上的代码,运行效果就跟文章图片中展示的一样了。
我觉得这篇文章也是属于干货,学到的知识点有:
由于篇幅的限制,还有一部分功能没有实现,就是我们的归属地的自定义风格,可拖拽等效果,没办法,只能放到下篇了。
关于项目相关文章,请访问:
项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe