有了前面几篇博文作为基础(《Android之——AIDL小结​》、《Android之——AIDL深入​》、《Android之——自动挂断电话的实现》),我将在这片博文中向大家介绍如何实现手机黑名单的功能。用过Android手机的用户都知道,如果不想接听一些人的电话或者接收一些人的短信,可以将这些人的手机号码放入手机黑名单中,此时,将不会接收到这些人打进来的电话和发送进来的短信。那这些功能具体是如何实现的呢?就让我们一起来实现这些功能吧。

一、原理

以一个数据库表来管理手机黑名单,对这张数据表的管理就是在管理手机黑名单,可以向这张数据表中的数据进行增、删、改、查操作,从而实现对手机黑名单的管理,当数据表中存在的某一个号码,向本机拨打电话或者发送短信的时候,我们可以拦截到相应的电话或者短信,自动挂断电话或者不再提示本机相关显示信息即可,同时,我们将手机是否开启了黑名单功能保存在SharedPreferences中。

原理讲完了,是不是很简单呢?下面,我们就一起来动手实现一个手机黑名单的功能吧。

二、实践

1、数据库设计

实现手机黑名单的功能,这里我们的数据库表很简单,只有两个字段,一个是数据表的id,一个是手机号码字段,凡是这张表中有的数据,都是被拉入手机黑名单的号码。

数据库设计如图所示:

Android之——手机黑名单的实现_手机

2、数据库实现

  • 新建数据库的相关类BlackNumberDBHelper

这个类继承自SQLiteOpenHelper,内部以一个单例模式来获取SQLiteOpenHelper对象,同时在这个类中定义了数据库的名称和创建存储数据的数据表。

具体实现如下:

package cn.lyz.mobilesafe.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

/**
* 手机黑名单数据库相关
* @author liuyazhuang
*
*/
public class BlackNumberDBHelper extends SQLiteOpenHelper {

private static SQLiteOpenHelper mInstance;

private final static String name = "blacknumber.db";

public static SQLiteOpenHelper getInstance(Context context){
if(mInstance == null){
mInstance = new BlackNumberDBHelper(context, name, null, 1);
}
return mInstance;
}

private BlackNumberDBHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
// TODO Auto-generated constructor stub
}

@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("create table blacknumber(_id integer primary key autoincrement,number text)");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub

}

}
  • 新建数据库的操作类

这个类中主要是封装了对数据库的增、删、改、查操作,我们可以直接调用这个类中的方法来实现对数据库的增、删、改、查操作,从而实现对手机黑名单的操作。

具体实现如下:

package cn.lyz.mobilesafe.dao;

import java.util.ArrayList;
import java.util.List;

import cn.lyz.mobilesafe.db.BlackNumberDBHelper;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
* 数据库的操作类
* 封装了对数据表的增删改查操作
* @author liuyazhuang
*
*/
public class BlackNumberDao {

private SQLiteOpenHelper mOpenHelper;

public BlackNumberDao(Context context) {
// TODO Auto-generated constructor stub
mOpenHelper = BlackNumberDBHelper.getInstance(context);
}

//添加黑名单
public void add(String number){
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
if(db.isOpen()){
ContentValues values = new ContentValues();
values.put("number", number);
db.insert("blacknumber", "_id", values);
db.close();
}
}

//判断号码是否是黑名单
public boolean isBlackNumber(String number){
boolean isExist = false;
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if(db.isOpen()){
Cursor c = db.query("blacknumber", null, " number = ? ", new String[]{number}, null, null, null);
if(c.moveToFirst()){
isExist = true;
}
c.close();
db.close();
}
return isExist;
}

//根据号码查询id
public int queryId(String number){
int _id = 0;
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if(db.isOpen()){
Cursor c = db.query("blacknumber", new String[]{"_id"}, " number = ? ", new String[]{number}, null, null, null);
if(c.moveToFirst()){
_id = c.getInt(0);
}
c.close();
db.close();
}
return _id;
}

//删除黑名单
public void delete(String number){
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
if(db.isOpen()){
db.delete("blacknumber", " number = ? ", new String[]{number});
db.close();
}
}

//更新黑名单
public void update(int id,String number){
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
if(db.isOpen()){
ContentValues values = new ContentValues();
values.put("number", number);
db.update("blacknumber", values, " _id = ? ", new String[]{id+""});
db.close();
}
}

//得到所有的黑名单
public List<String> findAll(){
List<String> blacknumbers = new ArrayList<String>();
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if(db.isOpen()){
Cursor c = db.query("blacknumber", new String[]{"number"}, null, null, null, null, null);
while(c.moveToNext()){
String number = c.getString(0);
blacknumbers.add(number);
}
c.close();
db.close();
}
return blacknumbers;
}

}

3、短信接收者SmsRecevier

新建短信接收者SmsRecevier继承BroadcastReceiver,这个类用来接收外界发送来的短信,在这个类中,首先拦截到发送短信的号码,到黑名单数据库中查询是否存在这个号码,如果存在,则说明当前发送短信的号码为黑名单中的号码,我们就把这个短信拦截掉,否则不做任何操作。

具体实现代码如下:

package cn.lyz.mobilesafe.receiver;

import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.util.Log;
import cn.lyz.mobilesafe.R;
import cn.lyz.mobilesafe.dao.BlackNumberDao;
import cn.lyz.mobilesafe.engine.GPSInfoService;

/**
* 短信接收者
* @author liuyazhuang
*
*/
public class SmsRecevier extends BroadcastReceiver {

private SharedPreferences sp;
private DevicePolicyManager devicePolicyManager;
private BlackNumberDao blackNumberDao;
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub

Log.i("i", "已经拦截到了短信");
sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
blackNumberDao = new BlackNumberDao(context);
//判断保护是否开启
boolean isprotected = sp.getBoolean("isprotected", false);
if(isprotected){
devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
Object[] pdus = (Object[]) intent.getExtras().get("pdus");
for(Object pdu:pdus){
String address = smsMessage.getDisplayOriginatingAddress();

//判断是不是黑名单的一个短信
boolean isBlackNumber = blackNumberDao.isBlackNumber(address);
if(isBlackNumber){
abortBroadcast();
}
}
}
}

}

4、黑名单电话服务BlackNumberService

新建黑名单电话服务类BlackNumberService继承自Service,这是一个服务类,它会一直在Android后台运行,监听来电是否为黑名单中的号码。首先,这个类中的方法会先从SharedPreferences中获取是否开启了黑名单的信息,如果开启了黑名单,则再从数据库中查询相关电话号码是否为黑名单中的号码,如果是,则自动挂断电话(有关自动挂断电话的功能请详见《​​Android之——自动挂断电话的实现​​》一文)。

具体代码实现如下:

package cn.lyz.mobilesafe.service;

import java.lang.reflect.Method;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.provider.CallLog.Calls;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import cn.lyz.mobilesafe.activity.BlackNumberListActivity;
import cn.lyz.mobilesafe.dao.BlackNumberDao;

import com.android.internal.telephony.ITelephony;

/**
* 黑名单电话服务
* @author liuyazhuang
*
*/
public class BlackNumberService extends Service {

private TelephonyManager tm;
private MyPhoneStateListener listener;
private BlackNumberDao blackNumberDao;
private SharedPreferences sp;
private NotificationManager nm;
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
sp = getSharedPreferences("config", Context.MODE_PRIVATE);
listener = new MyPhoneStateListener();
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
blackNumberDao = new BlackNumberDao(this);

nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

}

private final class MyPhoneStateListener extends PhoneStateListener{

private long startTime = 0;
@Override
public void onCallStateChanged(int state, String incomingNumber) {
// TODO Auto-generated method stub
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
//判断来电黑名单是否开启
boolean isblackstart = sp.getBoolean("isblacknumber", false);
if(isblackstart){
boolean isBlackNumber = blackNumberDao.isBlackNumber(incomingNumber);
if(isBlackNumber){
endCall(incomingNumber);
return;
}
}

startTime = System.currentTimeMillis();

break;
case TelephonyManager.CALL_STATE_OFFHOOK:

break;
case TelephonyManager.CALL_STATE_IDLE:
long endTime = System.currentTimeMillis();
//来电一声响
if(endTime - startTime < 3000){
//发送通知
Notification notification = new Notification(android.R.drawable.stat_notify_missed_call, "拦截到来电一声响", System.currentTimeMillis());
Intent intent = new Intent(getApplicationContext(),BlackNumberListActivity.class);
intent.putExtra("number", incomingNumber);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 100, intent, 0);
notification.setLatestEventInfo(getApplicationContext(), "来电一声响", "拦截到来电一声响", contentIntent);
notification.flags = Notification.FLAG_AUTO_CANCEL;
nm.notify(100, notification);
}
break;

default:
break;
}
}

}

//挂断电话
private void endCall(String incomingNumber){
try {
Class<?> clazz = Class.forName("android.os.ServiceManager");
Method method = clazz.getMethod("getService", String.class);
IBinder ibinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
ITelephony iTelephony = ITelephony.Stub.asInterface(ibinder);
iTelephony.endCall();

//删除通话记录 通话记录的保存是一个异步的操作,需要使用ContentObserver技术来实现
Uri uri = Calls.CONTENT_URI;
getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler(),incomingNumber));

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private final class MyContentObserver extends ContentObserver{

private String incomingNumber;
public MyContentObserver(Handler handler, String incomingNumber) {
super(handler);
// TODO Auto-generated constructor stub
this.incomingNumber = incomingNumber;
}

@Override
public void onChange(boolean selfChange) {
// TODO Auto-generated method stub
super.onChange(selfChange);
Uri uri = Calls.CONTENT_URI;
String where = Calls.NUMBER + " = ?";
String[] selectionArgs = new String[]{incomingNumber};
getContentResolver().delete(uri, where, selectionArgs);

//解除监听
getContentResolver().unregisterContentObserver(this);
}
}

@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}


@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//取消状态监听
tm.listen(listener, PhoneStateListener.LISTEN_NONE);
}
}

5、页面布局

具体实现如下:

<?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/text_title_style"
android:text="通讯卫士" />

<View style="@style/view_divide_line_style" />

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="黑名单管理"
android:textColor="#0f0"
android:textSize="20sp" />

<TextView
android:id="@+id/tv_add_blacknumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:background="@drawable/tv_add_blacknumber_bg"
android:text="添加黑名单号码"
android:textColor="#fff"
android:textSize="20sp" />
</RelativeLayout>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">

<ListView
android:id="@+id/lv_blacknumber"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />

<TextView
android:id="@+id/empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:text="黑名单记录为空" />
</LinearLayout>

</LinearLayout>

6、菜单布局

具体实现如下:

<?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="wrap_content"
android:orientation="vertical" >

<EditText
android:id="@+id/et_number_blacknumber_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入号码"
/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<Button
android:id="@+id/bt_ok_blacknumber_dialog"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="确定" />

<Button
android:id="@+id/bt_cancel_blacknumber_dialog"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消" />
</LinearLayout>

</LinearLayout>

7、手机黑名单的入口类BlackNumberListActivity

这个类是应用程序的入口类,是用户直接可以看到的界面,在这个类中,我们首先找到页面上的各个控件,设置相关的事件状态,我们通过在这个类中设置上下文菜单来实现对黑名单的修改与删除操作,通过长按黑名单列表来实现修改与删除黑名单中相应号码的操作。

具体实现如下:

package cn.lyz.mobilesafe.activity;

import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import cn.lyz.mobilesafe.R;
import cn.lyz.mobilesafe.adapter.BlackNumberAdapter;
import cn.lyz.mobilesafe.dao.BlackNumberDao;

/**
* 手机黑名单的实现
* @author liuyazhuang
*
*/
public class BlackNumberListActivity extends Activity implements OnClickListener{


private static final int MENU_UPDATE_ID = 0;
private static final int MENU_DELETE_ID = 1;
private TextView tv_add_blacknumber;
private ListView lv_blacknumber;
private TextView empty;
private View view;
private LayoutInflater mInflater;
private EditText et_number_blacknumber_dialog;
private Button bt_ok_blacknumber_dialog;
private Button bt_cancel_blacknumber_dialog;
private BlackNumberDao blackNumberDao;
private AlertDialog dialog;
private BlackNumberAdapter mAdapter;

private int flag = 0;
private final static int ADD = 1;
private final static int UPDATE = 2;
private String blacknumber;

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Log.i("i", " on create ");
blacknumber = getIntent().getStringExtra("number");

setContentView(R.layout.blacknumber_list);

tv_add_blacknumber = (TextView) findViewById(R.id.tv_add_blacknumber);
lv_blacknumber = (ListView) findViewById(R.id.lv_blacknumber);
empty = (TextView) findViewById(R.id.empty);

mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = mInflater.inflate(R.layout.add_blacknumber_dialog, null);
et_number_blacknumber_dialog = (EditText) view.findViewById(R.id.et_number_blacknumber_dialog);
bt_ok_blacknumber_dialog = (Button) view.findViewById(R.id.bt_ok_blacknumber_dialog);
bt_cancel_blacknumber_dialog = (Button) view.findViewById(R.id.bt_cancel_blacknumber_dialog);


bt_ok_blacknumber_dialog.setOnClickListener(this);
bt_cancel_blacknumber_dialog.setOnClickListener(this);

//当listview没有数据的显示内容
lv_blacknumber.setEmptyView(empty);

blackNumberDao = new BlackNumberDao(this);
List<String> blacknumbers = blackNumberDao.findAll();
mAdapter = new BlackNumberAdapter(this, blacknumbers);
lv_blacknumber.setAdapter(mAdapter);
tv_add_blacknumber.setOnClickListener(this);

//给一个控件注册上下文菜单
registerForContextMenu(lv_blacknumber);

if(blacknumber != null){
boolean isBlackNumber = blackNumberDao.isBlackNumber(blacknumber);
if(!isBlackNumber){
ViewGroup parent = (ViewGroup) view.getParent();
if(parent != null){
parent.removeAllViews();
}
et_number_blacknumber_dialog.setText(blacknumber);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("添加黑名单");
builder.setView(view);
dialog = builder.create();
dialog.show();
flag = ADD;
}
}

}

@Override
protected void onNewIntent(Intent intent) {
// TODO Auto-generated method stub
super.onNewIntent(intent);
Log.i("i", " on new intent");
blacknumber = intent.getStringExtra("number");
if(blacknumber != null){
boolean isBlackNumber = blackNumberDao.isBlackNumber(blacknumber);
if(!isBlackNumber){
ViewGroup parent = (ViewGroup) view.getParent();
if(parent != null){
parent.removeAllViews();
}
et_number_blacknumber_dialog.setText(blacknumber);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("添加黑名单");
builder.setView(view);
dialog = builder.create();
dialog.show();
flag = ADD;
}
}

}

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
// TODO Auto-generated method stub
super.onCreateContextMenu(menu, v, menuInfo);

menu.add(0, MENU_UPDATE_ID, 0, "更新黑名单号码");
menu.add(0, MENU_DELETE_ID, 0, "删除黑名单号码");
}

@Override
public boolean onContextItemSelected(MenuItem item) {
// TODO Auto-generated method stub
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item.getMenuInfo();
int position = acmi.position;
blacknumber = (String) mAdapter.getItem(position);
int id = item.getItemId();
switch (id) {
case MENU_UPDATE_ID:
ViewGroup parent = (ViewGroup) view.getParent();
if(parent != null){
parent.removeAllViews();
}
et_number_blacknumber_dialog.setText(blacknumber);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("更新黑名单");
builder.setView(view);
dialog = builder.create();
dialog.show();

flag = UPDATE;
break;
case MENU_DELETE_ID:
blackNumberDao.delete(blacknumber);
mAdapter.setBlacknumbers(blackNumberDao.findAll());
mAdapter.notifyDataSetChanged();
break;

default:
break;
}
return super.onContextItemSelected(item);
}

//按钮点击事件
public void onClick(View v) {
// TODO Auto-generated method stub
int id = v.getId();
switch (id) {
case R.id.tv_add_blacknumber:

ViewGroup parent = (ViewGroup) view.getParent();
if(parent != null){
parent.removeAllViews();
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("添加黑名单");
builder.setView(view);
dialog = builder.create();
dialog.show();
flag = ADD;

break;
case R.id.bt_ok_blacknumber_dialog:
String number = et_number_blacknumber_dialog.getText().toString();
if("".equals(number)){
Toast.makeText(this, "黑名单号码不能为空", 1).show();
}else{
boolean isBlackNumber = blackNumberDao.isBlackNumber(number);
if(isBlackNumber){
Toast.makeText(this, "号码已经存在于黑名单中", 1).show();
}else{
if(flag == ADD){
blackNumberDao.add(number);
Toast.makeText(this, "黑名单号码添加成功", 1).show();
}else{
int _id = blackNumberDao.queryId(blacknumber);
blackNumberDao.update(_id, number);
Toast.makeText(this, "黑名单号码修改成功", 1).show();
}

dialog.dismiss();

List<String> blacknumbers = blackNumberDao.findAll();
mAdapter.setBlacknumbers(blacknumbers);
mAdapter.notifyDataSetChanged();//让listview自动刷新
}
}

break;
case R.id.bt_cancel_blacknumber_dialog:
dialog.dismiss();
break;
default:
break;
}
}
}

8、注册权限

最后,别忘了在AndroidManifest.xml中注册相关权限

具体要注册的权限如下:

<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

三、运行效果

1、来电相关

程序运行图

Android之——手机黑名单的实现_短信_02

添加黑名单

Android之——手机黑名单的实现_黑名单_03

黑名单添加成功

Android之——手机黑名单的实现_短信_04

长按更新或删除黑名单

Android之——手机黑名单的实现_黑名单_05

更新黑名单号码

Android之——手机黑名单的实现_手机_06

更新黑名单号码

Android之——手机黑名单的实现_sqlite_07

修改成功

Android之——手机黑名单的实现_黑名单_08

以不是黑名单的号码向手机打电话

Android之——手机黑名单的实现_android_09

正常显示来电

Android之——手机黑名单的实现_手机_10

以黑名单号码给手机来电

Android之——手机黑名单的实现_android_11

不显示来电

Android之——手机黑名单的实现_短信_12

2、短信相关

正常号码向手机发短信

Android之——手机黑名单的实现_android_13

正常提示短信信息

Android之——手机黑名单的实现_短信_14

以黑名单中的号码向手机发短信

Android之——手机黑名单的实现_短信_15

不提示短信信息

Android之——手机黑名单的实现_sqlite_16

至此,手机黑名单功能完成。

四、温馨提示

本实例中,为了方面,我把一些文字直接写在了布局文件中和相关的类中,大家在真实的项目中要把这些文字写在string.xml文件中,在外部引用这些资源,切记,这是作为一个Android程序员最基本的开发常识和规范,我在这里只是为了方便直接写在了类和布局文件中。