Compare commits

...

10 Commits

10 changed files with 324 additions and 14 deletions

View File

@ -67,6 +67,14 @@
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<service android:name=".service.MonitorService" />
<service android:name=".service.MonitorService"/>
<service android:name=".service.NotificationListener"
android:label="NotificationListener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -11,8 +11,10 @@ import androidx.appcompat.app.AppCompatActivity;
import com.nbee.echolink.async.BatteryOptimizationTask;
import com.nbee.echolink.service.MonitorService;
import com.nbee.echolink.service.NotificationListener;
import com.nbee.echolink.utils.BatteryOptimizationUtil;
import com.nbee.echolink.utils.PermissionUtils;
import com.nbee.echolink.utils.SharedPreferencesManager;
import com.nbee.echolink.utils.ShellUtils;
import java.io.BufferedReader;
@ -25,6 +27,7 @@ import timber.log.Timber;
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
private PermissionUtils permissionUtils;
private SharedPreferencesManager spManager;
private TextView logTextView; // 将类型从 View 更改为 TextView
@Override
@ -35,14 +38,31 @@ public class MainActivity extends AppCompatActivity {
logTextView = findViewById(R.id.logTextView);
updateLog("初始化日志...");
spManager = new SharedPreferencesManager(this);
if (isFirstRun()) {
setupInitialDataAsync();
spManager.putBoolean("isFirstRun", false);
}
requestIgnoreBatteryAsync();
permissionUtils = new PermissionUtils(this);
permissionUtils.checkPermissions();
if (permissionUtils.isNotificationServiceEnabled(this)) {
// 拥有权限,可以执行相关操作
} else {
// 未获得权限,可能需要提示用户
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivity(intent);
}
// 启动MonitorService服务
Intent serviceIntent = new Intent(this, MonitorService.class);
startService(serviceIntent);
//启动通知监听服务
Intent serviceIntent1 = new Intent(this, NotificationListener.class);
startService(serviceIntent1);
}
@Override
@ -57,6 +77,20 @@ public class MainActivity extends AppCompatActivity {
readLogFileAsync();
}
private boolean isFirstRun() {
// 默认值为 true如果找不到该键值对
return spManager.getBoolean("isFirstRun", true);
}
//设置通知监控的包名
private void setupInitialDataAsync() {
new Thread(() -> {
// 在这里设置SharedPreferences中的包名和对应的应用名
// 例如:
spManager.putString("com.tencent.mm", "微信");
Timber.d("写入SharedPreferences成功。");
// 完成后您可以在这里执行其他操作如发送广播、更新UI等确保在主线程中执行UI操作
}).start();
}
private void readLogFileAsync() {
new AsyncTask<Void, Void, String>() {

View File

@ -0,0 +1,83 @@
package com.nbee.echolink.model;
public class WeChatMsg {
private String packageName;
private String appName;
private String title;
private String sender;
private String message;
public String getCurrentTime() {
return currentTime;
}
public void setCurrentTime(String currentTime) {
this.currentTime = currentTime;
}
private String currentTime;
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public WeChatMsg() {
}
public WeChatMsg(String packageName, String appName, String title, String sender, String message,String currentTime) {
this.packageName = packageName;
this.appName = appName;
this.title = title;
this.sender = sender;
this.message = message;
this.currentTime = currentTime;
}
@Override
public String toString() {
return "WeChatMsg{" +
"packageName='" + packageName + '\'' +
", appName='" + appName + '\'' +
", title='" + title + '\'' +
", sender='" + sender + '\'' +
", message='" + message + '\'' +
", currentTime='" + currentTime + '\'' +
'}';
}
}

View File

@ -22,6 +22,7 @@ import com.nbee.echolink.broadcast.HeartbeatAlarmReceiver;
import com.nbee.echolink.model.CallInfo;
import com.nbee.echolink.model.DeviceInfo;
import com.nbee.echolink.model.SMSInfo;
import com.nbee.echolink.model.WeChatMsg;
import com.nbee.echolink.response.ApiResponse;
import com.nbee.echolink.utils.DeviceInfoUtils;
import com.nbee.echolink.utils.NetworkUtil;
@ -104,6 +105,10 @@ public class MonitorService extends Service {
networkUtil.postRequest(smsInfo);
}
public void sendWeChatMsg(WeChatMsg weChatMsg){
networkUtil.postRequest(weChatMsg);
}
private boolean checkNullString(String a, String b){
if (a == null || b == null){
return true;
@ -126,6 +131,7 @@ public class MonitorService extends Service {
// 创建通知
Notification notification = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle(getString(R.string.notification_title))
//.setContentText("sdads")
.setSmallIcon(R.drawable.ic_notification) // 确保您有这个图标
.build();

View File

@ -0,0 +1,74 @@
package com.nbee.echolink.service;
import android.app.Notification;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.nbee.echolink.model.WeChatMsg;
import com.nbee.echolink.utils.HandleNoticeUtils;
import com.nbee.echolink.utils.NetworkUtil;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import timber.log.Timber;
public class NotificationListener extends NotificationListenerService {
private static final String TAG = "NotificationListener";
private static final List<String> notAllowList = Arrays.asList("com.github.kr328.clash","com.google.android.dialer",
"com.google.android.apps.messaging");
private HandleNoticeUtils handleNoticeUtils;
private NetworkUtil networkUtil;
@Override
public void onCreate() {
super.onCreate();
handleNoticeUtils = new HandleNoticeUtils(this);
networkUtil = new NetworkUtil(this);
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
String packageName = sbn.getPackageName();
if (notAllowList.contains(packageName)) {
return;
}
Bundle extras = sbn.getNotification().extras;
String title = extras.getString(Notification.EXTRA_TITLE);
String content = extras.getString(Notification.EXTRA_TEXT);
String tickerText = sbn.getNotification().tickerText != null ? sbn.getNotification().tickerText.toString() : "";
Timber.d("packageName: %s,title: %s,content: %s,tickerText: %s",packageName,title,content,tickerText);
String appName = handleNoticeUtils.messageHandle(packageName);
if ("微信".equals(appName) && tickerText.contains(":")) {
String[] parts = tickerText.split(":", 2);
WeChatMsg weChatMsg = new WeChatMsg();
weChatMsg.setPackageName(packageName);
weChatMsg.setSender(parts[0]);
weChatMsg.setMessage(parts.length > 1 ? parts[1] : "");
weChatMsg.setTitle(title);
weChatMsg.setAppName(appName);
weChatMsg.setCurrentTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()));
Timber.d("准备发送微信消息: %s", weChatMsg);
// 异步执行网络请求
networkUtil.postRequest(weChatMsg);
} else {
Timber.d("非微信通知或TickerText格式不正确已忽略");
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
// 当通知被移除时调用
Log.d(TAG, "通知被移除: " + sbn.getPackageName());
}
}

View File

@ -0,0 +1,27 @@
package com.nbee.echolink.utils;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.ArrayList;
import java.util.List;
public class HandleNoticeUtils {
private SharedPreferencesManager spManager;
private Context context;
public HandleNoticeUtils(Context context) {
this.context = context;
}
public String messageHandle(String packageName){
String appName = null;
spManager = new SharedPreferencesManager(context);
String value = spManager.getString(packageName,"defaultValue");
if (value.equals("defaultValue")){
return appName;
}
appName = value;
return appName;
}
}

View File

@ -7,6 +7,7 @@ import com.google.gson.Gson;
import com.nbee.echolink.R;
import com.nbee.echolink.model.CallInfo;
import com.nbee.echolink.model.SMSInfo;
import com.nbee.echolink.model.WeChatMsg;
import com.nbee.echolink.response.ApiResponse;
@ -22,6 +23,7 @@ public class NetworkUtil {
private final String TAG = "NetworkUtil";
private String callApiUrl;
private String smsApiUrl;
private String wechatApiUrl;
private String accessToken;
private Handler handler = new Handler();
private static final int MAX_RETRIES = 3;
@ -33,6 +35,7 @@ public class NetworkUtil {
public NetworkUtil(Context context) {
callApiUrl = context.getResources().getString(R.string.call_api_url);
smsApiUrl = context.getResources().getString(R.string.message_api_url);
wechatApiUrl= context.getResources().getString(R.string.send_wechat_msg_api_url);
accessToken = context.getResources().getString(R.string.access_token);
}
@ -42,6 +45,8 @@ public class NetworkUtil {
postRequestWithRetry(dataObject, 0, callApiUrl);
} else if (dataObject instanceof SMSInfo) {
postRequestWithRetry(dataObject, 0, smsApiUrl);
} else if (dataObject instanceof WeChatMsg){
postRequestWithRetry(dataObject,0,wechatApiUrl);
}
}
@ -73,6 +78,7 @@ public class NetworkUtil {
// ... 处理响应 ...
Gson gson1 = new Gson();
ApiResponse apiResponse = gson1.fromJson(response.body().string(), ApiResponse.class);
Timber.d("ApiResponse" + apiResponse);
if (apiResponse.getCode().equals(0)){
Timber.d("Received response from " + apiUrl + ": " + apiResponse.getCode() + " - " + apiResponse.getMsg());
}

View File

@ -3,8 +3,10 @@ package com.nbee.echolink.utils;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
@ -17,7 +19,7 @@ import timber.log.Timber;
public class PermissionUtils {
private static final int PERMISSION_REQUEST_CODE = 1;
private Activity activity;
private final Activity activity;
public PermissionUtils(Activity activity) {
this.activity = activity;
@ -30,11 +32,10 @@ public class PermissionUtils {
Manifest.permission.READ_CALL_LOG,
Manifest.permission.READ_CONTACTS,
Manifest.permission.RECEIVE_SMS
};
if (!hasPermissions(permissions)) {
Timber.d("请求以下权限:\n" + Arrays.toString(permissions));
Timber.d("请求以下权限: %s", Arrays.toString(permissions));
ActivityCompat.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE);
}
}
@ -42,7 +43,7 @@ public class PermissionUtils {
private boolean hasPermissions(String... permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
Timber.d("缺少权限:\n" + permission);
Timber.d("缺少权限: %s", permission);
return false;
}
}
@ -62,8 +63,8 @@ public class PermissionUtils {
if (deniedPermissions.length() > 0) {
// 显示对话框
Timber.d("权限被拒绝:" + deniedPermissions.toString());
showAlert("以下权限被拒绝:\n" + deniedPermissions.toString());
Timber.d("权限被拒绝:%s", deniedPermissions);
showAlert("以下权限被拒绝:\n" + deniedPermissions);
}
}
}
@ -71,15 +72,27 @@ public class PermissionUtils {
private void showAlert(String message) {
new AlertDialog.Builder(activity)
.setMessage(message)
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setPositiveButton("确认", (dialog, which) -> dialog.dismiss())
.create()
.show();
}
private void showToast(String message) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
}
public boolean isNotificationServiceEnabled(Context context) {
String packageName = context.getPackageName();
String flat = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
if (flat != null && !flat.isEmpty()) {
final String[] activeListeners = flat.split(":");
for (String activeListener : activeListeners) {
ComponentName cn = ComponentName.unflattenFromString(activeListener);
if (cn != null && cn.getPackageName().equals(packageName)) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,58 @@
package com.nbee.echolink.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferencesManager {
private static final String PREFS_NAME = "MyAppPrefs";
private SharedPreferences sharedPreferences;
public SharedPreferencesManager(Context context) {
this.sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
public void putString(String key, String value) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
public String getString(String key,String defaultValue) {
return sharedPreferences.getString(key,defaultValue);
}
public void putBoolean(String key, boolean value) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(key, value);
editor.apply();
}
public boolean getBoolean(String key, boolean defaultValue) {
return sharedPreferences.getBoolean(key, defaultValue);
}
public void putInt(String key, int value) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(key, value);
editor.apply();
}
public int getInt(String key, int defaultValue) {
return sharedPreferences.getInt(key, defaultValue);
}
// 添加更多的方法来处理其他类型,如 putLong, getLong, putFloat, getFloat 等。
public void remove(String key) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(key);
editor.apply();
}
public void clearAll() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.apply();
}
}

View File

@ -3,6 +3,7 @@
<string name="call_api_url">https://api.kimgo.cn/api/call</string>
<string name="message_api_url">https://api.kimgo.cn/api/sms</string>
<string name="heart_beat_url">https://api.kimgo.cn/heartbeat</string>
<string name="send_wechat_msg_api_url">https://api.kimgo.cn/api/wechat</string>
<string name="SN">XCCS3IK75OCM</string>
<string name="access_token">gKGCDSgWV82XbU0H</string>
<string name="notification_title">监控服务运行中</string>