## day05 ##android
## 骚扰拦截功能/黑名单管理 ##sql
- listview初始化 convertview和viewholer的优化写法....数据库
- 添加一条黑名单数据 点击保存时缓存
// 1.获取输入的号码ide
String number = etNum.getText().toString().trim();布局
// 2.判断是否为空优化
if (TextUtils.isEmpty(number)) {动画
Toast.makeText(getApplicationContext(), "号码不能为空", 0).show();this
return;url
}
// 3.获取拦截类型
int checkedRadioButtonId = rgType.getCheckedRadioButtonId();
// 4.判断是否为空
if (checkedRadioButtonId == -1) {
Toast.makeText(getApplicationContext(), "拦截类型不能为空", 0).show();
return;
}
int type = 0; // 根据选中的id 生成拦截方式
switch (checkedRadioButtonId) {
case R.id.rb_eb_number:
type = BlackInfo.TYPE_NUMBER;
break;
case R.id.rb_eb_sms:
type = BlackInfo.TYPE_SMS;
break;
case R.id.rb_eb_all:
type = BlackInfo.TYPE_ALL;
break;
default:
break;
}
// 保存
// 1.保存数据库
boolean success = mDao.insert(number, type);
if (success) {
Intent data = new Intent();
data.putExtra(EXTRA_NUMBER, number);// 电话号码
data.putExtra(EXTRA_TYPE, type);// 拦截方式
// 2.数据传回上一个页面
setResult(Activity.RESULT_OK, data);
// 3.页面消失
finish();
} else {
Toast.makeText(getApplicationContext(), "保存失败", 0).show();
}
- 在列表页面 接收数据 并增长一条显示在页面
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ADD) {// 判断是添加返回
if (resultCode == Activity.RESULT_OK) {
// 1获取传回的数据
String number = data
.getStringExtra(EditBlackActivity.EXTRA_NUMBER);
int type = data.getIntExtra(EditBlackActivity.EXTRA_TYPE, 1);
// System.out.println("number =" + number);
// System.out.println("type =" + type);
BlackInfo info = new BlackInfo(number, type);
// 2添加到数据集合
mInfos.add(info);
// 3刷新listview数据
mAdapter.notifyDataSetChanged();
}
}
}
## 进度圈显示 更改默认图片##
>progressbar设置图片 indeterminateDrawable属性
<ProgressBar
android:id="@+id/pb_blacklist_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateDuration="200" //旋转速度
android:indeterminateDrawable="@drawable/progress_loading" />
>图片是一个xml动画
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/loading"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="1800"
>
</rotate>
改变旋转速度的另一个方式 toDegrees的角度越大 旋转速度越快
- 子线程获取数据
new Thread() {
public void run() {
SystemClock.sleep(1500);
// 获取所有的黑名单数据 耗时 放在子线程
mInfos = mDao.queryAll();
mAdapter = new BlackAdapter();
// 更新界面
runOnUiThread(new Runnable() {
@Override
public void run() {
lvBlack.setAdapter(mAdapter);
// 进度圈圈消失
mPb.setVisibility(View.INVISIBLE);
}
});
};
}.start();
## 添加/更新黑名单 ##
>判断是添加仍是更新 在进入编辑页面前 给intent设置不一样的action
若是是更新
intent.setAction("update");
若是是添加
intent.setAction("add");
>编辑页面EditText的背景是一个selector 设置了是否可用状态
每种状态对应的图片是用xml文件写的shape 经过etNum.setEnabled(true);来转换不一样状态显示的背景
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<!-- 四个角的弧度 -->
<corners android:radius="5dp" />
<!-- 颜色 -->
<solid android:color="#FFFFCE" />
<!--
虚线间隔 android:dashGap="10dp"
android:dashWidth="5dp"
-->
<stroke
android:width="1dp"
android:color="#9DCAD9" />
<!-- 渐变色<gradient
android:centerColor="#0f0"
android:endColor="#00f"
android:startColor="#f00"
android:type="sweep"
>
</gradient> -->
</shape>
背景的selector
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/black_et_bg_enable" android:state_enabled="true" />
<item android:drawable="@drawable/black_et_bg_unenable"/>
</selector>
>在编辑页面收到对应action 作判断 显示不一样的内容
// 获取上一个页面传来的数据
Intent getIntnet = getIntent();
// 获取action 区分添加仍是更新
String action = getIntnet.getAction();
if (TextUtils.equals(action, "add")) {
isUpdate = false;// 添加一个成员变量 记录是添加仍是更新
tvtitle.setText("添加黑名单");
btnConfirm.setText("保存");
etNum.setEnabled(true);// 输入框可用状态 触发状态选择器 显示不一样图片
} else if (TextUtils.equals(action, "update")) {
isUpdate = true;
tvtitle.setText("更新黑名单");
btnConfirm.setText("更新");
etNum.setEnabled(false);// 输入框不可用状态 触发状态选择器 显示不一样图片
// 获取传来的数据 显示在页面
String number = getIntnet.getStringExtra(EXTRA_NUMBER);
int type = getIntnet.getIntExtra(EXTRA_TYPE, BlackInfo.TYPE_NUMBER);
//更新的那条索引
position = getIntnet.getIntExtra(EXTRA_POSITON, -1);
etNum.setText(number);
// 根据不一样的type 选中对应的radiobutton
switch (type) {
case BlackInfo.TYPE_NUMBER:
rgType.check(R.id.rb_eb_number);
break;
case BlackInfo.TYPE_SMS:
rgType.check(R.id.rb_eb_sms);
break;
case BlackInfo.TYPE_ALL:
rgType.check(R.id.rb_eb_all);
break;
default:
break;
}
}
> 点击添加或者是更新,经过DAO保存或更新数据库,而后经过setResult把数据传回黑名单列表页面
// 1.获取输入的号码
String number = etNum.getText().toString().trim();
// 2.判断是否为空
if (TextUtils.isEmpty(number)) {
Toast.makeText(getApplicationContext(), "号码不能为空", 0).show();
return;
}
// 3.获取拦截类型
int checkedRadioButtonId = rgType.getCheckedRadioButtonId();
// 4.判断是否为空
if (checkedRadioButtonId == -1) {
Toast.makeText(getApplicationContext(), "拦截类型不能为空", 0).show();
return;
}
int type = 0; // 根据选中的id 生成拦截方式
switch (checkedRadioButtonId) {
case R.id.rb_eb_number:
type = BlackInfo.TYPE_NUMBER;
break;
case R.id.rb_eb_sms:
type = BlackInfo.TYPE_SMS;
break;
case R.id.rb_eb_all:
type = BlackInfo.TYPE_ALL;
break;
default:
break;
}
if (isUpdate) {
// 更新
// 1.更新数据库
boolean success = mDao.update(number, type);
if (success) {
Intent data = new Intent();
data.putExtra(EXTRA_NUMBER, number);// 电话号码
data.putExtra(EXTRA_TYPE, type);// 拦截方式
data.putExtra(EXTRA_POSITON, position);// 更新的条目索引
// 2.数据传回上一个页面
setResult(Activity.RESULT_OK, data);
// 3.页面消失
finish();
} else {
Toast.makeText(getApplicationContext(), "更新失败", 0).show();
}
} else {
// 保存
// 1.保存数据库
boolean success = mDao.insert(number, type);
if (success) {
Intent data = new Intent();
data.putExtra(EXTRA_NUMBER, number);// 电话号码
data.putExtra(EXTRA_TYPE, type);// 拦截方式
// 2.数据传回上一个页面
setResult(Activity.RESULT_OK, data);
// 3.页面消失
finish();
} else {
Toast.makeText(getApplicationContext(), "保存失败", 0).show();
}
}
> 在黑名单列表页接受数据 更新页面
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ADD) {// 判断是添加返回
if (resultCode == Activity.RESULT_OK) {
// 1获取传回的数据
String number = data
.getStringExtra(EditBlackActivity.EXTRA_NUMBER);
int type = data.getIntExtra(EditBlackActivity.EXTRA_TYPE, 1);
// System.out.println("number =" + number);
// System.out.println("type =" + type);
BlackInfo info = new BlackInfo(number, type);
// 2添加到数据集合
mInfos.add(info);
// 3刷新listview数据
mAdapter.notifyDataSetChanged();
}
} else if (requestCode == REQUEST_CODE_UPDATE) {// 判断是更新返回
if (resultCode == Activity.RESULT_OK) {
// 1获取传回的数据
String number = data
.getStringExtra(EditBlackActivity.EXTRA_NUMBER);
int newType = data.getIntExtra(EditBlackActivity.EXTRA_TYPE, 1);
int position = data.getIntExtra(
EditBlackActivity.EXTRA_POSITON, -1);
// 2取出更改的数据bean 更改拦截方式为新的
mInfos.get(position).type = newType;
// 3.刷新页面
mAdapter.notifyDataSetChanged();
}
}
}
## 删除黑名单数据 ##
viewHolder.imgDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 删除
boolean delete = dao.delete(info.number);
if (delete) {
// 数据库删除成功 去更新界面
mInfos.remove(info);
notifyDataSetChanged();// 更新listview界面
} else {
ToastUtils.show(getApplicationContext(), "删除失败");
}
}
});
## 设置listview数据为空的界面显示 ##
>在布局文件里添加对应的ImageView
>获取到对应的ImageView lvBlck.setEmptyView(imgEmpty);
## listview 分页查找 ##
> 分页查找数据库 语句
String sql = select * from black_list limit 10 offset 20 从20后面查询10条 也就是21到30条
>监听listview的滑动 重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点
lvBlack.setOnScrollListener(new OnScrollListener() {
// 参1 当前的listview 参2滑动的状态
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// OnScrollListener.SCROLL_STATE_IDLE //中止状态
// OnScrollListener.SCROLL_STATE_FLING //松开后滑动的状态
// OnScrollListener.SCROLL_STATE_TOUCH_SCROLL//触摸滑动的状态
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
// 判断中止状态
// 获取最后可见的条目的索引
int lastVisiblePosition = lvBlack.getLastVisiblePosition();
// 判断是否是滑动到最后
if (lastVisiblePosition == mInfos.size() - 1) {
// 加载前 先显示进度条
mPb.setVisibility(View.VISIBLE);
new Thread() {
public void run() {
SystemClock.sleep(1000);
// 加载下一页数据 参2偏移量 就是集合的大小
ArrayList<BlackInfo> queryPart = mDao
.queryPart(SIZE, mInfos.size());
if (queryPart.size() == 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(
getApplicationContext(),
"没有数据了", 0).show();
// 隐藏进度条
mPb.setVisibility(View.INVISIBLE);
}
});
} else {
// 添加到数据源集合里
mInfos.addAll(queryPart);
runOnUiThread(new Runnable() {
@Override
public void run() {
// 刷新页面
mAdapter.notifyDataSetChanged();
// 隐藏进度条
mPb.setVisibility(View.INVISIBLE);
}
});
}
};
}.start();
}
}
}
// 滑动时一直执行 参1 listview 参2 第一个可见的条目的索引 参3 可见的数量 参4 所有数量
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
> 在删除黑名单里 每次删除一条 再从数据库查询一条补充上
// 删除
boolean delete = dao.delete(info.number);
if (delete) {
// 数据库删除成功 去更新界面
blackInfos.remove(position);
// 再从数据库查询一条 补上
ArrayList<BlackInfo> findPart = mDao.findPart(1,
mInfos.size());
mInfos.addAll(findPart);
// 更新listview
notifyDataSetChanged();
} else {
ToastUtils.show(getApplicationContext(), "删除失败");
}
## 黑名单服务 ##
- 判断service是否开启的方法 ActivityManager
public static boolean isRunning(Context context, Class<? extends Service> service) {
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
// 获取全部的正在运行的服务信息
List<RunningServiceInfo> infos = am.getRunningServices(1000);
for (RunningServiceInfo info : infos) {
// 获取服务的组建名称对象
ComponentName componentName = info.service;
//判断传进来的service是否是运行中的其中一个
if (TextUtils.equals(componentName.getClassName(), service.getName())) {
return true;
}
}
return false;
}
- 骚扰拦截服务的开关
svBlack.toggle();// 开关切换
Intent intent = new Intent(this, CallSmsService.class);
// 打开或者关闭 骚扰拦截的服务
if (ServiceStateUtils.isRunning(getApplicationContext(),
CallSmsService.class)) {
// 服务正在运行 去关闭
stopService(intent);
} else {
// 服务关闭 去开启
startService(intent);
}
- 回显数据 在onStart
// 回显数据 骚扰拦截服务状态 关于服务的回显 要写在onStart 防止home键后去设置里手动关闭服务
boolean isRunBlack = ServiceStateUtils.isRunning(
getApplicationContext(), CallSmsService.class);
svBlack.setToggleOn(isRunBlack);
- 短信拦截
// 动态注册黑名单短信拦截的广播
IntentFilter filter = new IntentFilter();
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(blackSmsReceiver, filter);
在receiver里根据电话查询拦截方式 而后拦截广播
int type = blackDao.find(phone);// 获取拦截方式
if (type == BlackDbConstants.TYPE_SMS
|| type == BlackDbConstants.TYPE_ALL) {
abortBroadcast();// 拦截短信
}
- 电话拦截
// 拦截电话
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// 监听来电状态
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
private PhoneStateListener listener = new PhoneStateListener() {
public void onCallStateChanged(int state, String incomingNumber) {
// TelephonyManager#CALL_STATE_IDLE 空闲状态
// TelephonyManager#CALL_STATE_RINGING 来电
// TelephonyManager#CALL_STATE_OFFHOOK 摘机/接听
if (state == TelephonyManager.CALL_STATE_RINGING) {
// 根据电话号码获取拦截方式
int type = blackDao.find(incomingNumber);
if (type == BlackDbConstants.TYPE_PHONE
|| type == BlackDbConstants.TYPE_ALL) {
System.out.println("挂断电话");
endCall(incomingNumber);
}
}
};
};
- 挂断电话 aidl
> 把ITelephony.aidl NeighboringCellInfo.aidl 拷贝到对应包下面
> // 经过反射调用ServiceManager的getService方法 获取IBinder对象
Method declaredMethod = mTm.getClass().getDeclaredMethod(
"getITelephony", null);
declaredMethod.setAccessible(true);
ITelephony itelephony = declaredMethod.invoke(mTm, null);
iTelephony.endCall();// 挂断电话 须要权限
- 删除通话记录
// 删除通话记录
final ContentResolver cr = getContentResolver();
final Uri url = CallLog.Calls.CONTENT_URI;// 通话记录的uri
final String where = CallLog.Calls.NUMBER + "=?";// 电话的字段名
final String[] selectionArgs = new String[] { incomingNumber };
// 注册一个观察者 "content://call_log/calls/"
// 参2 是否监听子uri
cr.registerContentObserver(url, true, new ContentObserver(
null) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// 数据库发生变化 就去删除
// 删除经过记录 权限android.permission.WRITE_CALL_LOG
cr.delete(url, where, selectionArgs);
// 解除观察者 尽快释放
cr.unregisterContentObserver(this);
}
});
### 查询归属地 ###
- 布局... 点击查询后的处理
- 输入框为空的颤抖动画提醒 全局搜索 要会 动画重点动画重点动画重点动画重点动画重点动画重点动画重点动画重点动画重点
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
etNum.startAnimation(shake);
shake 是一个xml写的动画
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXDelta="0"
android:interpolator="@anim/cycle_7"
android:toXDelta="10" />
cycle_7 是一个插值器\插补器
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="7" />
- 关于插值器 Interpolator http://my.oschina.net/banxi/blog/135633 一个介绍插值器的博客
API DEMO有插值器的效果
AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速
AccelerateInterpolator 在动画开始的地方速率改变比较慢,而后开始加速
AnticipateInterpolator 开始的时候向后而后向前甩
AnticipateOvershootInterpolator 开始的时候向后而后向前甩必定值后返回最后的值
BounceInterpolator 动画结束的时候弹起
CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
DecelerateInterpolator 在动画开始的地方快而后慢
LinearInterpolator 以常量速率改变
OvershootInterpolator 结束时向前甩必定值后再回到原来位置
## 在启动页面加载地址查询数据库 ##
- 把数据库放到assets目录下 copy到files缓存里面
/**
* 载入归属地数据库
*/
private void copyAddrDb() {
copyDb("address.db");
}
/**
* 载入数据库
*/
private void copyDb(String dbName) {
InputStream is = null;
FileOutputStream fos = null;
// 资产管理器
AssetManager assets = getAssets();
// 获取assets里的address.db文件流
try {
is = assets.open(dbName);
// data/data/com.xxx.ff/files/address.db
File targFile = new File(getFilesDir(), dbName);
if (targFile.exists()) {
// 若是存在 就再也不copy
return;
}
fos = new FileOutputStream(targFile);// 写入流
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
StreamTools.endStream(is);
StreamTools.endStream(fos);
}
}
/**
* 关闭流
* @param stream
*/
public static void endStream(Closeable stream) {
if(stream!=null){
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-----------------------------------------------------------------------------------------------------------------------------
- 写一个归属地查询的DAO
// 根据手机号查询归属地
public static String getAddress(Context ctx, String number) {
SQLiteDatabase db = SQLiteDatabase.openDatabase(new File(ctx.getFilesDir(),
"address.db").getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
String sql = "select cardtype from info where mobileprefix = ?";
String address = "未知";
// 判断是否是手机号
// 正则 1 3/4/5/7/8 9位数字 ^1[34578]\d{9}$
if (number.matches("^1[34578]\\d{9}$")) {
String num = number.substring(0, 7);
String[] selectionArgs = new String[] { num };
Cursor cursor = db.rawQuery(sql, selectionArgs);
if (cursor != null) {
if (cursor.moveToNext()) {
address = cursor.getString(0);
}
cursor.close();
}
return address;
} else {
// 非手机号
int length = number.length();
switch (length) {
case 3:
address = "紧急电话";
break;
case 4:
address = "模拟器";
break;
case 5:
address = "服务电话";
break;
case 7:
case 8:
address = "本地电话";
break;
case 10:
case 11:
case 12:
sql = "select distinct city from info where area = ?";
String num = number.substring(0, 3);
String[] selectionArgs = new String[] { num };
Cursor cursor = db.rawQuery(sql, selectionArgs);
if (cursor != null) {
if (cursor.moveToNext()) {
address = cursor.getString(0);
}
cursor.close();
}
if (TextUtils.equals(address, "未知")) {
num = number.substring(0, 4);
selectionArgs = new String[] { num };
cursor = db.rawQuery(sql, selectionArgs);
if (cursor != null) {
if (cursor.moveToNext()) {
address = cursor.getString(0);
}
cursor.close();
}
}
break;
default:
break;
}
// 3 110 120 119 紧急电话
// 4 5556 模拟器
// 5 10086 10010 服务电话
// 7 本地固定电话 6212888
// 8 本地固定电话 62128889
// 10 010 6212888 带区号的固定电话
// 11 010 62128889 0535 6212888 带区号的固定电话
// 12 0535 62128889 带区号的固定电话
}
return address;
}
- 在归属地查询界面 实现查询
String address = AddressADO.getAddress(getApplicationContext(), number);
tvLoc.setText(address);
- 根据输入框输入文字的改变实时查询,要监听输入框的变化
// 监听文字改变
etNum.addTextChangedListener(new TextWatcher() {
// 显示在输入框上
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
// 将要显示在输入框上
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
// 显示在输入框后
@Override
public void afterTextChanged(Editable s) {
String num = s.toString();
// 每次输入框文字改变后 实时查询更新
String address = AddressADO.getAddress(getApplicationContext(), num);
tvLoc.setText(address);
}
});