###1.骚扰拦截需求分析
1.界面
1.1 黑名单列表界面
1.2 添加黑名单界面
2.功能
2.1 黑名单的添加、删除
2.2 拦截电话
2.3 拦截短信

###2.黑名单数据库的创建
1.分析需要的字段
id 主键自增长,phone 电话号码,mode 拦截模式
2.创建数据库打开类BlackNumberDBOpenHelper,继承SQLiteOpenHelper

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

//数据库第一次被创建的时候调用,适合初始化数据库的表结构
@Override
public void onCreate(SQLiteDatabase db) {
//id 主键自增长, phone 电话号码 mode 拦截模式
db.execSQL("create table blacknumber (_id integer primary key autoincrement, phone varchar(20),mode varchar(2))");
}

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

}

}


3.创建数据库数据操作类BlackNumberDao,实现增删改查方法

###3.增删改查单元测试
1.创建测试类TestBlackNumberDao,继承AndroidTestCase,测试增删改查方法
2.在清单文件中对测试类进行如下配置:
2.1 manifest节点下添加

<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.hb.mobilesafe" >
</instrumentation>
2.2 application节点下添加
<uses-library android:name="android.test.runner"/>###4.android:layout_weight的用法


1.android:layout_weight:权重比例,将剩余空间按比例分配。只可在线性布局中使用
2.指定android:layout_weight属性后,
如果线性布局是水平的,View的宽度=原有宽度+线性布局剩余空间的占比
如果线性布局是竖直的,View的高度=原有高度+线性布局剩余空间的占比
3.Android官方推荐,在使用android:layout_weight时,
如果线性布局是水平的,要将android:layout_width设置为0dp
如果线性布局是竖直的,要将android:layout_height设置为0dp

###5.9patch图片(.9.png拼接图片)
![](http://i.imgur.com/A63gxy8.jpg)
1号黑色条位置向下覆盖的区域表示图片横向拉伸时,只拉伸该区域
2号黑色条位置向右覆盖的区域表示图片纵向拉伸时,只拉伸该区域
3号黑色条位置向左覆盖的区域表示图片纵向显示内容的区域
4号黑色条位置向上覆盖的区域表示图片横向显示内容的区域

###6.shape图形
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 <!-- 矩形|椭圆|线|环 --> 
 android:shape=["rectangle" | "oval" | "line" | "ring"] >
 <!-- 圆角 --> 
 <corners
 android:radius="integer"
 android:topLeftRadius="integer"
 android:topRightRadius="integer"
 android:bottomLeftRadius="integer"
 android:bottomRightRadius="integer" />
 <!-- 固定色 -->
 <solid
 android:color="color" />
 <!-- 渐变色 --> 
 <gradient
 android:type=["linear" | "radial" | "sweep"] 
 //linear 线性渐变,这是默认设置
 //radial 放射性渐变,以开始色为中心
 //sweep 扫描线式的渐变。
 android:startColor="color" //颜色值 起始颜色
 android:endColor="color" //颜色值 结束颜色
 android:centerColor="integer" //渐变中间颜色,即开始颜色与结束颜色之间的颜色
 android:angle="integer" //渐变角度
 android:centerX="integer" //渐变中心X点坐标的相对位置
 android:centerY="integer" //渐变中心Y点坐标的相对位置
 android:gradientRadius="integer" //渐变色半径.当 android:type="radial" 时才使用。单独使用 android:type="radial"会报错 />
 <!-- 边框 -->
 <stroke
 android:width="integer"
 android:color="color"
 android:dashWidth="integer"
 android:dashGap="integer" />
 <!-- 边距 --> 
 <padding
 android:left="integer"
 android:top="integer"
 android:right="integer"
 android:bottom="integer" />
 <!-- 大小 一般不指定-->
 <size
 android:width="integer"
 android:height="integer" />
 </shape>

###2.挂断电话

//加入权限
 <uses-permission android:name="android.permission.CALL_PHONE" /> /**
 * 挂断电话
 */
 public void endCall() { //系统内部调用方式
 //ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
 try { //ServiceManager获取不到,需要反射调用
 Class clazz = getClassLoader().loadClass("android.os.ServiceManager");
 Method method = clazz.getDeclaredMethod("getService", String.class);
 IBinder iBinder = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE); //挂断电话需要用到AIDL,拷贝需要用到的AIDL文件,ITelephony.aidl和NeighboringCellInfo.aidl,包名要与原包名一致
 ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
 iTelephony.endCall();
 } catch (Exception e) {
 e.printStackTrace();
 }
 }

###3.删除呼叫记录

//加入权限
 <uses-permission android:name="android.permission.READ_CALL_LOG" />
 <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> /**
 * 删除黑名单号码的呼叫记录
 * @param incomingNumber 来电黑名单号码
 */
 public void deleteCallLog(final String incomingNumber) {
 final ContentResolver resolver = getContentResolver(); //CallLog.Calls.CONTENT_URI 等价于 Uri.parse("content://call_log/calls");
 final Uri uri = Uri.parse("content://call_log/calls");

 //利用内容观察者 观察呼叫记录的数据库,如果生成了呼叫记录就立刻删除呼叫记录
 resolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {
 @Override
 public void onChange(boolean selfChange) {
 //当内容观察者观察到数据库的内容变化的时候调用的方法.
 super.onChange(selfChange);
 resolver.delete(uri, "number=?", new String[]{incomingNumber});
 }
 });
 }

###4.拦截短信
1.注册在清单文件中的广播接收者,无论应用是否启动都会接收到广播,想用开关控制拦截短信的功能,则在代码中注册短信广播接收者

receiver = new InnerSmsReceiver();
 IntentFilter filter = new IntentFilter();
 filter.addAction("android.provider.Telephony.SMS_RECEIVED");
 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 registerReceiver(receiver, filter); private class InnerSmsReceiver extends BroadcastReceiver{
 @Override
 public void onReceive(Context context, Intent intent) {
 Log.i(Tag,"服务内部广播接受者接收到了短信");
 Object[] objs = (Object[]) intent.getExtras().get("pdus");
 for(Object obj: objs){
 SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);
 String body = smsMessage.getMessageBody();
 if(body.contains("fapiao")){
 //你的头发票亮的很 分词技术
 Log.i(Tag,"发现发票垃圾短信,拦截");
 abortBroadcast();
 return;
 }
 String sender = smsMessage.getOriginatingAddress();
 String mode = dao.find(sender);
 if("2".equals(mode)||"3".equals(mode)){
 Log.i(Tag,"发现黑名单短信,拦截");
 abortBroadcast();
 }
 }
 }
 }

2.onDestroy中注销

unregisterReceiver(receiver);
 receiver = null;###5.导入已存在的数据库
 //assert资产目录里面的文件会原封不动的打包到apk里,不生成id /**
 * 拷贝归属地的数据库
 */
 private void copyAddressDB() {
 File file = new File(getFilesDir(), "address.db");
 //判断数据库文件是否存在
 if (file.exists() && file.length() > 0) {
 Log.i(TAG, "数据库存在,无需拷贝");
 } else {
 new Thread() {
 public void run() {
 // 把asset资产目录里面的数据库文件(在apk里面的)拷贝到手机系统里面
 try {
 InputStream is = getAssets().open("address.db");
 File file = new File(getFilesDir(), "address.db");
 FileOutputStream fos = new FileOutputStream(file);
 byte[] buffer = new byte[1024];
 int len = -1;
 while ((len = is.read(buffer)) != -1) {
 fos.write(buffer, 0, len);
 }
 fos.close();
 is.close();
 } catch (Exception e) {
 e.printStackTrace();
 }
 };
 }.start();
 }
 }
###6.查询号码归属地
 /**
 * 查询手机号码的归属地信息
 * @param mobilenumber
 * @return
 */
 public static String findLocation(String mobilenumber) {
 String path = "/data/data/com.hb.mobilesafe/files/address.db";
 //打开已存在的数据库
 SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null,
 SQLiteDatabase.OPEN_READONLY);
 Cursor cursor = db.rawQuery(
 "select location from data2 where id = (select outkey from data1 where id = ?)",
 new String[]{mobilenumber.substring(0, 7)});
 String location ="";
 if(cursor.moveToNext()){
 location = cursor.getString(0);
 }
 cursor.close();
 db.close();
 return location;
 }

###7.判断一个号码是否是手机号码

// ^1[34578]\d{9}$
 // ^ 匹配输入字符串的开始位置。
 // [] 字符集合。匹配所包含的任意一个字符。
 // \d 匹配一个数字字符。
 // {} n 是一个非负整数。匹配确定的 n 次。
 // $ 匹配输入字符串的结束位置。
 boolean result = number.matches("^1[34578]\\d{9}$");###8.给EditText添加文本变化监听器
 // 给文本输入框注册一个内容变化的监听器.
 et_number.addTextChangedListener(new TextWatcher() {

 //当文本变化之前调用的方法,s为改变前字符串,可获取被替换内容
 //在s中从start开始的count个字符即将被after个字符替换
 @Override
 public void beforeTextChanged(CharSequence s, int start, int count,
 int after) { }
 //当文本变化之后调用的方法,s为改变后字符串,可获取替换内容
 //在s中从start开始的before个字符刚刚被count个字符替换
 @Override
 public void onTextChanged(CharSequence s, int start, int before,
 int count) {
 if (s.length() >= 10) {
 String location = AddressDBDao.findLocation(s.toString());
 tv_location.setText("归属地为:" + location);
 }
 } //当文本变化之后调用的方法,s为改变后字符串,操作s可直接改变EditText内容,EditText内容改变会继续调用beforeTextChanged和onTextChanged方法
 //s中有内容被改变
 @Override
 public void afterTextChanged(Editable s) { }
 });



每次输入都会调用3个方法,调用顺序为beforeTextChanged-->onTextChanged-->afterTextChanged

###9.CharSequence、String、Editable
1.CharSequence:接口,只有length()、charAt(int index)、subSequence(int start, int end)、toString()四个方法
2.String:实现了CharSequence接口,具有很多操作字符串的方法,不可修改
3.Editable:实现了CharSequence接口,具有增删改等修改的方法,可修改

###10.动画插值器Interpolator
1.interpolator定义一个动画的变化率(the rate of change)。这使得基本的动画效果(alpha, scale, translate, rotate)得以加速,减速,重复等。
2.常用Interpolator
LinearInterpolator 以常量速率改变

AccelerateInterpolator 加速
DecelerateInterpolator 减速
AccelerateDecelerateInterpolator 先加速后减速

AnticipateInterpolator
OvershootInterpolator
AnticipateOvershootInterpolator

BounceInterpolator 弹跳

CycleInterpolator 循环播放特定的次数,速率改变沿着正弦曲线

###11.监听外拨电话
1.添加权限
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
2.注册广播接收者

receiver = new OutCallReceiver();
 IntentFilter filter = new IntentFilter();
 filter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
 registerReceiver(receiver, filter);

 private class OutCallReceiver extends BroadcastReceiver{
 @Override
 public void onReceive(Context context, Intent intent) {
 String number = getResultData();
 String address = AddressDBDao.findLocation(number);
 Toast.makeText(ShowAddressService.this, address, 1).show();
 }
 }


3.释放资源

@Override
 public void onDestroy() {
 unregisterReceiver(receiver);
 receiver = null;
 super.onDestroy();
 }

###12.通过WindowManager添加自定义View到窗体
1.添加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
2.获取WindowManager
WindowManager mWM = (WindowManager) getSystemService(WINDOW_SERVICE);
3.自定义View
View mView;
4.设置布局参数

final WindowManager.LayoutParams 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_TOAST;


5.添加到窗体
mWM.addView(view, mParams);
6.移除View
mWM.removeView(view);

 

代码:



1 package com.hb.mobilesafe.activities;
  2 
  3 import java.util.List;
  4 
  5 import android.annotation.SuppressLint;
  6 import android.app.Activity;
  7 import android.content.Intent;
  8 import android.os.Bundle;
  9 import android.os.SystemClock;
 10 import android.view.View;
 11 import android.view.View.OnClickListener;
 12 import android.view.ViewGroup;
 13 import android.view.Window;
 14 import android.widget.BaseAdapter;
 15 import android.widget.ImageView;
 16 import android.widget.LinearLayout;
 17 import android.widget.ListView;
 18 import android.widget.TextView;
 19 import android.widget.Toast;
 20 
 21 import com.hb.demo_mobilesafe.R;
 22 import com.hb.mobilesafe.bean.BlackInfo;
 23 import com.hb.mobilesafe.dbdao.BlackNumberDao;
 24 
 25 public class BlackNumberActivity extends Activity {
 26     private ImageView iv_empt;
 27     private BlackNumberDao dao;
 28     private List<BlackInfo> list;
 29     private ListView lv_blacknumber;
 30     private MyAdapter adapter;
 31     private BlackInfo info;
 32     private LinearLayout ll_progressbar;
 33     @Override
 34     protected void onCreate(Bundle savedInstanceState) {
 35         // TODO Auto-generated method stub
 36         super.onCreate(savedInstanceState);
 37         requestWindowFeature(Window.FEATURE_NO_TITLE);
 38         setContentView(R.layout.activity_blacknumber);
 39         lv_blacknumber=(ListView) findViewById(R.id.lv_blacknumber);
 40         iv_empt=(ImageView) findViewById(R.id.iv_empt);
 41         ll_progressbar=(LinearLayout) findViewById(R.id.ll_progressbar);
 42         dao=new BlackNumberDao(this);
 43         ll_progressbar.setVisibility(View.VISIBLE);
 44         new Thread(){
 45             public void run() {
 46                 SystemClock.sleep(2500);
 47                 runOnUiThread(new Runnable() {
 48                     public void run() {
 49                         ll_progressbar.setVisibility(View.INVISIBLE);
 50                         updateUI();
 51                         adapter=new MyAdapter();
 52                         lv_blacknumber.setAdapter(adapter);
 53                     }
 54 
 55                 });
 56             };
 57         }.start();
 58 
 59 
 60 
 61     }
 62     private void updateUI() {
 63         list = dao.queryAll();
 64         if(list.size()>0){
 65             iv_empt.setVisibility(View.INVISIBLE);
 66         }else{
 67             iv_empt.setVisibility(View.VISIBLE);
 68         }
 69     }
 70     private class MyAdapter extends BaseAdapter{
 71 
 72 
 73 
 74         @Override
 75         public int getCount() {
 76 
 77             return list.size();
 78         }
 79         @SuppressWarnings("null")
 80         @SuppressLint("ViewHolder") @Override
 81         public View getView(final int position, View convertView, ViewGroup parent) {
 82             View view;
 83             ViewHolder holder = null;
 84             if(convertView!=null){
 85                 view=convertView;//复用历史缓存抄作
 86                 holder=(ViewHolder) view.getTag();
 87             }else {
 88                 holder=new ViewHolder();
 89                 view=View.inflate(BlackNumberActivity.this, R.layout.blacknumber_item, null);
 90                 holder.tv_number=(TextView) view.findViewById(R.id.tv_number);
 91                 holder.tv_mode=(TextView) view.findViewById(R.id.tv_mode);
 92                 holder.iv_del=(ImageView) view.findViewById(R.id.iv_delete);
 93                 view.setTag(holder);
 94             }
 95 
 96             /**
 97              * 删除黑名单
 98              */
 99             holder.iv_del.setOnClickListener(new OnClickListener() {
100 
101                 @Override
102                 public void onClick(View v) {
103                     String num = list.get(position).getNumber();
104                     dao.delete(num);
105                     list.remove(num);
106                     updateUI();
107                     adapter.notifyDataSetChanged();
108                 }
109             });
110             String mode = list.get(position).getMode();
111             if(mode.equals("1")){
112                 holder.tv_mode.setText("电话拦截");
113             }else if(mode.equals("2")){
114                 holder.tv_mode.setText("短信拦截");
115             }else{
116                 holder.tv_mode.setText("全部拦截");
117             }
118             holder.tv_number.setText(list.get(position).getNumber());
119             return view;
120         }
121 
122         @Override
123         public Object getItem(int position) {
124             return null;
125         }
126 
127         @Override
128         public long getItemId(int position) {
129             return 0;
130         }
131 
132 
133 
134     }
135     class ViewHolder{
136         TextView tv_number;
137         TextView tv_mode;
138         ImageView iv_del;
139 
140     }
141 
142     public void addnumber(View view){
143         Intent intent=new Intent(BlackNumberActivity.this, AddNumberActivity.class);
144         startActivityForResult(intent, 0);
145     }
146     @Override
147     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
148         super.onActivityResult(requestCode, resultCode, data);
149         if(data!=null){
150             boolean result = data.getBooleanExtra("addnumber", false);
151             if(result){
152                 Toast.makeText(BlackNumberActivity.this, "添加成功!", 0).show();
153                 String number = data.getStringExtra("number");
154                 String mode = data.getStringExtra("mode");
155                 info = new BlackInfo();
156                 info.setNumber(number);
157                 info.setMode(mode);
158                 list.add(0, info);//减少数据库查询的操作
159                 updateUI();
160                 adapter.notifyDataSetChanged();
161             }else{
162                 Toast.makeText(BlackNumberActivity.this, result+"添加失败!", 0).show();
163             }
164 
165         }
166     }
167 }



1 package com.hb.mobilesafe.service;
  2 
  3 import java.lang.reflect.Method;
  4 
  5 import com.android.internal.telephony.ITelephony;
  6 import com.hb.mobilesafe.dbdao.BlackNumberDao;
  7 
  8 import android.app.Service;
  9 import android.content.BroadcastReceiver;
 10 import android.content.ContentResolver;
 11 import android.content.Context;
 12 import android.content.Intent;
 13 import android.content.IntentFilter;
 14 import android.database.ContentObserver;
 15 import android.net.Uri;
 16 import android.os.Handler;
 17 import android.os.IBinder;
 18 import android.telephony.PhoneStateListener;
 19 import android.telephony.SmsMessage;
 20 import android.telephony.TelephonyManager;
 21 import android.util.Log;
 22 
 23 public class BlackNumberSafeService extends Service {
 24     private static final String TAG = "BlackNumberSafeService";
 25     private TelephonyManager tm;
 26     private CallStatic listener;
 27     private BlackNumberDao dao;
 28     private SmsReceiver smsreceiver;
 29 
 30     @Override
 31     public IBinder onBind(Intent intent) {
 32         return null;
 33     }
 34     @Override
 35     public void onCreate() {
 36         
 37         Log.i(TAG, "黑名单服务已经开启了");
 38         System.out.println("黑名单服务已经开启了");
 39         tm=(TelephonyManager) getSystemService(TELEPHONY_SERVICE);
 40         listener=new CallStatic();
 41         dao=new BlackNumberDao(this);
 42         tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
 43         
 44         smsreceiver=new SmsReceiver();
 45         
 46         IntentFilter filter = new IntentFilter();
 47         filter.addAction("android.provider.Telephony.SMS_RECEIVED");
 48         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 49         registerReceiver(smsreceiver, filter);
 50         super.onCreate();
 51     }
 52     @Override
 53     public void onDestroy() {
 54         super.onDestroy();
 55         Log.i(TAG, "黑名单服务已经销毁");
 56         System.out.println("黑名单服务已经销毁");
 57         //销毁时关闭监听
 58         tm.listen(listener, PhoneStateListener.LISTEN_NONE);
 59         listener=null;
 60         
 61         //注销广播
 62         unregisterReceiver(smsreceiver);
 63         smsreceiver=null;
 64     }
 65     private class CallStatic extends PhoneStateListener {
 66 
 67         @Override
 68         public void onCallStateChanged(int state, String incomingNumber) {
 69             super.onCallStateChanged(state, incomingNumber);
 70             switch (state) {
 71             //空闲状态
 72             case TelephonyManager.CALL_STATE_IDLE:
 73                 break;
 74                 //响铃状态
 75             case TelephonyManager.CALL_STATE_RINGING:
 76                 String mode = dao.query(incomingNumber);
 77                 if("1".equals(mode) || "3".equals(mode)){
 78                     System.out.println("电话已经响铃");
 79                     //挂断电话
 80                     endcall();
 81                     //删除记录
 82                     delteCallLog(incomingNumber);
 83                 }
 84                 break;
 85                 //关闭状态
 86             case TelephonyManager.CALL_STATE_OFFHOOK:
 87                 break;
 88 
 89             }
 90         }
 91         
 92         
 93 
 94 
 95     }
 96     private void endcall() {
 97         //ServiceManager获取不到,需要反射调用
 98         try {
 99             Class<?> clazz=getClassLoader().loadClass("android.os.ServiceManager");
100             Method method=clazz.getDeclaredMethod("getService", String.class);
101             IBinder binder=(IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
102             
103             //挂断电话需要用到AIDL,拷贝需要用到的AIDL文件,ITELEPHONY.AIDL和NeighboringcellInfo.aidl包名需要和原名一致
104             ITelephony iTelephony=ITelephony.Stub.asInterface(binder);
105             iTelephony.endCall();
106             System.out.println("电话挂断了");
107         } catch (Exception e) {
108             // TODO Auto-generated catch block
109             e.printStackTrace();
110         }
111     }
112     /**
113      * 
114      * @param 删除电话记录
115      */
116     private void delteCallLog(final String incomingNumber) {
117         final ContentResolver resolver = getContentResolver();
118         final Uri parse = Uri.parse("content://call_log/calls");
119         //利用内容观察者,观察呼叫数据库的内容,如果有记录就删除
120         resolver.registerContentObserver(parse, true,new ContentObserver(new Handler()) {
121 
122             @Override
123             public void onChange(boolean selfChange) {
124                 super.onChange(selfChange);
125                 //当内容观察者观察到数据库的内容变化时调用的方法
126                 resolver.delete(parse, "number=?", new String[]{incomingNumber});
127                 System.out.println("删除电话记录");
128             }
129             
130         } );
131     }
132     /**
133      * @pargram 短信拦截
134      */
135     private class SmsReceiver extends BroadcastReceiver{
136 
137         @Override
138         public void onReceive(Context context, Intent intent) {
139             Object[] objects=(Object[]) intent.getExtras().get("pdus");
140             for (Object object : objects) {
141                 SmsMessage sms = SmsMessage.createFromPdu((byte[]) object);
142                 String address = sms.getOriginatingAddress();
143                 String mode = dao.query(address);
144                 if("2".equals(mode) || "3".equals(mode)){
145                     System.out.println("123456789");
146                     abortBroadcast();
147                     Log.i(TAG, "短信已经拦截了");
148                     
149                 }
150             }
151         }
152         
153     }
154 
155 }