这个布局是自己为了自己设置是否显示归属地的。
public class CallAddressDao {
//数据库路径
private static final String PATH = "data/data/com.sjr.calladdress/files/address.db";
/**
* 从数据库中获取手机归属地
* @param number
* @return 手机归属地
*/
public static String getCallAddress(String number){
String callAddress = "未知号码";
//获取数据库对象
SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH, null,
SQLiteDatabase.OPEN_READONLY);
//正则表达式匹配
if (number.matches("^1[3-8]\\d{9}$")) {//匹配器11位手机号
Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)",
new String[]{number.substring(0, 7)});//截取前七个
if (cursor.moveToNext())
callAddress = cursor.getString(0);
cursor.close();
}else if (number.matches("^\\d+$")){//匹配数字
switch (number.length()){
case 3:
callAddress = "报警电话";//三位数就是报警电话
break;
case 4:
callAddress = "模拟器";
break;
case 5:
callAddress = "客服电话";
break;
case 7:
case 8:
callAddress = "本地电话";
break;
default:
if (number.startsWith("0")&&number.length()>10){//有可能是长途电话
//有些区号是4位,有些区号是3位(包括0)
// 先查询4位区号
Cursor cursor = database.rawQuery("select location from data2 where area =?",
new String[]{number.substring(1,4)});
if (cursor.moveToNext())
callAddress = cursor.getString(0);
else {
cursor.close();
//查询3位区号
cursor = database.rawQuery("select location from data2 where area =?",
new String[]{number.substring(1,3)});
if (cursor.moveToNext())
callAddress = cursor.getString(0);
cursor.close();
}
}
break;
}
}
database.close();//关闭数据库
return callAddress;
}
}
//获取数据库对象
SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH, null,
SQLiteDatabase.OPEN_READONLY);
PATH是数据库路径,第二个是一个可选的选项,直接传null,第三个然后我们只是要读,并不修改数据库,所以设置为只读
data/data/com.sjr.calladdress/files/address.db
这不能是assets文件夹下面的,只能是data/data/包名/ 目录下,不然是访问不到的,但是这时这个路径下是没有数据库的,所以我就在第一次进入主界面的时候先把数据库拷到这个目录下
/**
* 把assets目录下的数据库拷贝到data/data目录下
*
* @param dbName
*/
private void copyDB(String dbName) {
// File filesDir = getFilesDir();///data/data/com.sjr.calladdress/files
// Log.d("print","路径:"+filesDir.getAbsolutePath());
File desFile = new File(getFilesDir(), dbName);//要拷贝的目标地址
if (desFile.exists()) {//如果已经存在,就不进行拷贝工作直接返回
Log.d("print", "数据库" + dbName + "已经存在");
return;
}
//否则就进行数据库拷贝
FileOutputStream out = null;
InputStream in = null;
try {
in = getAssets().open(dbName);//读写assets文件夹下的数据库
out = new FileOutputStream(desFile);//写入data目录
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
if (in != null)
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面的代码就把数据库从assets文件夹下面拷贝到data目录下了
/**
* Created by 宋家任 on 2016/3/1 15:22.
* 监听来去电服务
*/
public class CallAddressService extends Service {
private TelephonyManager mTM;//电话管理者
private WindowManager mWM;//桌面窗口管理者
private MPhoneStateListener mListener;
private View mView;//桌面展示归属地的view
private OutCallReceiver mReceiver;//监听去电广播
private SharedPreferences mPref;//记录归属地的位置
private int startX;
private int startY;
private WindowManager.LayoutParams params;
private int windowWidth;
private int windowHeight;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
mPref = getSharedPreferences("config", MODE_PRIVATE);
mTM = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
mListener = new MPhoneStateListener();
mTM.listen(mListener, PhoneStateListener.LISTEN_CALL_STATE);
mReceiver = new OutCallReceiver();
//动态注册去电广播
IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
registerReceiver(mReceiver, filter);
}
/**
* 监听来电通话状态
* 需要权限
*
*/
class MPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:// 电话铃响
String address = CallAddressDao.getCallAddress(incomingNumber);//把来电号码跟数据库号码进行比对
showView(address);
break;
case TelephonyManager.CALL_STATE_IDLE:// 电话闲置状态
if (mWM != null && mView != null) {
mWM.removeView(mView);// 从window中移除view
mView = null;
}
break;
}
}
}
/**
* 去电广播
* 监听去电广播需要下面的权限
*
*/
class OutCallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String number = getResultData();//获取去电号码
String address = CallAddressDao.getCallAddress(number);//将去电号码跟数据库的进行比对
showView(address);
}
}
/**
*
* 弹自定义的界面
* 使用电话窗口需要下面的权限
*
* @param address
*/
private void showView(String address) {
//获得一个窗口管理者实例
mWM = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
windowWidth = mWM.getDefaultDisplay().getWidth();//获取屏幕宽度
windowHeight = mWM.getDefaultDisplay().getHeight();//获取屏幕高度
//布局参数
params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_PHONE;//电话窗口。用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
params.gravity = Gravity.LEFT + Gravity.TOP;// 将重心位置设置为左上方,
// 也就是(0,0)从左上方开始,而不是默认的重心位置
params.setTitle("Toast");
//先获取上次显示的位置
int lastX = mPref.getInt("lastX", 0);
int lastY = mPref.getInt("lastY", 0);
//设置悬浮窗的位置,基于左上方的偏移量
params.x = lastX;
params.y = lastY;
//要展示的view
mView = View.inflate(this, R.layout.view_address, null);
//给view设置背景
mView.setBackgroundResource(R.drawable.call_locate_blue);// 根据存储的样式更新背景
//设置文字
TextView tvText = (TextView) mView.findViewById(R.id.tv_number);
tvText.setText(address);
mWM.addView(mView, params);// 将view添加在屏幕上(Window)
//给这个归属地view设置监听,移动的时候记录坐标
mView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
// 初始化起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE://移动
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
// 计算移动偏移量
int dx = endX - startX;
int dy = endY - startY;
// 更新浮窗位置
params.x += dx;
params.y += dy;
// 防止坐标偏离屏幕
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}
// 防止坐标偏离屏幕
if (params.x > windowWidth - mView.getWidth()) {
params.x = windowWidth - mView.getWidth();
}
if (params.y > windowHeight - mView.getHeight()) {
params.y = windowHeight - mView.getHeight();
}
mWM.updateViewLayout(mView, params);//更新布局
// 重新初始化起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP://抬起
// 记录坐标点
SharedPreferences.Editor edit = mPref.edit();
edit.putInt("lastX", params.x);
edit.putInt("lastY", params.y);
edit.commit();
break;
default:
break;
}
return true;//返回true消费这个手势事件
}
});
}
/**
* 服务销毁的时候会回调这个方法
*/
@Override
public void onDestroy() {
mTM.listen(mListener, PhoneStateListener.LISTEN_NONE);//停止来电监听
unregisterReceiver(mReceiver);//注销监听去电广播
}
}
/**
* 去电广播
* 监听去电广播需要下面的权限
*
*/
class OutCallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String number = getResultData();//获取去电号码
String address = CallAddressDao.getCallAddress(number);//将去电号码跟数据库的进行比对
showView(address);
}
}
//动态注册去电广播
IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
registerReceiver(mReceiver, filter);
在服务关闭的时候就取消注册:
/**
* 服务销毁的时候会回调这个方法
*/
@Override
public void onDestroy() {
mTM.listen(mListener, PhoneStateListener.LISTEN_NONE);//停止来电监听
unregisterReceiver(mReceiver);//注销监听去电广播
}
/**
* 监听来电通话状态
* 需要权限
*
*/
class MPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:// 电话铃响
String address = CallAddressDao.getCallAddress(incomingNumber);//把来电号码跟数据库号码进行比对
showView(address);
break;
case TelephonyManager.CALL_STATE_IDLE:// 电话闲置状态
if (mWM != null && mView != null) {
mWM.removeView(mView);// 从window中移除view
mView = null;
}
break;
}
}
}
写一个类去继承PhoneStateListener,然后监听通话状态,注意也是需要权限的,然后同监听去电广播类似,在服务初始化的时候监听,在服务销毁的时候注销监听。
然后写一个方法在通话窗口展示归属地当然使用电话窗口也是需要权限的,权限如下
/**
*
* 弹自定义的界面
* 使用电话窗口需要下面的权限
*
* @param address
*/
private void showView(String address) {
//获得一个窗口管理者实例
mWM = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
windowWidth = mWM.getDefaultDisplay().getWidth();//获取屏幕宽度
windowHeight = mWM.getDefaultDisplay().getHeight();//获取屏幕高度
//布局参数
params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_PHONE;//电话窗口。用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
params.gravity = Gravity.LEFT + Gravity.TOP;// 将重心位置设置为左上方,
// 也就是(0,0)从左上方开始,而不是默认的重心位置
params.setTitle("Toast");
//先获取上次显示的位置
int lastX = mPref.getInt("lastX", 0);
int lastY = mPref.getInt("lastY", 0);
//设置悬浮窗的位置,基于左上方的偏移量
params.x = lastX;
params.y = lastY;
//要展示的view
mView = View.inflate(this, R.layout.view_address, null);
//给view设置背景
mView.setBackgroundResource(R.drawable.call_locate_blue);// 根据存储的样式更新背景
//设置文字
TextView tvText = (TextView) mView.findViewById(R.id.tv_number);
tvText.setText(address);
mWM.addView(mView, params);// 将view添加在屏幕上(Window)
//给这个归属地view设置监听,移动的时候记录坐标
mView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
// 初始化起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE://移动
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
// 计算移动偏移量
int dx = endX - startX;
int dy = endY - startY;
// 更新浮窗位置
params.x += dx;
params.y += dy;
// 防止坐标偏离屏幕
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y = 0;
}
// 防止坐标偏离屏幕
if (params.x > windowWidth - mView.getWidth()) {
params.x = windowWidth - mView.getWidth();
}
if (params.y > windowHeight - mView.getHeight()) {
params.y = windowHeight - mView.getHeight();
}
mWM.updateViewLayout(mView, params);//更新布局
// 重新初始化起点坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP://抬起
// 记录坐标点
SharedPreferences.Editor edit = mPref.edit();
edit.putInt("lastX", params.x);
edit.putInt("lastY", params.y);
edit.commit();
break;
default:
break;
}
return true;//返回true消费这个手势事件
}
});
}
上面的方法就是在通话界面显示自定义的view,为了便于理解我加了很多注释,然后在这里还是解释下好了,获得一个桌面管理者,然后设置归属地view的相关参数,然后把归属地view添加到桌面,然后给view设置一个监听,让view可以移动,上面用到了SharedPreferences,这是为了移动完归属地view后记录下位置然后下次初始就让这个view显示在那个位置,view布局如下:
背景是从网上找的一张.9图然后图标是Android系统的一个电话图标,效果如下
然后完成这个服务类以后只要在MainActivity里开启服务就好了,MainActivity代码如下:
/**
* Created by 宋家任 on 2016/5/6 13:55.
* 主界面
*/
public class MainActivity extends Activity implements View.OnClickListener {
private Button btnShow;//显示归属地
private Button btnNoShow;//不显示归属地
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyDB("address.db");// 拷贝归属地查询数据库
initDatas();
}
/**
* 把assets目录下的数据库拷贝到data/data目录下
*
* @param dbName
*/
private void copyDB(String dbName) {
// File filesDir = getFilesDir();///data/data/com.sjr.calladdress/files
// Log.d("print","路径:"+filesDir.getAbsolutePath());
File desFile = new File(getFilesDir(), dbName);//要拷贝的目标地址
if (desFile.exists()) {//如果已经存在,就不进行拷贝工作直接返回
Log.d("print", "数据库" + dbName + "已经存在");
return;
}
//否则就进行数据库拷贝
FileOutputStream out = null;
InputStream in = null;
try {
in = getAssets().open(dbName);//打开assets下的数据库
out = new FileOutputStream(desFile);
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
if (in != null)
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void initDatas() {
btnNoShow = (Button) findViewById(R.id.btn_show_address);
btnShow = (Button) findViewById(R.id.btn_noshow_address);
btnShow.setOnClickListener(this);
btnNoShow.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_show_address:
startService(new Intent(this, CallAddressService.class));//开启显示归属地服务
break;
case R.id.btn_noshow_address:
stopService(new Intent(this, CallAddressService.class));//关闭显示归属地服务
break;
}
}
}
到这里整个归属地的设置就已经完成了,下面是实际运行结果
源码地址为http://download.csdn.net/detail/lxzmmd/9512104