Android项目:手机安全卫士(10)—— 电话号码归属地显示

1 概述

前一篇文章已经提供了电话号码的归属地查询功能,现在要做的,就是在打电话和来电显示的时候,显示一个电话归属地提示框,就像这样:

android10 获取来电号码 安卓10来电归属地_Android

感觉很简单是不是,No,这个还费了一点功夫,首先是监听来电、去电,同时这个提示框是可以拖动的,而且这个提示框可以自定义风格,可以设置它的颜色,是否显示等,所以,并不简单。

关于项目相关文章,请访问:

项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe

2 监听来电

首先创建一个服务: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.
     * 博客:
     */
    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() 方法中弹出提示框。

3 监听去电(打电话)

监听打电话与监听来电略有不同,需要监听广播。创建一个广播 CallReceiver,并传入 PhoneListener 对象,因为我们要用到归属地提示框的显示与关闭,代码如下:

/**
     * 打电话监听:去电监听
     *
     * Created by XWdoor on 2016/3/11 011 13:44.
     * 博客:
     */
    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;
    }

4 开启服务

在 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;
    }

5 显示归属地提示框

以上的工作就可以正常打印日志了,但是还没有具体的提示框出现,现在就来实现我们的提示框,实现原理参照系统的 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 的源码中复制过来的,所以,呵呵。有了以上的代码,运行效果就跟文章图片中展示的一样了。

6 总结

我觉得这篇文章也是属于干货,学到的知识点有:

  • 监听来电,获取来电号码,实现电话拦截
  • 监听去电,记录行为
  • WindowManager、ActivityManager、TelephonyManager 的使用

由于篇幅的限制,还有一部分功能没有实现,就是我们的归属地的自定义风格,可拖拽等效果,没办法,只能放到下篇了。

关于项目相关文章,请访问:

项目源码地址(实时更新):https://github.com/xwdoor/MobileSafe