手机安全卫士第四天

一、归属地数据库介绍 & 数据库拷贝

号码归属地查询常用两种做法:
第一种:联网查询。
第二种:把数据库放在本地。(采用第二种)
数据库的来源,可以在淘宝上购买,但是买的数据不一定是Android下用的数据库。如果在Android上使用得自己做一个,把数据库写到Android的数据库里。
这里我们使用已经下载好的小米数据库,使用SQLite工具打开:
观察data1、data2表的设计结果:


根据电话号码,在data1、data2两表之间进行多表查询,查询号码的归属地:

将已下载好的数据库拷贝到项目assets目录下:
assets目录下存放资源文件(音频,图片,xml...),不会自动生成id且不会自动占用空间。这个目录用得不多,主要可以存放一些随程序打包的文件,在你的程序运行时可以动态读取到这些文件的内容。另外,如果你的程序中使用到了WebView加载本地网页的功能,所有网页相关的文件也都存放在这个目录下。

前面我们介绍过闪屏页面SplashActivity.java主要负责初始化数据,检测版本更新,校验网络是否正常等功能。因此,我们将数据库的拷贝功能,放在该页面,让应用启动的时候就已经将归属地查询数据库拷贝到了响应的目录下了。
SplashActivity.java
package com.xbmu.mobilesafe;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.xbmu.mobilesafe.tools.StreamUtils;

/**
 * 启动页面,完成版本更新功能。
 * 
 * @author Administrator
 * 
 */
public class SplashActivity extends Activity {

	// ...
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_splash);
		tvVersion = (TextView) findViewById(R.id.tv_version);
		tvProgress = (TextView) findViewById(R.id.tv_progress);// 默认是隐藏的
		
		rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
		
		tvVersion.setText("版本号:" + getVersionName());
		
		copyDB("address.db");//拷贝归属地查询数据库
		
		sPre = getSharedPreferences("config", MODE_PRIVATE);
		boolean isChecked = sPre.getBoolean("checked", true);
		if(isChecked){
			//从服务器获取最新版本号并进行校验
			checkVersion();			
		}else{
			handler.sendEmptyMessageDelayed(CODE_ENTER_HOME, 2000);//延迟2秒后发送消息
		}
		
		//渐变的动画效果
		AlphaAnimation aa = new AlphaAnimation(0.3f, 1.0f);
		aa.setDuration(3000);
		rlRoot.setAnimation(aa);
	}

	//...
	/**拷贝数据库*/
	private void copyDB(String dbName){
//		File filesDir = getFilesDir();
//		System.out.println("路径:"+filesDir.getAbsolutePath());//路径:/data/data/com.xbmu.mobilesafe/files
		
		//要拷贝的目标地址
		File destFile = new File(getFilesDir(), dbName);
		if(destFile.exists()){
			System.out.println("数据库:"+dbName+"已经存在");
			return ;
		}
		FileOutputStream out = null;
		InputStream in = null;
		try {
			in = getAssets().open(dbName);
			out = new FileOutputStream(destFile);
			
			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 {
				in.close();
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
运行应用程序后,该归属地数据库已经拷贝到了 /data/data/com.xbmu.mobilesafe/files 该路径下了。

二、正则匹配号码 & 数据库查询

AddressDao.java
package com.xbmu.mobilesafe.db.dao;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 归属地查询工具
 * 
 * @author Administrator
 * 
 */
public class AddressDao {
	// 注意:该路径必须是data/data目录的文件,否则数据库访问不到
	private static final String PATH = "data/data/com.xbmu.mobilesafe/files/address.db";

	public static String getAddress(String number) {
		String address = "未知号码";
		// 获取数据库对象
		SQLiteDatabase database = SQLiteDatabase.openDatabase(PATH, null,
				SQLiteDatabase.OPEN_READONLY);

		// 手机号码:1 + (3,4,5,6,7,8) + (9位数字)
		// 正则表达式 : ^1[3-8]\d{9}$
		if (number.matches("^1[3-8]\\d{9}$")) {// 匹配手机号码
			// rawQuery(String sql, String[] selectionArgs):执行带占位符的SQL查询
			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()) {// 将记录指针移动到下一行,如果移动成功则返回true
				address = cursor.getString(0);
			}
			cursor.close();
		} else if (number.matches("^\\d+$")) {// 匹配数字
			switch (number.length()) {
			case 3:
				address = "报警电话";
				break;
			case 4:
				address = "模拟器";
				break;
			case 5:
				address = "客服电话";
				break;
			case 7:
			case 8:
				address = "本地电话";
				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()) {
						address = cursor.getString(0);
					} else {
						// 查询3位区号
						cursor = database.rawQuery(
								"select location from data2 where area = ?",
								new String[] { number.substring(1, 3) });
						if (cursor.moveToNext()) {
							address = cursor.getString(0);
						}
						cursor.close();
					}
					cursor.close();
				}
				break;
			}
		}
		database.close();// 关闭数据库
		return address;
	}

}
HomeActivity.java

activity_tools.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="@style/TitleStyle"
        android:text="高级工具" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button"
        android:clickable="true"
        android:drawableLeft="@android:drawable/ic_menu_camera"
        android:drawablePadding="5dp"
        android:gravity="center_vertical"
        android:onClick="numberAddressQuery"
        android:padding="10dp"
        android:text="电话归属地查询"
        android:textColor="#000"
        android:textSize="20sp" />

</LinearLayout>
res/values/button.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/button_pressed" android:state_pressed="true"/>
    <!-- pressed -->
    <item android:drawable="@drawable/button_pressed" android:state_focused="true"/>
    <!-- focused -->
    <item android:drawable="@drawable/button_normal"/>
    <!-- default -->

</selector>
AToolsActivity.java
package com.xbmu.mobilesafe;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
/**
 * 高级工具
 * @author Administrator
 *
 */
public class AToolsActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_tools);
	}
	/**号码归属地查询*/
	public void numberAddressQuery(View view){
		startActivity(new Intent(this, AddressActivity.class));
	}
}
activity_address.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="@style/TitleStyle"
        android:text="归属地查询" />
   <EditText android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:id="@+id/et_number"
       android:inputType="phone"/>
   <Button android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="查询"
       android:onClick="query"/>
   <TextView android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/tv_result"
       android:text="查询结果"
       android:textSize="18sp"
       android:padding="5dp"
       />

</LinearLayout>
AddressActivity.java
package com.xbmu.mobilesafe;

import com.xbmu.mobilesafe.db.dao.AddressDao;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class AddressActivity extends Activity {
	private EditText etNumber;
	private TextView tvResult;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_address);
		etNumber = (EditText) findViewById(R.id.et_number);
		tvResult = (TextView) findViewById(R.id.tv_result);
	}
	/**归属地查询*/
	public void query(View view){
		String number = etNumber.getText().toString().trim();
		if(!TextUtils.isEmpty(number)){
			String address = AddressDao.getAddress(number);
			tvResult.setText(address);
		}
	}
}
运行效果:

三、文本监听器

AddressActivity.java
package com.xbmu.mobilesafe;

import com.xbmu.mobilesafe.db.dao.AddressDao;

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class AddressActivity extends Activity {
	private EditText etNumber;
	private TextView tvResult;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_address);
		etNumber = (EditText) findViewById(R.id.et_number);
		tvResult = (TextView) findViewById(R.id.tv_result);
		
		
		//设置监听事件,监听EditText中的文本变化
		etNumber.addTextChangedListener(new TextWatcher() {
			//文本变化了,回调该方法
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) {
				String address = AddressDao.getAddress(s.toString());
				tvResult.setText(address);
			}
			//文本变化之前,回调该方法
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				// TODO Auto-generated method stub
				
			}
			//文本变化之后,回调该方法
			@Override
			public void afterTextChanged(Editable s) {
				// TODO Auto-generated method stub
				
			}
		});
		
	}
	/**归属地查询*/
	public void query(View view){
		String number = etNumber.getText().toString().trim();
		if(!TextUtils.isEmpty(number)){
			String address = AddressDao.getAddress(number);
			tvResult.setText(address);
		}
	}
}

运行效果:


四、抖动效果 & 插补器

抖动效果:

什么是抖动效果啊,我们通过api demos中的应用实例,运行可观察:

上图的运行效果就是抖动效果,当我们点击登录按钮时,输入框会左右浮动,这种效果就是抖动效果,能给用户带来良好的体验效果。
要写写出这样的抖动效果,我们可以参考api demos中的实例,向工作区间中导入api demos工程。
小技巧:
比如说,要想查出上图抖动效果的源码,首先你观察上图界面中的布局,从布局着手,上图有个Please enter your password:这个TextView。
因此,我们利用eclipse中的search功能查看该文本所在的布局,进而查出引用该布局的activity组件...
1.选中ApiDemos项目,点击上面菜单栏中的Search菜单,选择File

2.点击search按钮,在slected resources资源区域搜素该字段所在的文件。

重复上面两个步骤,这次搜素的是animation_1_instructions

这次搜素animation_1



利用上面的方法,依次搜素,变查找到了抖动效果的源码,源码在Animation1.java文件中。便可参考源码,将抖动效果挪用到自己的项目中去。
以上搜素的小技巧,在以后项目开发中经常会使用,当你接手一个正在开发的项目时,修改或者添加项目中某项功能,即可用该方法快速定位到要修改的代码的地方。
AddressActivity.java
/** 归属地查询 */
	public void query(View view) {
		String number = etNumber.getText().toString().trim();
		if (!TextUtils.isEmpty(number)) {
			String address = AddressDao.getAddress(number);
			tvResult.setText(address);
		} else {
			//为空的时候,点击查询按钮,输入框会发生抖动效果,提醒用户。
			Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
			etNumber.startAnimation(shake);
		}
	}
res/anim/shake.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXDelta="0"
    android:interpolator="@anim/cycle_7"
    android:toXDelta="10" />
res/anim/cycle_7.xml
<?xml version="1.0" encoding="utf-8"?>
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:cycles="7" />
运行效果:
实现自己的插补器:
/** 归属地查询 */
	public void query(View view) {
		String number = etNumber.getText().toString().trim();
		if (!TextUtils.isEmpty(number)) {
			String address = AddressDao.getAddress(number);
			tvResult.setText(address);
		} else {
			//为空的时候,点击查询按钮,输入框会发生抖动效果,提醒用户。
			Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
			/**
			//实现自己的插补器
			shake.setInterpolator(new Interpolator() {
				
				@Override
				public float getInterpolation(float x) {
					//实现自己插补器的业务逻辑代码
					float y = x;
					return y;
				}
			});
			*/
			etNumber.startAnimation(shake);
		}
	}

五、震动器实现

AddressActivity.java
package com.xbmu.mobilesafe;
import android.app.Activity;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.EditText;
import android.widget.TextView;

import com.xbmu.mobilesafe.db.dao.AddressDao;

public class AddressActivity extends Activity {
	private EditText etNumber;
	private TextView tvResult;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_address);
		etNumber = (EditText) findViewById(R.id.et_number);
		tvResult = (TextView) findViewById(R.id.tv_result);

		// 设置监听事件,监听EditText中的文本变化
		etNumber.addTextChangedListener(new TextWatcher() {
			// 文本变化了,回调该方法
			@Override
			public void onTextChanged(CharSequence s, int start, int before,
					int count) {
				String address = AddressDao.getAddress(s.toString());
				tvResult.setText(address);
			}

			// 文本变化之前,回调该方法
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				// TODO Auto-generated method stub

			}

			// 文本变化之后,回调该方法
			@Override
			public void afterTextChanged(Editable s) {
				// TODO Auto-generated method stub

			}
		});

	}

	/** 归属地查询 */
	public void query(View view) {
		String number = etNumber.getText().toString().trim();
		if (!TextUtils.isEmpty(number)) {
			String address = AddressDao.getAddress(number);
			tvResult.setText(address);
		} else {
			//为空的时候,点击查询按钮,输入框会发生抖动效果,提醒用户。
			Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
			/**
			//实现自己的插补器
			shake.setInterpolator(new Interpolator() {
				
				@Override
				public float getInterpolation(float x) {
					//实现自己插补器的业务逻辑代码
					float y = x;
					return y;
				}
			});
			*/
			etNumber.startAnimation(shake);
			virbate();//手机震动
		}
	}
	/**手机震动,需要权限android.permission.VIBRATE*/
	private void virbate(){
		Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
		//vibrator.vibrate(2000);震动两秒
		/**
		 * 先等待1秒,再震动2秒,再等待1秒,再震动3秒,
		 * 参数2等于-1表示只执行一次,不循环
		 * 参数2等于0表示凑够头循环,
		 * 参数2表示从第几个位置开始循环
		 */
		vibrator.vibrate(new long[]{1000, 2000,3000},0);
	}
}

六、来电归属地显示

使用服务service在后台监听来电的状态,Android内部也是这么要求的。
AddressService.java
package com.xbmu.mobilesafe.service;

import com.xbmu.mobilesafe.db.dao.AddressDao;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.Toast;
/**
 * 来电提醒的服务
 * @author Administrator
 *
 */
public class AddressService extends Service {

	private TelephonyManager tm;
	private MyListener listener;
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void onCreate() {
		super.onCreate();
		//监听来电
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
		listener = new MyListener();
		tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);//监听来电的状态
	}
	class MyListener extends PhoneStateListener{
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			super.onCallStateChanged(state, incomingNumber);
			switch (state) {
			case TelephonyManager.CALL_STATE_RINGING:
				System.out.println("电话响铃了...");
				String address = AddressDao.getAddress(incomingNumber);//根据来电号码查询归属地
				Toast.makeText(AddressService.this, address, 1).show();
				break;

			default:
				break;
			}
		}
	}
	@Override
	public void onDestroy() {
		super.onDestroy();
		tm.listen(listener, PhoneStateListener.LISTEN_NONE);//停止来电监听
	}

}
在清单文件中配置该服务:
<service android:name="com.xbmu.mobilesafe.service.AddressService" />
在设置中心,设置归属地是否显示的开关
SettingActivity.java
package com.xbmu.mobilesafe;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

import com.xbmu.mobilesafe.service.AddressService;
import com.xbmu.mobilesafe.tools.ServiceStatusUtils;
import com.xbmu.mobilesafe.view.SettingItemView;

public class SettingActivity extends Activity {
	private SettingItemView mSivUpDate;// 设置升级
	private SharedPreferences sPre;
	private SettingItemView mSivAddress;// 设置归属地

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_setting);
		sPre = getSharedPreferences("config", MODE_PRIVATE);
		
		initUpdateView();
		initAddressView();
	}

	/** 初始化自动更新开关 */
	private void initUpdateView() {
		mSivUpDate = (SettingItemView) findViewById(R.id.siv_update);

		boolean isChecked = sPre.getBoolean("checked", true);
		if (isChecked) {
			mSivUpDate.setChecked(true);
		} else {
			mSivUpDate.setChecked(false);
		}

		// 设置监听事件
		mSivUpDate.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// 判断当前的勾选状态
				if (mSivUpDate.isChecked()) {
					// 设置不勾选
					mSivUpDate.setChecked(false);

					// 更新SharedPreferences
					sPre.edit().putBoolean("checked", false).commit();
				} else {
					mSivUpDate.setChecked(true);
					sPre.edit().putBoolean("checked", true).commit();
				}

			}
		});

	}

	/** 初始化归属地开关 */
	private void initAddressView() {
		mSivAddress = (SettingItemView) findViewById(R.id.siv_address);
		
		boolean serviceRunning = ServiceStatusUtils.isServiceRunning(this, "com.xbmu.mobilesafe.service.AddressService");
		//这样处理的好处避免服务一直运行在后台,能够节约电量
		if (serviceRunning) {//如果归属地服务在运行,在选中checkbox
			mSivAddress.setChecked(true);
		}else{//如果归属地服务没有在运行,不选中checkbox
			mSivAddress.setChecked(false);
		}
		
		mSivAddress.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (mSivAddress.isChecked()) {
					mSivAddress.setChecked(false);
					stopService(new Intent(SettingActivity.this,
							AddressService.class));//停止来电归属地服务
				} else {
					mSivAddress.setChecked(true);
					startService(new Intent(SettingActivity.this,
							AddressService.class));//开启来电归属地服务
				}
			}
		});
	}
}
activity_setting.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xbmu="http://schemas.android.com/apk/res/com.xbmu.mobilesafe"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="@style/TitleStyle"
        android:text="设置中心" />

    <com.xbmu.mobilesafe.view.SettingItemView
        android:id="@+id/siv_update"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        xbmu:desc_off="自动更新已经关闭"
        xbmu:desc_on="自动更新已经开启"
        xbmu:title="设置自动更新" />
    <com.xbmu.mobilesafe.view.SettingItemView
        android:id="@+id/siv_address"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        xbmu:desc_off="归属地显示已经关闭"
        xbmu:desc_on="归属地显示已经开启"
        xbmu:title="电话归属地显示设置" />

</LinearLayout>
ServiceStatusUtils.java
package com.xbmu.mobilesafe.tools;

import java.util.List;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.Context;

/**
 * 服务状态工具类
 * 
 * @author Administrator
 */
public class ServiceStatusUtils {
	/**
	 * 检测服务是否正在运行
	 * 
	 * @return
	 */
	public static boolean isServiceRunning(Context context,String serviceName) {
		ActivityManager am = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);
		//获取系统所有正在运行的服务,这里设置最多返回100个
		List<RunningServiceInfo> runningServices = am.getRunningServices(100);
		for (RunningServiceInfo runningServiceInfo : runningServices) {
			String className = runningServiceInfo.service.getClassName();//获取服务的名称
//			System.out.println(className);
			if(className.equals(serviceName)){//服务存在
				return true;
			}
		}
		return false;
	}
}
运行效果:
运行效果说明:当我们打开归属地显示设置的时候,后台就会开启一个服务:

当我们关闭归属地显示设置的时候,后台就没有这样的一个服务:

七、去电归属地显示 & 动态注册广播

在Android内部要求,使用广播接受者接收去电的电话号码。从而判断该电话号码的归属地。

静态注册广播:

OutCallReceiver.java
package com.xbmu.mobilesafe.receiver;

import com.xbmu.mobilesafe.db.dao.AddressDao;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
 * 监听去电的广播接收者
 * 需要权限:android.permission.PROCESS_OUTGOING_CALLS
 * @author Administrator
 *
 */
public class OutCallReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		String number = getResultData();//获取去电电话号码
		String address = AddressDao.getAddress(number);
		Toast.makeText(context, address, Toast.LENGTH_LONG).show();

	}

}
在清单文件中静态注册该广播接收者
<receiver android:name="com.xbmu.mobilesafe.receiver.OutCallReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>
在清单文件中加入权限:
/**允许程序监视、修改有关播出电话*/
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
运行效果图:

动态注册广播:

AddressService.java
package com.xbmu.mobilesafe.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.Toast;

import com.xbmu.mobilesafe.db.dao.AddressDao;
/**
 * 来电提醒的服务
 * @author Administrator
 *
 */
public class AddressService extends Service {

	private TelephonyManager tm;
	private MyListener listener;
	private OutCallReceiver receiver;
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public void onCreate() {
		super.onCreate();
		//监听来电
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
		listener = new MyListener();
		tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);//监听来电的状态
		
		receiver = new OutCallReceiver();
		IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
		registerReceiver(receiver, filter);//动态注册广播
	}
	class MyListener extends PhoneStateListener{
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			super.onCallStateChanged(state, incomingNumber);
			switch (state) {
			case TelephonyManager.CALL_STATE_RINGING:
				System.out.println("电话响铃了...");
				String address = AddressDao.getAddress(incomingNumber);//根据来电号码查询归属地
				Toast.makeText(AddressService.this, address, 1).show();
				break;

			default:
				break;
			}
		}
	}
	/**
	 * 监听去电的广播接收者
	 * 需要权限:android.permission.PROCESS_OUTGOING_CALLS
	 * @author Administrator
	 *
	 */
	class OutCallReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			String number = getResultData();//获取去电电话号码
			String address = AddressDao.getAddress(number);
			Toast.makeText(context, address, Toast.LENGTH_LONG).show();

		}
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		tm.listen(listener, PhoneStateListener.LISTEN_NONE);//停止来电监听
		
		unregisterReceiver(receiver);//注销广播
	}

}
在清单文件中注释掉静态注册的广播:


运行效果和前面的一样,但是要注意的是,要在开启服务的前提下。因为这个去电的广播是在AddressService类中onCreate()方法中注册,在onDestory()方法中注销的。

八、使用WindowManager在窗口上显示view

AddressService.java
package com.xbmu.mobilesafe.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

import com.xbmu.mobilesafe.db.dao.AddressDao;

/**
 * 来电提醒的服务
 * 
 * @author Administrator
 * 
 */
public class AddressService extends Service {

	//....

	/**
	 * 监听去电的广播接收者 需要权限:android.permission.PROCESS_OUTGOING_CALLS
	 * 
	 * @author Administrator
	 * 
	 */
	class OutCallReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			String number = getResultData();// 获取去电电话号码
			String address = AddressDao.getAddress(number);
			//Toast.makeText(context, address, Toast.LENGTH_LONG).show();
			showToast(address);

		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		tm.listen(listener, PhoneStateListener.LISTEN_NONE);// 停止来电监听

		unregisterReceiver(receiver);// 注销广播
	}

	/** 自定义归属地浮窗 */
	private void showToast(String text) {
		// 可以在第三方app中弹出自己的浮窗
		WindowManager mWM = (WindowManager) this
				.getSystemService(Context.WINDOW_SERVICE);
		WindowManager.LayoutParams params = new WindowManager.LayoutParams();
		params.width = WindowManager.LayoutParams.WRAP_CONTENT;
		params.height = 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;
		params.setTitle("Toast");
		
		TextView view = new TextView(this);
		view.setText(text);
		view.setTextColor(Color.RED);
		mWM.addView(view, params);//将view添加在屏幕上(Window)
	}
}
运行效果:
从运行效果上看来实现了在窗口上显示view的效果,但是存在一个小bug。挂完电话后这个显示的view一直在,不会消失。
因此,我们必须解决这个bug。
思路:在电话闲置状态的时候,从window中移除该view:
AddressService.java
package com.xbmu.mobilesafe.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.WindowManager;
import android.widget.TextView;

import com.xbmu.mobilesafe.db.dao.AddressDao;

/**
 * 来电提醒的服务
 * 
 * @author Administrator
 * 
 */
public class AddressService extends Service {

	private TelephonyManager tm;
	private MyListener listener;
	private OutCallReceiver receiver;
	private TextView view;
	private WindowManager mWM;

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		// 监听来电
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
		listener = new MyListener();
		tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);// 监听来电的状态

		receiver = new OutCallReceiver();
		IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
		registerReceiver(receiver, filter);// 动态注册广播
	}

	class MyListener extends PhoneStateListener {
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			super.onCallStateChanged(state, incomingNumber);
			switch (state) {
			case TelephonyManager.CALL_STATE_RINGING:
				System.out.println("电话响铃了...");
				String address = AddressDao.getAddress(incomingNumber);// 根据来电号码查询归属地
				showToast(address);
				break;
			case TelephonyManager.CALL_STATE_IDLE://电话闲置状态
				if(mWM!=null && view!=null){
					mWM.removeView(view);//从Window中移除view
					view = null;
				}
				break;
			default:
				break;
			}
		}
	}

	/**
	 * 监听去电的广播接收者 需要权限:android.permission.PROCESS_OUTGOING_CALLS
	 * 
	 * @author Administrator
	 * 
	 */
	class OutCallReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			String number = getResultData();// 获取去电电话号码
			String address = AddressDao.getAddress(number);
			//Toast.makeText(context, address, Toast.LENGTH_LONG).show();
			showToast(address);

		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		tm.listen(listener, PhoneStateListener.LISTEN_NONE);// 停止来电监听

		unregisterReceiver(receiver);// 注销广播
	}

	/** 自定义归属地浮窗 */
	private void showToast(String text) {
		mWM = (WindowManager) this
				.getSystemService(Context.WINDOW_SERVICE);
		WindowManager.LayoutParams params = new WindowManager.LayoutParams();
		params.width = WindowManager.LayoutParams.WRAP_CONTENT;
		params.height = 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;
		params.setTitle("Toast");
		
		view = new TextView(this);
		view.setText(text);
		view.setTextColor(Color.RED);
		mWM.addView(view, params);//将view添加在屏幕上(Window)
	}
}
运行效果图:

九、归属地界面优化

toast_address.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
	android:background="@drawable/call_locate_blue"
	android:gravity="center_vertical"
    android:orientation="horizontal" >
    <ImageView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView1"
        android:src="@android:drawable/ic_menu_call"/>
	<TextView android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:text="未知电话"
	    android:textSize="18sp"
	    android:textColor="#fff"
	    android:id="@+id/tv_number"/>
</LinearLayout>
AddressService.java
/** 自定义归属地浮窗 */
	private void showToast(String text) {
		mWM = (WindowManager) this
				.getSystemService(Context.WINDOW_SERVICE);
		WindowManager.LayoutParams params = new WindowManager.LayoutParams();
		params.width = WindowManager.LayoutParams.WRAP_CONTENT;
		params.height = 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;
		params.setTitle("Toast");
		
//		view = new TextView(this);
		view = View.inflate(this, R.layout.toast_address, null);
		TextView tvText = (TextView) view.findViewById(R.id.tv_number);
		tvText.setText(text);
		mWM.addView(view, params);//将view添加在屏幕上(Window)
	}
运行效果:

十、自定义归属地显示风格:

运行效果图:

代码实现:
res/layout/view_setting_click.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="65dp"
        android:padding="5dp" >

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/black"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_title"
            android:textColor="#a000"
            android:textSize="15sp" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true" 
            android:src="@drawable/jiantou1_pressed"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_alignParentBottom="true"
            android:background="#6000" />
    </RelativeLayout>

</LinearLayout>
SettingClickView.java
package com.xbmu.mobilesafe.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.xbmu.mobilesafe.R;

public class SettingClickView extends RelativeLayout {

	private TextView tvTitle;
	private TextView tvDesc;
	public SettingClickView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initView();
	}

	public SettingClickView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		//根据属性名,获取属性值
		initView();
	}

	public SettingClickView(Context context) {
		super(context);
		initView();
	}
	/**初始化布局*/
	private void initView() {
		//将自定义好的布局文件设置给当前的SettingClickView
		View.inflate(getContext(), R.layout.view_setting_click, this);
		tvTitle = (TextView) findViewById(R.id.tv_title);
		tvDesc = (TextView) findViewById(R.id.tv_desc);
		
	}
	/**设置标题*/
	public void setTitle(String title){
		tvTitle.setText(title);
	}
	/**设置描述*/
	public void setDesc(String desc){
		tvDesc.setText(desc);
	}
}
activity_setting.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:xbmu="http://schemas.android.com/apk/res/com.xbmu.mobilesafe"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="@style/TitleStyle"
        android:text="设置中心" />

    <com.xbmu.mobilesafe.view.SettingItemView
        android:id="@+id/siv_update"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        xbmu:desc_off="自动更新已经关闭"
        xbmu:desc_on="自动更新已经开启"
        xbmu:title="设置自动更新" />
    <com.xbmu.mobilesafe.view.SettingItemView
        android:id="@+id/siv_address"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        xbmu:desc_off="归属地显示已经关闭"
        xbmu:desc_on="归属地显示已经开启"
        xbmu:title="电话归属地显示设置" />
    <com.xbmu.mobilesafe.view.SettingClickView
        android:id="@+id/scv_address_style"
        android:layout_width="match_parent"
        android:layout_height="60dp" />

</LinearLayout>
SettingActivity.java
package com.xbmu.mobilesafe;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

import com.xbmu.mobilesafe.service.AddressService;
import com.xbmu.mobilesafe.tools.ServiceStatusUtils;
import com.xbmu.mobilesafe.view.SettingClickView;
import com.xbmu.mobilesafe.view.SettingItemView;

public class SettingActivity extends Activity {
	
	//...
	private SettingClickView mScvAddressStyle;//设置归属地显示风格
	private String[] items = new String[]{"半透明","活力橙","卫士蓝","金属灰","苹果绿"};//归属地显示风格样式名称数组
	private int style;//缓存的样式

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_setting);
		sPre = getSharedPreferences("config", MODE_PRIVATE);
		style = sPre.getInt("address_style", 0);
		initUpdateView();
		initAddressView();
		initAddressStyleView();
	}
	/**初始化归属地显示风格*/
	private void initAddressStyleView() {
		mScvAddressStyle = (SettingClickView) findViewById(R.id.scv_address_style);
		mScvAddressStyle.setTitle("归属地提示风格");
		
		
		mScvAddressStyle.setDesc(items[style]);
		mScvAddressStyle.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				showSingleChooseDialog();
			}
		});
	}
	/**显示归属地风格对话框*/
	protected void showSingleChooseDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setIcon(R.drawable.ic_launcher);
		builder.setTitle("归属地提示框风格");
		
		builder.setSingleChoiceItems(items, style, new DialogInterface.OnClickListener() {
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				sPre.edit().putInt("address_style", which).commit();//将选择的风格保存到SharedPreferences
				mScvAddressStyle.setDesc(items[which]);//设置描述
				dialog.dismiss();//取消对话框
			}
		});
		builder.setNegativeButton("取消", null);
		builder.show();
	}
	//...
	
}
AddressService.java
package com.xbmu.mobilesafe.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import com.xbmu.mobilesafe.R;
import com.xbmu.mobilesafe.db.dao.AddressDao;

/**
 * 来电提醒的服务
 * 
 * @author Administrator
 * 
 */
public class AddressService extends Service {

	private TelephonyManager tm;
	private MyListener listener;
	private OutCallReceiver receiver;
	private View view;
	private WindowManager mWM;
	
	private SharedPreferences mPre;

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		
		
		mPre = getSharedPreferences("config", MODE_PRIVATE);

		// 监听来电
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
		listener = new MyListener();
		tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);// 监听来电的状态

		receiver = new OutCallReceiver();
		IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
		registerReceiver(receiver, filter);// 动态注册广播
	}

	class MyListener extends PhoneStateListener {
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			super.onCallStateChanged(state, incomingNumber);
			switch (state) {
			case TelephonyManager.CALL_STATE_RINGING:
				System.out.println("电话响铃了...");
				String address = AddressDao.getAddress(incomingNumber);// 根据来电号码查询归属地
				showToast(address);
				break;
			case TelephonyManager.CALL_STATE_IDLE:// 电话闲置状态
				if (mWM != null && view != null) {
					mWM.removeView(view);// 从Window中移除view
					view = null;
				}
				break;
			default:
				break;
			}
		}
	}

	/**
	 * 监听去电的广播接收者 需要权限:android.permission.PROCESS_OUTGOING_CALLS
	 * 
	 * @author Administrator
	 * 
	 */
	class OutCallReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			String number = getResultData();// 获取去电电话号码
			String address = AddressDao.getAddress(number);
			// Toast.makeText(context, address, Toast.LENGTH_LONG).show();
			showToast(address);

		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		tm.listen(listener, PhoneStateListener.LISTEN_NONE);// 停止来电监听

		unregisterReceiver(receiver);// 注销广播
	}

	/** 自定义归属地浮窗 */
	private void showToast(String text) {
		mWM = (WindowManager) this
				.getSystemService(Context.WINDOW_SERVICE);
		WindowManager.LayoutParams params = new WindowManager.LayoutParams();
		params.width = WindowManager.LayoutParams.WRAP_CONTENT;
		params.height = 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;
		params.setTitle("Toast");
		
//		view = new TextView(this);
		view = View.inflate(this, R.layout.toast_address, null);
		
		
		int[] bgs = new int[]{R.drawable.call_locate_white,
				R.drawable.call_locate_orange,R.drawable.call_locate_blue,
				R.drawable.call_locate_gray,R.drawable.call_locate_green};
		int style = mPre.getInt("address_style", 0);
		view.setBackgroundResource(bgs[style]);//根据存储的样式更新背景
		
		
		TextView tvText = (TextView) view.findViewById(R.id.tv_number);
		tvText.setText(text);
		mWM.addView(view, params);//将view添加在屏幕上(Window)
	}
}
运行效果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上善若水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值