安卓_手机卫士_第七,八天(火箭特效,去电监听,拦截短信电话黑名单功能(listview优化及分页,动态短信监听,aidl挂断电话,内容观察者删除来电记录))

火箭特效


效果展示

这里写图片描述
这里写图片描述

实现思路

1,火箭游离在activity,activity不论开启还是关闭,不影响小火箭代码逻辑,火箭写在服务中
2,火箭挂载载窗体上
3,喷射火焰,两张图片做轮训切换展示
4,火箭可以被拖拽
5,火箭拖拽到指定区域的时候放手(抬起)才可以被发射
6,尾气动画效果(开启一个透明的activity尾气图片使用渐变动画))

火箭的服务
代码

public class RocketService extends Service {
    private WindowManager mWM;
    private int mScreenHeight;
    private int mScreenWidth;
    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
    private View mRocketView;
    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            params.y = (Integer) msg.obj;
            //告知窗体更新火箭view的所在位置
            mWM.updateViewLayout(mRocketView, params);
        };
    };


    private WindowManager.LayoutParams params;  @Override
    public void onCreate() {
        //获取窗体对象
        mWM = (WindowManager) getSystemService(WINDOW_SERVICE);

        mScreenHeight = mWM.getDefaultDisplay().getHeight();
        mScreenWidth = mWM.getDefaultDisplay().getWidth();

        //开启火箭
        showRocket();
        super.onCreate();
    }
    private void showRocket() {
        params = mParams;
        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.setTitle("Toast");

        //指定吐司的所在位置(将吐司指定在左上角)
        params.gravity = Gravity.LEFT+Gravity.TOP;

        //定义吐司所在的布局,并且将其转换成view对象,添加至窗体(权限)

        mRocketView = View.inflate(this, R.layout.rocket_view, null);

        ImageView iv_rocket = (ImageView) mRocketView.findViewById(R.id.iv_rocket);
        AnimationDrawable animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
        animationDrawable.start();

        mWM.addView(mRocketView, params);

        mRocketView.setOnTouchListener(new OnTouchListener() {
            private int startX;
            private int startY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startX = (int) event.getRawX();
                    startY = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int moveX = (int) event.getRawX();
                    int moveY = (int) event.getRawY();

                    int disX = moveX-startX;
                    int disY = moveY-startY;

                    params.x = params.x+disX;
                    params.y = params.y+disY;

                    //容错处理
                    if(params.x<0){
                        params.x = 0;
                    }

                    if(params.y<0){
                        params.y=0;
                    }

                    if(params.x>mScreenWidth-mRocketView.getWidth()){
                        params.x = mScreenWidth-mRocketView.getWidth();
                    }

                    if(params.y>mScreenHeight-mRocketView.getHeight()-22){
                        params.y = mScreenHeight-mRocketView.getHeight()-22;
                    }

                    //告知窗体吐司需要按照手势的移动,去做位置的更新
                    mWM.updateViewLayout(mRocketView, params);

                    startX = (int) event.getRawX();
                    startY = (int) event.getRawY();

                    break;
                case MotionEvent.ACTION_UP:
                    if(params.x>100 && params.x<200 && params.y>350){
                        //发射火箭
                        sendRocket();
                        //开启产生尾气的activity
                        Intent intent = new Intent(getApplicationContext(), BackgroundActivity.class);
                        //开启火箭后,关闭了唯一的activity对应的任务栈,所以在此次需要告知新开启的activity开辟一个新的任务栈
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                    }
                    break;
                }
                return true;
            }
        });
    }

    protected void sendRocket() {
        //在向上的移动过程中,一直去减少y轴的大小,直到减少为0为止
        //在主线程中不能去睡眠,可能会导致主线程阻塞
        new Thread(){
            public void run() {
                for(int i=0;i<11;i++){
                    int height = 350-i*35;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Message msg = Message.obtain();
                    msg.obj = height;
                    mHandler.sendMessage(msg);
                }
            };
        }.start();


    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onDestroy() {
        if(mWM!=null && mRocketView!=null){
            mWM.removeView(mRocketView);
        }
        super.onDestroy();
    }
}

火箭图标的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/iv_rocket"
        android:background="@drawable/rocket_bg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout

两张切换的背景图

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- 第一张图片,火焰较短 -->
    <item android:drawable="@drawable/desktop_rocket_launch_1"
        android:duration="200"/>
    <!-- 第二张图片,火焰较长 -->
    <item android:drawable="@drawable/desktop_rocket_launch_2"
        android:duration="200"/>
</animation-list>

尾气activity

注册透明activity
<activity android:name="com.example.rocketman.BackgroundActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>

布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <ImageView 
        android:id="@+id/iv_bottom"
        android:layout_alignParentBottom="true"
        android:background="@drawable/desktop_smoke_m"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ImageView 
        android:id="@+id/iv_top"
        android:layout_above="@id/iv_bottom"
        android:background="@drawable/desktop_smoke_t"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

代码

public class BackgroundActivity extends Activity {
    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            finish();
        };
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_background);

        ImageView iv_top = (ImageView) findViewById(R.id.iv_top);
        ImageView iv_bottom = (ImageView) findViewById(R.id.iv_bottom);

        AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
        alphaAnimation.setDuration(500);
        iv_top.startAnimation(alphaAnimation);
        iv_bottom.startAnimation(alphaAnimation);

        mHandler.sendEmptyMessageDelayed(0, 1000);
    }
}

去电监听显示归属地


在来电监听的服务中,注册去电的广播接收者
在接收的方法中根据电话号码,展示自定义toast(同来电显示归属地)

去电广播监听的权限
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

代码

        //监听播出电话的广播过滤条件(权限)
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
        //创建广播接受者
        mInnerOutCallReceiver = new InnerOutCallReceiver();
        registerReceiver(mInnerOutCallReceiver, intentFilter);
    class InnerOutCallReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            //接收到此广播后,需要显示自定义的吐司,显示播出归属地号码
            //获取播出电话号码的字符串
            String phone = getResultData();
            showToast(phone);
        }
    }

服务销毁时需要反注册广播接收者

    @Override
    public void onDestroy() {
        //取消对电话状态的监听(开启服务的时候监听电话的对象)
        if(mTM!=null && mPhoneStateListener!=null){
            mTM.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
        }
        if(mInnerOutCallReceiver!=null){
            //去电广播接受者的注销过程
            unregisterReceiver(mInnerOutCallReceiver);
        }
        super.onDestroy();
    }

拦截短信电话黑名单功能


功能效果
这里写图片描述这里写图片描述

黑名单相关数据库创建

代码

public class BlackNumberOpenHelper extends SQLiteOpenHelper {
    public BlackNumberOpenHelper(Context context) {
        super(context, "blacknumber.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建数据库中表的方法
        db.execSQL("create table blacknumber " +
                "(_id integer primary key autoincrement , phone varchar(20), mode varchar(5));");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

Dao 增删改查

创建单例Dao 代码

public class BlackNumberDao {
    private BlackNumberOpenHelper blackNumberOpenHelper;
    //BlackNumberDao单例模式
    //1,私有化构造方法
    private BlackNumberDao(Context context){
        //创建数据库已经其表机构
        blackNumberOpenHelper = new BlackNumberOpenHelper(context);
    }
    //2,声明一个当前类的对象
    private static BlackNumberDao blackNumberDao = null;
    //3,提供一个静态方法,如果当前类的对象为空,创建一个新的
    public static BlackNumberDao getInstance(Context context){
        if(blackNumberDao == null){
            blackNumberDao = new BlackNumberDao(context);
        }
        return blackNumberDao;
    }

    /**增加一个条目
     * @param phone 拦截的电话号码
     * @param mode  拦截类型(1:短信   2:电话    3:拦截所有(短信+电话))
     */
    public void insert(String phone,String mode){
        //1,开启数据库,准备做写入操作
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put("phone", phone);
        values.put("mode", mode);
        db.insert("blacknumber", null, values);

        db.close();
    }

    /**从数据库中删除一条电话号码
     * @param phone 删除电话号码
     */
    public void delete(String phone){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();

        db.delete("blacknumber", "phone = ?", new String[]{phone});

        db.close();
    }

    /**
     * 根据电话号码去,更新拦截模式
     * @param phone 更新拦截模式的电话号码
     * @param mode  要更新为的模式(1:短信    2:电话    3:拦截所有(短信+电话)
     */
    public void update(String phone,String mode){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();

        ContentValues contentValues = new ContentValues();
        contentValues.put("mode", mode);

        db.update("blacknumber", contentValues, "phone = ?", new String[]{phone});

        db.close();
    }

    /**
     * @return  查询到数据库中所有的号码以及拦截类型所在的集合
     */
    public List<BlackNumberInfo> findAll(){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();

        Cursor cursor = db.query("blacknumber", new String[]{"phone","mode"}, null, null, null, null, "_id desc");
        List<BlackNumberInfo> blackNumberList = new ArrayList<BlackNumberInfo>();
        while(cursor.moveToNext()){
            BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
            blackNumberInfo.phone = cursor.getString(0);
            blackNumberInfo.mode = cursor.getString(1);
            blackNumberList.add(blackNumberInfo);
        }
        cursor.close();
        db.close();

        return blackNumberList;
    }

    /**
     * 每次查询20条数据
     * @param index 查询的索引值
     */
    public List<BlackNumberInfo> find(int index){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();

        Cursor cursor = db.rawQuery("select phone,mode from blacknumber order by _id desc limit ?,20;", new String[]{index+""});

        List<BlackNumberInfo> blackNumberList = new ArrayList<BlackNumberInfo>();
        while(cursor.moveToNext()){
            BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
            blackNumberInfo.phone = cursor.getString(0);
            blackNumberInfo.mode = cursor.getString(1);
            blackNumberList.add(blackNumberInfo);
        }
        cursor.close();
        db.close();

        return blackNumberList;
    }

    /**
     * @return  数据库中数据的总条目个数,返回0代表没有数据或异常
     */
    public int getCount(){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
        int count = 0;
        Cursor cursor = db.rawQuery("select count(*) from blacknumber;", null);
        if(cursor.moveToNext()){
            count = cursor.getInt(0);
        }

        cursor.close();
        db.close();
        return count;
    }

    /**
     * @param phone 作为查询条件的电话号码
     * @return  传入电话号码的拦截模式 1:短信    2:电话    3:所有    0:没有此条数据
     */
    public int getMode(String phone){
        SQLiteDatabase db = blackNumberOpenHelper.getWritableDatabase();
        int mode = 0;
        Cursor cursor = db.query("blacknumber", new String[]{"mode"}, "phone = ?", new String[]{phone}, null, null,null);
        if(cursor.moveToNext()){
            mode = cursor.getInt(0);
        }

        cursor.close();
        db.close();
        return mode;
    }
}

Junit单元测试Dao

清单文件增加配置

     <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.itheima.mobilesafe74" />

applicatin下增加
<uses-library android:name="android.test.runner" />

test类代码
继承AndroidTestCase 可获得context上下文环境

public class Test extends AndroidTestCase {
    public void insert(){
        BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
        for(int i=0;i<100;i++){
            if(i<10){
                dao.insert("1860000000"+i, 1+new Random().nextInt(3)+"");
            }else{
                dao.insert("186000000"+i, 1+new Random().nextInt(3)+"");
            }
        }
    }

    public void delete(){
        BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
        dao.delete("110");
    }

    public void update(){
        BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
        dao.update("110", "2");
    }

    public void findAll(){
        BlackNumberDao dao = BlackNumberDao.getInstance(getContext());
        List<BlackNumberInfo> blackNumberInfoList = dao.findAll();
    }
}

黑名单页面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView 
            style="@style/TitleStyle"
            android:gravity="left"
            android:text="黑名单管理"/>  
        <Button
            android:id="@+id/bt_add"
            android:text="添加"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>      
    </RelativeLayout>
    <ListView 
        android:id="@+id/lv_blacknumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </ListView>
</LinearLayout>

listview,adapter的view布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" 
    android:padding="5dp">
    <TextView
        android:id="@+id/tv_phone"
        android:text="拦截号码"
        android:textColor="#000"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tv_mode"
        android:layout_below="@id/tv_phone"
        android:text="拦截类型"
        android:textColor="#000"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <ImageView 
        android:id="@+id/iv_delete"
        android:background="@drawable/selector_blacknumber_delete_btn_bg"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

黑名单activity

初始化数据

    private void initData() {
        //获取数据库中所有电话号码
        new Thread(){
            public void run() {
                //1,获取操作黑名单数据库的对象
                mDao = BlackNumberDao.getInstance(getApplicationContext());
                //2,查询部分数据
                mBlackNumberList = mDao.find(0);
                mCount = mDao.getCount();

                //3,通过消息机制告知主线程可以去使用包含数据的集合
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }

初始化UI

listview 分页

监听listview的滚动条事件

        //监听其滚动状态
        lv_blacknumber.setOnScrollListener(new OnScrollListener() {
            //滚动过程中,状态发生改变调用方法()
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
//              OnScrollListener.SCROLL_STATE_FLING 飞速滚动
//              OnScrollListener.SCROLL_STATE_IDLE   空闲状态
//              OnScrollListener.SCROLL_STATE_TOUCH_SCROLL  拿手触摸着去滚动状态

                if(mBlackNumberList!=null){
                    //条件一:滚动到停止状态
                    //条件二:最后一个条目可见(最后一个条目的索引值>=数据适配器中集合的总条目个数-1)
                    if(scrollState == OnScrollListener.SCROLL_STATE_IDLE
                            && lv_blacknumber.getLastVisiblePosition()>=mBlackNumberList.size()-1
                            && !mIsLoad){
                        /*mIsLoad防止重复加载的变量
                        如果当前正在加载mIsLoad就会为true,本次加载完毕后,再将mIsLoad改为false
                        如果下一次加载需要去做执行的时候,会判断上诉mIsLoad变量,是否为false,如果为true,就需要等待上一次加载完成,将其值
                        改为false后再去加载*/

                        //如果条目总数大于集合大小的时,才可以去继续加载更多
                        if(mCount>mBlackNumberList.size()){
                            //加载下一页数据
                            new Thread(){
                                public void run() {
                                    //1,获取操作黑名单数据库的对象
                                    mDao = BlackNumberDao.getInstance(getApplicationContext());
                                    //2,查询部分数据
                                    List<BlackNumberInfo> moreData = mDao.find(mBlackNumberList.size());
                                    //3,添加下一页数据的过程
                                    mBlackNumberList.addAll(moreData);
                                    //4,通知数据适配器刷新
                                    mHandler.sendEmptyMessage(0);
                                }
                            }.start();
                        }
                    }
                }

            }

            //滚动过程中调用方法
            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {

            }
        });
    }
使用convertView与ViewHolder优化listview

convertView复用机制
这里写图片描述

viewholder
利用converView的setTag方法,存储一个保存了item中View对象的引用
这里写图片描述
代码

    private List<BlackNumberInfo> mBlackNumberList;
    private MyAdapter mAdapter;
    private int mode = 1;
    private boolean mIsLoad = false;
    private int mCount;

    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            //4,告知listView可以去设置数据适配器
            if(mAdapter == null){
                mAdapter = new MyAdapter();
                lv_blacknumber.setAdapter(mAdapter);
            }else{
                mAdapter.notifyDataSetChanged();
            }
        };
    };

    class MyAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return mBlackNumberList.size();
        }

        @Override
        public Object getItem(int position) {
            return mBlackNumberList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
//          View view = null;
/*          if(convertView == null){
                view = View.inflate(getApplicationContext(), R.layout.listview_blacknumber_item, null);
            }else{
                view = convertView;
            }*/

            //1,复用convertView

            //复用viewHolder步骤一
            ViewHolder holder = null;
            if(convertView == null){
                convertView = View.inflate(getApplicationContext(), R.layout.listview_blacknumber_item, null);
                //2,减少findViewById()次数
                //复用viewHolder步骤三
                holder = new ViewHolder();
                //复用viewHolder步骤四
                holder.tv_phone = (TextView) convertView.findViewById(R.id.tv_phone);
                holder.tv_mode = (TextView)convertView.findViewById(R.id.tv_mode);
                holder.iv_delete = (ImageView)convertView.findViewById(R.id.iv_delete);
                //复用viewHolder步骤五
                convertView.setTag(holder);
            }else{
                holder = (ViewHolder) convertView.getTag();
            }

            holder.iv_delete.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    //1,数据库删除
                    mDao.delete(mBlackNumberList.get(position).phone);
                    //2,集合中的删除
                    mBlackNumberList.remove(position);
                    //3,通知数据适配器刷新
                    if(mAdapter!=null){
                        mAdapter.notifyDataSetChanged();
                    }
                }
            });

            holder.tv_phone.setText(mBlackNumberList.get(position).phone);
            int mode = Integer.parseInt(mBlackNumberList.get(position).mode);
            switch (mode) {
            case 1:
                holder.tv_mode.setText("拦截短信");
                break;
            case 2:
                holder.tv_mode.setText("拦截电话");
                break;
            case 3:
                holder.tv_mode.setText("拦截所有");
                break;
            }
            return convertView;
        }
    }
    //复用viewHolder步骤二
    static class ViewHolder{
        TextView tv_phone;
        TextView tv_mode;
        ImageView iv_delete;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_blacknumber);

        initUI();
        initData();
    }
添加按钮保存弹窗dialog

弹窗布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView 
        style="@style/TitleStyle"
        android:background="#fcc"
        android:text="添加黑名单号码"/>
    <EditText 
        android:id="@+id/et_phone"
        android:hint="请输入拦截号码"
        android:inputType="phone"
        android:textColor="#000"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <RadioGroup
        android:id="@+id/rg_group" 
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <RadioButton 
            android:id="@+id/rb_sms"
            android:text="短信"
            android:textColor="#000"
            android:checked="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <RadioButton 
            android:id="@+id/rb_phone"
            android:text="电话"
            android:textColor="#000"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <RadioButton 
            android:id="@+id/rb_all"
            android:text="所有"
            android:textColor="#000"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </RadioGroup>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/bt_submit"
            android:text="确认"
            android:textColor="#000"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/bt_cancel"
            android:text="取消"
            android:textColor="#000"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</LinearLayout>

添加按钮添加点击事件展示对应弹窗代码

        bt_add.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialog();
            }
        });
    protected void showDialog() {
        Builder builder = new AlertDialog.Builder(this);

        final AlertDialog dialog = builder.create();
        View view = View.inflate(getApplicationContext(), R.layout.dialog_add_blacknumber, null);
        dialog.setView(view, 0, 0, 0, 0);

        final EditText et_phone = (EditText) view.findViewById(R.id.et_phone);
        RadioGroup rg_group = (RadioGroup) view.findViewById(R.id.rg_group);

        Button bt_submit = (Button) view.findViewById(R.id.bt_submit);
        Button bt_cancel = (Button)view.findViewById(R.id.bt_cancel);

        //监听其选中条目的切换过程
        rg_group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                switch (checkedId) {
                case R.id.rb_sms:
                    //拦截短信
                    mode = 1;
                    break;
                case R.id.rb_phone:
                    //拦截电话
                    mode = 2;
                    break;
                case R.id.rb_all:
                    //拦截所有
                    mode = 3;
                    break;
                }
            }
        });

        bt_submit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //1,获取输入框中的电话号码
                String phone = et_phone.getText().toString();
                if(!TextUtils.isEmpty(phone)){
                    //2,数据库插入当前输入的拦截电话号码
                    mDao.insert(phone, mode+""); 
                    //3,让数据库和集合保持同步(1.数据库中数据重新读一遍,2.手动向集合中添加一个对象(插入数据构建的对象))
                    BlackNumberInfo blackNumberInfo = new BlackNumberInfo();
                    blackNumberInfo.phone = phone;
                    blackNumberInfo.mode = mode+"";
                    //4,将对象插入到集合的最顶部
                    mBlackNumberList.add(0, blackNumberInfo);
                    //5,通知数据适配器刷新(数据适配器中的数据有改变了)
                    if(mAdapter!=null){
                        mAdapter.notifyDataSetChanged();
                    }
                    //6,隐藏对话框
                    dialog.dismiss();
                }else{
                    ToastUtil.show(getApplicationContext(), "请输入拦截号码");
                }
            }
        });

        bt_cancel.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        });

        dialog.show();
    }

拦截黑名单的短信与电话


拦截短信

短信在接受的时候,广播发送,监听广播接受者,拦截短信(有序),
动态注册短信广播接收者,将广播的优先级级别提高到最高 (1000)

拦截电话

有电话拨入,处于响铃状态,响铃状态通过代码去挂断电话(aidl,反射),拦截电话
挂断电话的API早期版本endCall()是可以使用的,现在不可以用了;但本身挂断电话这个功能是存在的
挂断电话号码的方法,放置在了aidl文件中名称为endCall
在此处去查看TelePhoneManager源码,去查找获取ITelephony对象的方法:

ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
ServiceManager此类android对开发者隐藏,所以不能去直接调用其方法,所以需要反射调用

需要的权限
<uses-permission android:name="android.permission.CALL_PHONE"/>

aidl 挂断电话endCall 调用过程
将对应的
ITelephony.aidl 和其依赖的NeighboringCellInfo.aidl 文件放入对应的包(原包名)中

//1,获取ServiceManager字节码文件
Class<?> clazz = Class.forName("android.os.ServiceManager");
//2,获取方法
Method method = clazz.getMethod("getService", String.class);
//3,反射调用此方法
IBinder iBinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
//4,调用获取aidl文件对象方法
ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
//5,调用在aidl中隐藏的endCall方法
iTelephony.endCall();

使用内容观察者删除来电记录

由于代码挂断电话后,被挂断的号码仍然会进入通话记录中, 我们需要将这种记录删除
需要的权限
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>

思路
1.找到通话记录所在数据库对应的表
data/data/com.android.providers.contacts/databases/contacts2.db/calls
2.访问数据库方式,系统数据库有提供内容提供者(),通过内容解析器解析,Uri地址(源码)
3.访问数据库中calls表的Uri地址content://call_log/calls
编写代码需要注意bug:
再删除通话记录的时候,删除的是以前的通话记录,本次拦截下来的电话号码,通话记录没有删除?
问题原因:数据库中本次通话记录的电话号码还没有插入,就做了删除操作
bug解决方法:
内容提供者:对外提供数据库的访问方式
内容解析器:用内容提供者提供的访问方式Uri,访问数据库(增删改查)
内容观察者:观察数据库的变化,一旦数据发生改变,调用相应方法
通过内容观察者,观察数据库的插入,一旦有插入,则做删除此条插入数据操作

黑名单拦截服务代码

public class BlackNumberService extends Service {
    private InnerSmsReceiver mInnerSmsReceiver;
    private BlackNumberDao mDao;
    private TelephonyManager mTM;
    private MyPhoneStateListener mPhoneStateListener;
    private MyContentObserver mContentObserver;
    @Override
    public void onCreate() {
        mDao = BlackNumberDao.getInstance(getApplicationContext());

        //拦截短信
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
        intentFilter.setPriority(1000);

        mInnerSmsReceiver = new InnerSmsReceiver();
        registerReceiver(mInnerSmsReceiver, intentFilter);

        //监听电话的状态
        //1,电话管理者对象
        mTM = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        //2,监听电话状态
        mPhoneStateListener = new MyPhoneStateListener();
        mTM.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

        super.onCreate();
    }

    class MyPhoneStateListener extends PhoneStateListener{
        //3,手动重写,电话状态发生改变会触发的方法
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            switch (state) {
            case TelephonyManager.CALL_STATE_IDLE:
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                //挂断电话  aidl文件中去了
//              mTM.endCall();
                endCall(incomingNumber);
                break;
            }
            super.onCallStateChanged(state, incomingNumber);
        }
    }
    class InnerSmsReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            //获取短信内容,获取发送短信电话号码,如果此电话号码在黑名单中,并且拦截模式也为1(短信)或者3(所有),拦截短信
            //1,获取短信内容
            Object[] objects = (Object[]) intent.getExtras().get("pdus");
            //2,循环遍历短信过程
            for (Object object : objects) {
                //3,获取短信对象
                SmsMessage sms = SmsMessage.createFromPdu((byte[])object);
                //4,获取短信对象的基本信息
                String originatingAddress = sms.getOriginatingAddress();
                String messageBody = sms.getMessageBody();

                int mode = mDao.getMode(originatingAddress);

                if(mode == 1 || mode == 3){
                    //拦截短信(android 4.4版本失效  短信数据库,删除)
                    abortBroadcast();
                }
            }
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }
    public void endCall(String phone) {
        int mode = mDao.getMode(phone);

        if(mode == 2 || mode == 3){
//          ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
            //ServiceManager此类android对开发者隐藏,所以不能去直接调用其方法,需要反射调用
            try {
                //1,获取ServiceManager字节码文件
                Class<?> clazz = Class.forName("android.os.ServiceManager");
                //2,获取方法
                Method method = clazz.getMethod("getService", String.class);
                //3,反射调用此方法
                IBinder iBinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
                //4,调用获取aidl文件对象方法
                ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
                //5,调用在aidl中隐藏的endCall方法
                iTelephony.endCall();
            } catch (Exception e) {
                e.printStackTrace();
            }

            //6,在内容解析器上,去注册内容观察者,通过内容观察者,观察数据库(Uri决定那张表那个库)的变化
            mContentObserver = new MyContentObserver(new Handler(),phone);
            getContentResolver().registerContentObserver(
                    Uri.parse("content://call_log/calls"), true, mContentObserver);
        }
    }

    class MyContentObserver extends ContentObserver{
        private String phone;
        public MyContentObserver(Handler handler,String phone) {
            super(handler);
            this.phone = phone;
        }
        //数据库中指定calls表发生改变的时候会去调用方法
        @Override
        public void onChange(boolean selfChange) {
            //插入一条数据后,再进行删除
            getContentResolver().delete(
                    Uri.parse("content://call_log/calls"), "number = ?", new String[]{phone});
            super.onChange(selfChange);
        }
    }
    @Override
    public void onDestroy() {
        //注销广播
        if(mInnerSmsReceiver!=null){
            unregisterReceiver(mInnerSmsReceiver);
        }

        //注销内容观察者
        if(mContentObserver!=null){
            getContentResolver().unregisterContentObserver(mContentObserver);
        }

        //取消对电话状态的监听
        if(mPhoneStateListener!=null){
            mTM.listen(mPhoneStateListener,PhoneStateListener.LISTEN_NONE);
        }
        super.onDestroy();
    }
}

猜你喜欢

转载自blog.csdn.net/opopopwqwqwq/article/details/80201320