接下来我们来实现通讯卫士功能,这个功能就是为用户提供一个黑名单功能。用户把不想接听的号码添加到黑名单中,如果该号码来电就自动挂断。
因为是视图分离,所以我们先来设计界面。黑名单管理界面比较简单,只有一个标题,一个“添加”按钮,然后下面是一个ListView用来显示所有的黑名单。如果黑名单为空,就显示“无黑名单”。长按黑名单号码,会弹出来菜单选项,删除或者是修改。blacklist_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/blacklist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:textSize="30dp"
android:text="黑名单管理"/>
<Button
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="260dp"
android:text="添加"/>
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView
android:id="@+id/blacknum_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
<TextView
android:id="@+id/tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="黑名单为空"/>
</LinearLayout>
</LinearLayout>
这个listview很简单,只是简单的显示一个字符串black_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="strings"/>
</LinearLayout>
点击添加或者修改按钮会弹出来一个对话框,用来输入号码。black_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/add_number_Dialog"
android:hint="请输入号码"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout android:layout_height="wrap_content"
android:layout_width="match_parent">
<Button
android:id="@+id/ok_number_Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:layout_marginLeft="1dp"/>
<Button
android:id="@+id/cancel_number_Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"
android:layout_marginLeft="120dp"/>
</LinearLayout>
</LinearLayout>
接下来写实现黑名单的主要逻辑:
首先要实现黑名单功能,一个主要的前提就是建立数据库,android提供轻量级的sqlite数据库,就是一个文本文件。
我们先建一个blacknumDBHelper类,这个类继承自SQLiteOpenHelper
package com.yangmiaoqing.example.myassitant;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by yangmiaoqing on 2018/3/29.
*/
public class blacknumDBHelper extends SQLiteOpenHelper {
private static SQLiteOpenHelper mInstance;
private final static String name="blacknumber.db";
public static SQLiteOpenHelper getInstance(Context context){
if (mInstance==null){
mInstance=new blacknumDBHelper(context,name,null,1);
}
return mInstance;
}
public blacknumDBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version){
super(context,name,factory,version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table blacknumber(_id integer primary key autoincrement,number text)");
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
首先得到一个SQLiteOpenHelper实例,super中写出版本号和数据库名,然后在onCreate中建立黑名单表,表中只有两个字段,既序号和number。onUpgrade是当数据库更新才用得到的。
然后是数据库操作类blacknumberDao
package com.yangmiaoqing.example.myassitant;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yangmiaoqing on 2018/3/29.
*/
public class blacknumberDao {
private SQLiteOpenHelper mInstance;
public blacknumberDao(Context context){
mInstance=blacknumDBHelper.getInstance(context);
}
public void add(String number){
SQLiteDatabase db=mInstance.getWritableDatabase();
if(db.isOpen()){
ContentValues values=new ContentValues();
values.put("number",number);
db.insert("blacknumber",null,values);
db.close();
Log.e("TAG","添加成功");
}
}
public boolean isBlackNumber(String number){
boolean isExist=false;
SQLiteDatabase db=mInstance.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;
}
public void delete(String number){
SQLiteDatabase db=mInstance.getWritableDatabase();
if(db.isOpen()){
db.delete("blacknumber","number=?",new String[]{number});
db.close();
}
}
public List<String> findAll(){
List<String>blacknumbers=new ArrayList<String>();
SQLiteDatabase db=mInstance.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;
}
public int queryId(String number){
int _id=0;
SQLiteDatabase db=mInstance.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 update(int id,String number){
SQLiteDatabase db=mInstance.getWritableDatabase();
if(db.isOpen()){
ContentValues values=new ContentValues();
values.put("number",number);
db.update("blacknumber",values,"_id=?",new String[]{id+""});
db.close();
}
}
}
主要需要的数据库操作只有这么几种,即插入,查询是否是黑名单,删除,修改,找出所有的黑名单号码以List的形式返回。
所有的黑名单要显示出来,前面我们说了,内容是包含在一个ListView中的。先收一下这个LIstView的适配器。blacknumAdapter这次继承自BaseAdapter。因为比较简单,只需要重写下getView即可。
package com.yangmiaoqing.example.myassitant;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;
/**
* Created by yangmiaoqing on 2018/3/29.
*/
public class blacknumberAdapter extends BaseAdapter {
private List<String> list=null;
private Context context=null;
private LayoutInflater inflater=null;
public blacknumberAdapter(Context context,List<String> list){
this.context=context;
this.list=list;
inflater=LayoutInflater.from(context);
}
public void setBlackNumbers(List<String> list){
this.list=list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
String s=list.get(i);
view=inflater.inflate(R.layout.black_item,null);
TextView textView=(TextView)view.findViewById(R.id.number);
textView.setText(s);
return view;
}
}
接下来是黑名单的操作逻辑blacknumber_activity.java
package com.yangmiaoqing.example.myassitant;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
/**
* Created by yangmiaoqing on 2018/3/29.
*/
public class blacklist_Activity extends Activity implements View.OnClickListener {
private static final int MENU_UPDATE_ID=0;
private static final int MENU_DELETE_ID=1;
private TextView tips;
private Button add_blacknum;
private ListView list_blacknumber;
private LayoutInflater mInflater;
private EditText add_number_Dialog;
private Button ok_number_Dialog;
private Button cancle_number_Dialog;
private AlertDialog dialog;
private String blacknumber;
private View view;
private blacknumberDao blacknumberDao;
private blacknumberAdapter blacknumberAdapter;
private int flag = 0;
private final static int ADD = 1;
private final static int UPDATE = 2;
@Override
protected void onCreate(Bundle savedInstance){
super.onCreate(savedInstance);
// blacknumber=getIntent().getStringExtra("number");
setContentView(R.layout.blacklist_layout);
add_blacknum=(Button)findViewById(R.id.add);
list_blacknumber=(ListView)findViewById(R.id.blacknum_list);
tips=(TextView)findViewById(R.id.tips);
mInflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view=mInflater.inflate(R.layout.black_menu,null); //为了加载上下文菜单布局
add_number_Dialog=(EditText)view.findViewById(R.id.add_number_Dialog);
ok_number_Dialog=(Button)view.findViewById(R.id.ok_number_Dialog);
cancle_number_Dialog=(Button)view.findViewById(R.id.cancel_number_Dialog);
ok_number_Dialog.setOnClickListener(this);
cancle_number_Dialog.setOnClickListener(this);
list_blacknumber.setEmptyView(tips); //当listview为空时,显示信息
blacknumberDao=new blacknumberDao(this);
List<String>blacknumbers=blacknumberDao.findAll();
blacknumberAdapter=new blacknumberAdapter(this,blacknumbers);
list_blacknumber.setAdapter(blacknumberAdapter);
add_blacknum.setOnClickListener(this);
registerForContextMenu(list_blacknumber); //为listview注册上下文菜单
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo){
super.onCreateContextMenu(menu,v,menuInfo);
menu.add(0,MENU_UPDATE_ID,0,"更新"); //四个参数的含义:1,分组 2,ID 3,显示顺序 4,显示信息
menu.add(0,MENU_DELETE_ID,0,"删除");
}
@Override
public boolean onContextItemSelected(MenuItem item){
AdapterView.AdapterContextMenuInfo acmi=(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
int position=acmi.position;
blacknumber=(String)blacknumberAdapter.getItem(position);
int id=item.getItemId();
switch(id){
case MENU_UPDATE_ID:
ViewGroup parent=(ViewGroup)view.getParent();
if(parent!=null)parent.removeAllViews();
// add_number_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);
blacknumberAdapter.setBlackNumbers(blacknumberDao.findAll());
blacknumberAdapter.notifyDataSetChanged();
break;
default:
break;
}
return super.onContextItemSelected(item);
}
public void onClick(View v){
int id=v.getId();
switch (id){
case R.id.add:
ViewGroup parent = (ViewGroup) view.getParent();
if(parent != null){
parent.removeAllViews();
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
add_number_Dialog.setText("");
builder.setTitle("添加黑名单");
builder.setView(view);
dialog = builder.create();
dialog.show();
flag = ADD;
break;
case R.id.ok_number_Dialog:
String number=add_number_Dialog.getText().toString();
if("".equals(number))
Toast.makeText(this,"黑名单号码不能为空",Toast.LENGTH_LONG).show();
else{
boolean isBlackNumber=blacknumberDao.isBlackNumber(number);
if(isBlackNumber)Toast.makeText(this,"已存在此号码",Toast.LENGTH_LONG).show();
else {
if(flag==ADD){
blacknumberDao.add(number);
Toast.makeText(this,"添加成功",Toast.LENGTH_LONG).show();
}else if(flag==UPDATE){
int _id=blacknumberDao.queryId(blacknumber);
String number1=add_number_Dialog.getText().toString();
blacknumberDao.update(_id,number1);
Toast.makeText(this,"修改成功",Toast.LENGTH_LONG).show();
}
dialog.dismiss();
List<String> blacknumbers=blacknumberDao.findAll();
blacknumberAdapter.setBlackNumbers(blacknumbers);
blacknumberAdapter.notifyDataSetChanged();
}
}
break;
case R.id.cancel_number_Dialog:
dialog.dismiss();
break;
}
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
}
以上都是本地的操作黑名单的动作实现,接下来是最重要的:监听来电,判断是否存在于黑名单中,如果存在,调用endcall,自动挂断电话。
值得一提的是,android并没有暴漏这个方法,也就是说并不直接提供我们使用,但是的确存在。如何使用呢?反射
这里需要补充一点的是:由于我们是直接拦截的来电,相当于是跨进程间的通信,这个时候aidl就登场了,好在Android有这些接口:
因为下面的代码会调用这些接口,所以我们提交准备好:
1.在根目录新建一个android.telephony的包,在里面新建一个aidl文件,命名为NeighboringCellInfo;
2.在根目录下新建一个com.android.internal.telephony的包,在里面新建一个aidl文件,命名为ITelephony;
这里一定要弄好,不然无法调用方法。
package com.yangmiaoqing.example.myassitant;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
import java.lang.reflect.Method;
/**
* Created by yangmiaoqing on 2018/3/30.
*/
public class blackNumService extends Service {
private boolean isblacknum;
private blacknumberDao blacknumber=new blacknumberDao(this);
private TelephonyManager tm;
private MyPhoneStateListener listener;
private NotificationManager nm;
@Override
public void onCreate(){
super.onCreate();
tm= (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
listener=new MyPhoneStateListener();
tm.listen(listener,PhoneStateListener.LISTEN_CALL_STATE);
}
private final class MyPhoneStateListener extends PhoneStateListener{
@Override
public void onCallStateChanged(int state,String incomingnumber){
super.onCallStateChanged(state,incomingnumber);
//isblacknum=true;
switch (state){
case TelephonyManager.CALL_STATE_RINGING:
isblacknum=blacknumber.isBlackNumber(incomingnumber);
Log.e("blackNumService",incomingnumber+"来电");
if(isblacknum){
endCall();
}
break;
case TelephonyManager.CALL_STATE_IDLE:
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
break;
default:
break;
}
}
}
private void endCall(){
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();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
解释下,这个监听功能肯定是要在后台服务的,所以继承自Service,在MyPhoneStateListener中监听来电状态,如果是响铃,则判断是否来电号码在黑名单中,如果是则调用endcall自动挂断。endcall函数利用反射机制拿到通过IBinder拿到ITelephony的方法endcall。
最后需要说明的是,在android6.0之后要得到监听电话的权限是要动态实现的。前面已经说过,这个权限实在Mainactivity中申请的。至此,通讯卫士功能就算基本完成了,等所有功能完成之后再来完善。
(后续)因为要在设置中添加黑名单功能的开关,其实很简单,但是做的时候遇到了问题。理论上来说只需要停止我们的service即可,但是试了半天都不可行,很名单功能死活都停止不了。最后发现,Service停止的时候不是立即停止,而是等所有功能完成之后停止,我们PhoneStateListenter还在监听状态,只需要在onDestroy中添加
tm.listen(listener,PhoneStateListener.LISTEN_NONE);
即可