正文:
试想:你会不会有这样一个时候,因为某些原因,你的朋友喜欢将手机设置为震动、甚至是静音,而且全天都是这样。然后当你有急事找你的朋友需要打电话时,发现他(她)死活不接听,其实不是他(她)不想接,很多时候是因为他没有听到而已。我就遇到过这样的问题,那天我站在寒冷的女生宿舍楼下给女友打电话,结果女友睡过头了,愣是打了半个小时电话没人接,因为她喜欢手机静音,这样的场景发生过多次,本人很是受伤,于是,我开始寻找方法能够快速准确的叫醒女朋友,最终这款小应用产生了,取名为《LoveRing》。
为了简单有效,我首先想到了使用短信来控制对方手机(安卓),给对方手机上装上定时炸弹(LoveRing),当对方手机接收到对应的代号后,对方的手机将会想起音乐,并弹出对话框,直至对方醒来点击按钮方可终止音乐。总体设计思想就是这样。
为了监控短信消息,我首先想到了监听短信广播(优先级问题,可能会无效,后边使用其他方法),经过简单设计后,我开始风风火火的编码:
首先想到了最简单的方法:静态广播,然而静态广播似乎不太好用,我发现静态广播的优先级虽然也可以设置,但是经过测试发现,静态广播接收器的优先级低于动态广播接收器的优先级。下边把静态广播接收的方法写下来(我尝试过,大家也可以尝试下):
首先添加接收,发送短信等权限:
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
注册广播接收器,接收短信广播:
<receiver
android:name="yxxrui.com.wakeup.MyBroadcastReceiver"
android:enabled="true">
<intent-filter android:priority="2147483647">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
然后开始编写广播接收器类(为了方便,我在代码中写注释来帮助理解):
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String strRes = "android.provider.Telephony.SMS_RECEIVED";
private static MediaPlayer music = null;//用来播放声音
private static int status = 1;//当前状态,是否已经处理过,由于后边将动态广播与静态广播放到了一起,故可能接收到两次广播,此处做处理
private static AudioManager am = null;
private static int musicVolume = 0;
private int maxVolum = 50;
@Override
public void onReceive(Context context, Intent intent) {
if(strRes.equals(intent.getAction())){
//收到短信
Bundle bundle = intent.getExtras();
if(bundle!=null){
Object[] pdus = (Object[])bundle.get("pdus");
SmsMessage[] msg = new SmsMessage[pdus.length];
for(int i = 0 ;i<pdus.length;i++){
msg[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
}
for(SmsMessage curMsg:msg){
String address = curMsg.getDisplayOriginatingAddress();
long time = curMsg.getTimestampMillis();
String content = curMsg.getDisplayMessageBody();
dealMsg(context, address, time, content);//处理短信
}
}
}else if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){
//开机完毕
Intent smsIntent=new Intent(context, SmsBindService.class);
context.startService(smsIntent);
Intent socketIntent=new Intent(context, SocketPortBindService.class);
context.startService(socketIntent);
}
}
/**
* 为了安全,防止坏人任意唤起音乐,此处简单设计了密码,大家可以对此处做优化,加密处理
*/
private String pswStr = null;//密码
private String hintStr = null;//弹框中显示的内容
private String codeStr = null;//代号,如:#GetUp#
private String numberStr = null;//发短信手机号码
private String feedbackStr = null;//自动回复的短信内容,方便接收到信息后快速回复信息
private int volume = 70;//音乐声音大小
/**
* 处理短信,验证是否为命令
* @param context 上下文
* @param address 手机号码
* @param time 发送时间
* @param content 发送的内容
*/
public void dealMsg(Context context, String address, long time, String content){
try{
final String msgContent = content;
//基本的参数是允许用户自己修改的,保存到SharedPreferences中
SharedPreferences prefer = context.getSharedPreferences("yxxrui",
context.MODE_PRIVATE);
pswStr = prefer.getString("psw",
g(context,R.string.psw_content_default));
codeStr = prefer.getString("code",
g(context,R.string.code_content_default));
feedbackStr = prefer.getString("feedback",
g(context,R.string.feedback_content_default));
hintStr = prefer.getString("hint",
g(context,R.string.hint_content_default));
double percent = prefer.getInt("volume", 70);
if(am==null){
am = (AudioManager)context.getSystemService(context.AUDIO_SERVICE);
maxVolum = am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM);
}
//此处需要注意:一开始我认为最大音量就是100,然后可以随意设置大小,结果翻阅文档和测试发现音量大小各手机不同
//其实相当于你点几次音量+键,就共有多少个档位,我的手机为7,故此处需要计算百分比
volume = (int) Math.floor(maxVolum * (percent/100));
//此处暗藏一个命令,将对方手机开启标准模式,方便你打电话联系,哈哈哈(应该不算犯规吧!)
if(content.equals("#RingCall#456852")){
am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
am.setStreamVolume(AudioManager.STREAM_RING, 50, 0);
deleteSMS(context, msgContent);
}else if(content.startsWith(codeStr)){
//若短信的代号与本机所设置的代号相同的话,初步判定是命令,那么继续操作
content = content.replace(codeStr, "");
//切分出密码,使用的是两个英文逗号分割
String[] result = content.split(",,");
//SimpleDateFormat formatter = new SimpleDateFormat("MMddHHmm");
//Date curDate = new Date(time);
//int qustion = Math.abs(12240214 - Integer.parseInt(formatter.format(curDate)));
String answer = "";
if(result.length>=1){
answer = result[0];
}
if(answer.equals(pswStr)/*Math.abs(qustion - answer) < 5*/){
//验证通过后,可以进行真正工作了
if(status == 2){//播放过了已经
return;
}
try{
this.abortBroadcast();
}catch(Exception ex){
}
numberStr = address;
//此处将收到的命令删除(放心,绝对不删除其他短信)
deleteSMS(context, msgContent);
//播放音乐
PlayMusic(context);
status = 2;
//弹出对话框,显示温馨提示,温馨提示可以有发送者定义,默认为本机设置的提示语
String str = result.length >= 2 ? result[1] : hintStr;
showDialog(context,str);
}
}
}catch(Exception e){
Toast.makeText(context,e.toString(),Toast.LENGTH_LONG).show();
}
}
protected String g(Context context, int id){
return context.getResources().getString(id);
}
private void showDialog(Context context,String content){
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(content)
.setTitle(g(context,R.string.hint_title))
.setCancelable(false)
.setPositiveButton(g(context,R.string.hint_btn_not_reply), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if(music!=null){
music.pause();
music.stop();
}
recover();
dialog.cancel();
}
})
.setNegativeButton(g(context,R.string.hint_btn_reply), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//暂时这么处理,之后发送短信回复已起床
if(music!=null){
music.pause();
music.stop();
}
recover();
sendMessage(numberStr, feedbackStr);
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alert.show();
}
private void sendMessage(String number, String msg){
SmsManager smsManager = SmsManager.getDefault();
if(msg.length() > 70) {
List<String> contents = smsManager.divideMessage(msg);
for(String sms : contents) {
smsManager.sendTextMessage(number, null, sms, null, null);
}
} else {
smsManager.sendTextMessage(number, null, msg, null, null);
}
}
private Vibrator vibrator = null;
protected void PlayMusic(final Context context) {
vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
final long [] pattern = {2000,500,150,500}; // 停止 开启 停止 开启
/*vibrator.vibrate(pattern,2); //重复两次上面的pattern 如果只想震动一次,index设为-1
*/ Thread th = new Thread(new Runnable(){
@Override
public void run() {
int MusicId = R.raw.wakeup1;
//保存当前音量,播放完后恢复回去
//systemVolume = am.getStreamVolume(AudioManager.STREAM_SYSTEM);
musicVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
//am.setStreamVolume(AudioManager.STREAM_SYSTEM, volume, AudioManager.FLAG_PLAY_SOUND);
am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, AudioManager.FLAG_PLAY_SOUND);
if(music!=null&&music.isPlaying()){
return;
}
music = MediaPlayer.create(context, MusicId);
music.setLooping(true);
music.setOnCompletionListener(new OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
recover();
}
});
vibrator.vibrate(pattern,0);
music.start();
}
});
th.start();
/*Toast.makeText(context, systemVolume+"", Toast.LENGTH_LONG).show();
Toast.makeText(context, musicVolume+"", Toast.LENGTH_LONG).show();*/
}
private void recover(){
am.setStreamVolume(AudioManager.STREAM_MUSIC, musicVolume, 0);
//am.setStreamVolume(AudioManager.STREAM_SYSTEM, systemVolume, 0);
status = 1;//恢复原状
vibrator.cancel();
}
public void deleteSMS(Context context, String smscontent)
{
/*所有文件夹:content://sms/all
收件箱:content://sms/inbox
已发送:content://sms/sent
草稿:content://sms/draft
发件箱:content://sms/outbox
发送失败:content://sms/failed
排队消息:content://sms/queued
未送达:content://sms/undelivered
对话:content://sms/conversations*/
try{
// 准备系统短信收信箱的uri地址
Uri uri = Uri.parse("content://sms");// 全部,只要符合条件,全部删除
// 查询收信箱里所有的短信
ContentResolver cR = context.getContentResolver();
Cursor isRead = cR.query(uri, null, "read<=" + 1,null, null);
while (isRead.moveToNext()){
// String phone =
// isRead.getString(isRead.getColumnIndex("address")).trim();//获取发信人
String body =
isRead.getString(isRead.getColumnIndex("body")).trim();// 获取信息内容
if (body.equals(smscontent)){
int id = isRead.getInt(isRead.getColumnIndex("_id"));
cR.delete(Uri.parse("content://sms"), "_id=" + id, null);
}
}
}catch (Exception e){
Toast.makeText(context, "delete Failed! "+e.toString(), Toast.LENGTH_SHORT);
}
}
}
优先级不高,经常不响,于是改成了动态广播,为了更加有效,需要设置开机监听,开机启动service,然后在service中监听广播,收到广播后依旧发送至上边写的广播接收器中,方便:
添加开机启动广播权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
修改上边的广播注册器:在filter中添加一条开机事件:
<receiver
android:name="yxxrui.com.wakeup.MyBroadcastReceiver"
android:enabled="true">
<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
其实上边的广播接收类中已经写好了开机事件,只是上边的事件中需要启动一个service,而service还没有写上去:这里贴上:
首先,注册服务:
<service android:name="yxxrui.com.wakeup.SmsBindService"
android:priority="1000">
</service>
然后,简单粗暴,贴service代码:
public class SmsBindService extends Service{
private MyBroadcastReceiver receiver;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate(){
super.onCreate();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(2147483647);
receiver =new MyBroadcastReceiver();
registerReceiver(receiver, filter);
}
@Override
public int onStartCommand(Intent intent,int flags, int startId){
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy(){
super.onDestroy();
//Toast.makeText(this, "WakeUp service has been stoped", Toast.LENGTH_SHORT).show();
Intent localIntent = new Intent();
localIntent.setClass(this, SmsBindService.class); //销毁时重新启动Service
this.startService(localIntent);
unregisterReceiver(receiver);
}
}
当然,主界面 很水,代码就不再粘贴,这里只是对一些参数进行设置而已,没有什么技术含量:
到此,动态,静态均已完成,但是这样写好以后真的可以实现所有短信都能检测到吗?不,不可能,因为有一些大佬的短信广播永远比你厉害,这样的应用会经常失效,如何才能解决这个问题呢?其实就是题目中说的内容提供者,我这里直接叠加到上边的代码中,也就是一个应用使用三种检测短信的方式:静态广播,动态广播,内容提供者。
现在在SmsBindService 类中添加一个内部类,用于检测内容提供者的数据库改变事件,当数据库的内容发生改变时,系统会自动回调这个onChange() 方法,让后再onChange()方法中获取未读信息,并验证是否为命令,再做处理,我在这里的处理方法仍然是使用上边写好的短信处理方法
class MyContentObserver extends ContentObserver{
public MyContentObserver(Handler handler) {
super(handler);
}
//当被监听的内容发生改变时回调
@Override
public void onChange(boolean selfChange) {
if(selfChange){
return;
}
Uri uri = Uri.parse("content://sms/inbox"); //收件箱uri
//查看发件箱内容
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri,
new String[]{"address","date","body"},
"read <=" + 1, null, "date desc");
if(cursor!=null && cursor.getCount()>0){
long now = System.currentTimeMillis();
String address;
long time;
String content;
//只看前5条,2分钟内的短信
int n = 0;
while(n<5 && cursor.moveToNext()){
address = cursor.getString(0);
time = cursor.getInt(1);
content = cursor.getString(2);
if(Math.abs(now-time) > 5*60*1000){
receiver.dealMsg(SmsBindService.this, address, time, content);
}
n++;
}
cursor.close();
}
}
}
最后修改上边SmsBindService 的代码,在onCreate()方法末尾添加以下代码即可:
ContentResolver resolver = getContentResolver();
//注册一个内容观察者观察短信数据库
resolver.registerContentObserver(
Uri.parse("content://sms/"), true, new MyContentObserver(new Handler()));
有问题可以联系我,我的邮箱是:yxxrui@163.com,我的网址是:http://www.yxxrui.cn
原创文章如转载,请注明出处“
伊人博客”