## 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);
}
});