first blood
This commit is contained in:
40
rnwalletman/android/build.gradle
Normal file
40
rnwalletman/android/build.gradle
Normal file
@@ -0,0 +1,40 @@
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "33.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.4.2")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
}
|
||||
30
rnwalletman/android/src/main/AndroidManifest.xml
Normal file
30
rnwalletman/android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.rnwalletman">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />
|
||||
|
||||
<application>
|
||||
<!-- SMS 广播接收器 -->
|
||||
<receiver
|
||||
android:name=".SmsReceiver"
|
||||
android:exported="true"
|
||||
android:enabled="true">
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- 通知监听服务 -->
|
||||
<service
|
||||
android:name=".RNWalletNotificationListener"
|
||||
android:label="RNWalletMan Notification Listener"
|
||||
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>
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ActivityEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import net.one97.paytm.upi.transaction.common.models.o;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class PaytmPersonalModule extends ReactContextBaseJavaModule implements ActivityEventListener {
|
||||
private static final String TAG = "PaytmPersonalModule";
|
||||
private Promise tokenPromise;
|
||||
|
||||
public PaytmPersonalModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
reactContext.addActivityEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PaytmPersonalModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getToken(Promise promise) {
|
||||
try {
|
||||
tokenPromise = promise;
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject("ERROR", "Activity is null");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("paytmgtk://getToken"));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "拉起 Paytm 失败", e);
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
tokenPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
@SuppressLint("WrongConstant")
|
||||
public void pay(String amount, String payeeName, String accountNo, String ifscCode, String comments, Promise promise) {
|
||||
try {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject("ERROR", "Activity is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(
|
||||
"net.one97.paytm",
|
||||
"net.one97.paytm.moneytransfer.eas.view.activity.MTEnterAmountActivity"
|
||||
));
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("payee_name", payeeName);
|
||||
bundle.putString("account_no", accountNo);
|
||||
bundle.putString("bankname", ifscCode.substring(0, 4));
|
||||
bundle.putString("ifsc", ifscCode);
|
||||
bundle.putString("amount", amount);
|
||||
bundle.putString("comments", comments);
|
||||
bundle.putBoolean("post_txn_scan_flow", false);
|
||||
bundle.putBoolean("post_txn_collect_flow", false);
|
||||
|
||||
intent.putExtra("post_txn_data", bundle);
|
||||
intent.putExtra("t", o.INSTANCE);
|
||||
intent.addFlags(268468224);
|
||||
|
||||
activity.startActivity(intent);
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Paytm 支付失败", e);
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (intent == null || intent.getData() == null || tokenPromise == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = intent.getData();
|
||||
if ("ipay".equals(uri.getScheme()) && "native".equals(uri.getHost())) {
|
||||
String base64Data = uri.getQueryParameter("data");
|
||||
if (base64Data != null) {
|
||||
try {
|
||||
byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
|
||||
String jsonStr = new String(decodedBytes);
|
||||
JSONObject tokenData = new JSONObject(jsonStr);
|
||||
|
||||
String mobile = tokenData.optString("mobile", "");
|
||||
String token = tokenData.optString("token", "");
|
||||
String userId = tokenData.optString("userId", "");
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("mobile", mobile);
|
||||
result.put("token", token);
|
||||
result.put("userId", userId);
|
||||
|
||||
tokenPromise.resolve(result.toString());
|
||||
tokenPromise = null;
|
||||
} catch (Exception e) {
|
||||
tokenPromise.reject("ERROR", e.getMessage());
|
||||
tokenPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ActivityEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class PhonepePersonalModule extends ReactContextBaseJavaModule implements ActivityEventListener {
|
||||
private static final String TAG = "PhonepePersonalModule";
|
||||
private Promise tokenPromise;
|
||||
|
||||
public PhonepePersonalModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
reactContext.addActivityEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PhonepePersonalModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getToken(Promise promise) {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject("ERROR", "Activity is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
tokenPromise = promise;
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("phonepegtk://getToken?callback=" + activity.getPackageName()));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
activity.startActivity(intent);
|
||||
Log.d(TAG, "已拉起 PhonePe,等待回调...");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "拉起 PhonePe 失败", e);
|
||||
tokenPromise = null;
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
if (intent == null || intent.getData() == null || tokenPromise == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = intent.getData();
|
||||
String scheme = uri.getScheme();
|
||||
String host = uri.getHost();
|
||||
|
||||
Log.d(TAG, "onNewIntent - Scheme: " + scheme + ", Host: " + host);
|
||||
|
||||
// PhonePe 也使用 ipay://native 回调
|
||||
if ("ipay".equals(scheme) && "native".equals(host)) {
|
||||
String base64Data = uri.getQueryParameter("data");
|
||||
if (base64Data != null) {
|
||||
try {
|
||||
byte[] decodedBytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT);
|
||||
String jsonStr = new String(decodedBytes);
|
||||
JSONObject tokenData = new JSONObject(jsonStr);
|
||||
|
||||
String mobile = tokenData.optString("mobile", "");
|
||||
String token = tokenData.optString("token", "");
|
||||
String userId = tokenData.optString("userId", "");
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("mobile", mobile);
|
||||
result.put("token", token);
|
||||
result.put("userId", userId);
|
||||
|
||||
tokenPromise.resolve(result.toString());
|
||||
tokenPromise = null;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "解析 Token 失败", e);
|
||||
tokenPromise.reject("ERROR", e.getMessage());
|
||||
tokenPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* 通知监听服务
|
||||
*/
|
||||
public class RNWalletNotificationListener extends NotificationListenerService {
|
||||
private static final String TAG = "RNWalletNotification";
|
||||
private static RNWalletNotificationListener instance;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
instance = this;
|
||||
Log.i(TAG, "RNWalletNotificationListener 服务已启动");
|
||||
|
||||
// 检查并打印当前通知数量
|
||||
try {
|
||||
StatusBarNotification[] notifications = getActiveNotifications();
|
||||
Log.i(TAG, "服务启动时当前有 " + notifications.length + " 条活跃通知");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "无法获取活跃通知: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
instance = null;
|
||||
Log.i(TAG, "RNWalletNotificationListener stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务实例
|
||||
*/
|
||||
public static RNWalletNotificationListener getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取当前所有活跃的通知
|
||||
*/
|
||||
public StatusBarNotification[] readActiveNotifications() {
|
||||
try {
|
||||
StatusBarNotification[] notifications = super.getActiveNotifications();
|
||||
Log.i(TAG, "读取到 " + notifications.length + " 条活跃通知");
|
||||
for (StatusBarNotification sbn : notifications) {
|
||||
Log.d(TAG, "通知: " + sbn.getPackageName() + " - " + sbn.getId());
|
||||
}
|
||||
return notifications;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "读取通知失败: " + e.getMessage(), e);
|
||||
return new StatusBarNotification[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
// 检查是否启用监听
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("rnwalletman", MODE_PRIVATE);
|
||||
boolean isEnabled = prefs.getBoolean("notification_enabled", false);
|
||||
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String packageName = sbn.getPackageName();
|
||||
String title = "";
|
||||
String text = "";
|
||||
String bigText = "";
|
||||
|
||||
if (sbn.getNotification() != null && sbn.getNotification().extras != null) {
|
||||
android.os.Bundle extras = sbn.getNotification().extras;
|
||||
title = getStringFromExtras(extras, "android.title");
|
||||
text = getStringFromExtras(extras, "android.text");
|
||||
bigText = getStringFromExtras(extras, "android.bigText");
|
||||
}
|
||||
|
||||
Log.i(TAG, "收到通知: " + packageName + " - " + title);
|
||||
|
||||
// 发送事件到 React Native
|
||||
SmsNotificationModule.sendNotificationEvent(
|
||||
String.valueOf(sbn.getId()),
|
||||
packageName,
|
||||
sbn.getTag(),
|
||||
sbn.getPostTime(),
|
||||
title,
|
||||
text,
|
||||
bigText
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "处理通知失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getStringFromExtras(android.os.Bundle extras, String key) {
|
||||
if (extras == null || key == null) {
|
||||
return "";
|
||||
}
|
||||
Object obj = extras.get(key);
|
||||
return obj != null ? obj.toString() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
// 不需要处理
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RnWalletmanPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
modules.add(new PaytmPersonalModule(reactContext));
|
||||
modules.add(new PhonepePersonalModule(reactContext));
|
||||
modules.add(new SmsNotificationModule(reactContext));
|
||||
modules.add(new TcpProxyModule(reactContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Telephony;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
/**
|
||||
* SMS 和通知监听模块
|
||||
*/
|
||||
public class SmsNotificationModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "SmsNotificationModule";
|
||||
private static ReactApplicationContext reactContext;
|
||||
|
||||
private static final String EVENT_SMS_RECEIVED = "onSmsMessage";
|
||||
private static final String EVENT_NOTIFICATION_RECEIVED = "onNotificationMessage";
|
||||
|
||||
public SmsNotificationModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
reactContext = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SmsNotificationModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void addListener(String eventName) {
|
||||
// Set up any upstream listeners or background tasks as necessary
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeListeners(Integer count) {
|
||||
// Remove upstream listeners, stop unnecessary background tasks
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信事件到 JS
|
||||
*/
|
||||
public static void sendSmsEvent(String sender, String message, long timestamp) {
|
||||
if (reactContext == null) return;
|
||||
|
||||
try {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("address", sender);
|
||||
params.putString("body", message);
|
||||
params.putDouble("timestamp", timestamp);
|
||||
|
||||
reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(EVENT_SMS_RECEIVED, params);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "发送SMS事件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知事件到 JS
|
||||
*/
|
||||
public static void sendNotificationEvent(String id, String packageName, String tag,
|
||||
long postTime, String title, String text, String bigText) {
|
||||
if (reactContext == null) return;
|
||||
|
||||
try {
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putString("id", id);
|
||||
params.putString("packageName", packageName);
|
||||
params.putString("tag", tag);
|
||||
params.putDouble("postTime", postTime);
|
||||
params.putString("title", title);
|
||||
params.putString("text", text);
|
||||
params.putString("bigText", bigText);
|
||||
|
||||
reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(EVENT_NOTIFICATION_RECEIVED, params);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "发送通知事件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 SMS 权限
|
||||
*/
|
||||
@ReactMethod
|
||||
public void checkSmsPermission(Promise promise) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
boolean hasReadSms = ContextCompat.checkSelfPermission(reactContext, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED;
|
||||
boolean hasReceiveSms = ContextCompat.checkSelfPermission(reactContext, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED;
|
||||
promise.resolve(hasReadSms && hasReceiveSms);
|
||||
} else {
|
||||
promise.resolve(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
promise.reject("CHECK_PERMISSION_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求 SMS 权限
|
||||
*/
|
||||
@ReactMethod
|
||||
public void requestSmsPermission(final Promise promise) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
promise.resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
PermissionAwareActivity activity = (PermissionAwareActivity) reactContext.getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject("NO_ACTIVITY", "Activity is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String[] permissions = new String[]{
|
||||
Manifest.permission.READ_SMS,
|
||||
Manifest.permission.RECEIVE_SMS
|
||||
};
|
||||
|
||||
activity.requestPermissions(permissions, 1, new PermissionListener() {
|
||||
@Override
|
||||
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (requestCode == 1) {
|
||||
boolean allGranted = true;
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
allGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
promise.resolve(allGranted);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
promise.reject("REQUEST_PERMISSION_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查通知监听权限
|
||||
*/
|
||||
@ReactMethod
|
||||
public void checkNotificationPermission(Promise promise) {
|
||||
try {
|
||||
String packageName = reactContext.getPackageName();
|
||||
String flat = Settings.Secure.getString(reactContext.getContentResolver(), "enabled_notification_listeners");
|
||||
|
||||
if (flat != null && flat.contains(packageName)) {
|
||||
promise.resolve(true);
|
||||
} else {
|
||||
promise.resolve(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
promise.reject("CHECK_NOTIFICATION_PERMISSION_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开通知监听设置页面
|
||||
*/
|
||||
@ReactMethod
|
||||
public void openNotificationSettings(Promise promise) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
reactContext.startActivity(intent);
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("OPEN_SETTINGS_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 SMS 监听
|
||||
*/
|
||||
@ReactMethod
|
||||
public void startSmsListener(Promise promise) {
|
||||
try {
|
||||
SharedPreferences prefs = reactContext.getSharedPreferences("rnwalletman", ReactApplicationContext.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean("sms_enabled", true).apply();
|
||||
Log.i(TAG, "SMS 监听已启用");
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("START_SMS_LISTENER_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止 SMS 监听
|
||||
*/
|
||||
@ReactMethod
|
||||
public void stopSmsListener(Promise promise) {
|
||||
try {
|
||||
SharedPreferences prefs = reactContext.getSharedPreferences("rnwalletman", ReactApplicationContext.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean("sms_enabled", false).apply();
|
||||
Log.i(TAG, "SMS 监听已停止");
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("STOP_SMS_LISTENER_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动通知监听
|
||||
*/
|
||||
@ReactMethod
|
||||
public void startNotificationListener(Promise promise) {
|
||||
try {
|
||||
SharedPreferences prefs = reactContext.getSharedPreferences("rnwalletman", ReactApplicationContext.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean("notification_enabled", true).apply();
|
||||
Log.i(TAG, "通知监听已启用");
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("START_NOTIFICATION_LISTENER_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止通知监听
|
||||
*/
|
||||
@ReactMethod
|
||||
public void stopNotificationListener(Promise promise) {
|
||||
try {
|
||||
SharedPreferences prefs = reactContext.getSharedPreferences("rnwalletman", ReactApplicationContext.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean("notification_enabled", false).apply();
|
||||
Log.i(TAG, "通知监听已停止");
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("STOP_NOTIFICATION_LISTENER_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取所有短信
|
||||
* @param limit 限制数量,0 表示全部
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAllSms(int limit, Promise promise) {
|
||||
try {
|
||||
WritableArray result = Arguments.createArray();
|
||||
ContentResolver cr = reactContext.getContentResolver();
|
||||
|
||||
Uri uri = Telephony.Sms.CONTENT_URI;
|
||||
String[] projection = new String[] {
|
||||
Telephony.Sms._ID,
|
||||
Telephony.Sms.ADDRESS,
|
||||
Telephony.Sms.BODY,
|
||||
Telephony.Sms.DATE,
|
||||
Telephony.Sms.TYPE,
|
||||
Telephony.Sms.READ
|
||||
};
|
||||
|
||||
String sortOrder = Telephony.Sms.DATE + " DESC";
|
||||
if (limit > 0) {
|
||||
sortOrder += " LIMIT " + limit;
|
||||
}
|
||||
|
||||
Cursor cursor = cr.query(uri, projection, null, null, sortOrder);
|
||||
|
||||
if (cursor != null) {
|
||||
int count = 0;
|
||||
while (cursor.moveToNext() && (limit == 0 || count < limit)) {
|
||||
WritableMap sms = Arguments.createMap();
|
||||
|
||||
sms.putString("id", cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms._ID)));
|
||||
sms.putString("address", cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.ADDRESS)));
|
||||
sms.putString("body", cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.BODY)));
|
||||
sms.putDouble("timestamp", cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Sms.DATE)));
|
||||
sms.putInt("type", cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Sms.TYPE)));
|
||||
sms.putBoolean("read", cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Sms.READ)) == 1);
|
||||
|
||||
result.pushMap(sms);
|
||||
count++;
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
Log.i(TAG, "读取短信: " + result.size() + " 条");
|
||||
promise.resolve(result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "读取短信失败", e);
|
||||
promise.reject("SMS_READ_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取所有活跃通知
|
||||
* 需要先在系统设置中授权通知监听
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getAllNotifications(Promise promise) {
|
||||
try {
|
||||
RNWalletNotificationListener service = RNWalletNotificationListener.getInstance();
|
||||
|
||||
Log.d(TAG, "获取通知监听服务实例: " + (service != null ? "成功" : "失败"));
|
||||
|
||||
if (service == null) {
|
||||
// 服务未运行,返回空数组而非错误(可能系统还未启动服务)
|
||||
Log.w(TAG, "通知监听服务未运行,返回空数组");
|
||||
promise.resolve(Arguments.createArray());
|
||||
return;
|
||||
}
|
||||
|
||||
StatusBarNotification[] notifications = service.readActiveNotifications();
|
||||
WritableArray result = Arguments.createArray();
|
||||
|
||||
Log.d(TAG, "从系统获取到 " + notifications.length + " 条通知");
|
||||
|
||||
for (StatusBarNotification sbn : notifications) {
|
||||
WritableMap notification = Arguments.createMap();
|
||||
|
||||
notification.putString("id", String.valueOf(sbn.getId()));
|
||||
notification.putString("packageName", sbn.getPackageName());
|
||||
notification.putString("tag", sbn.getTag());
|
||||
notification.putDouble("postTime", sbn.getPostTime());
|
||||
|
||||
if (sbn.getNotification() != null && sbn.getNotification().extras != null) {
|
||||
notification.putString("title", getStringFromExtras(sbn.getNotification().extras, "android.title"));
|
||||
notification.putString("text", getStringFromExtras(sbn.getNotification().extras, "android.text"));
|
||||
notification.putString("bigText", getStringFromExtras(sbn.getNotification().extras, "android.bigText"));
|
||||
}
|
||||
|
||||
result.pushMap(notification);
|
||||
}
|
||||
|
||||
Log.i(TAG, "返回通知数据: " + result.size() + " 条");
|
||||
promise.resolve(result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "读取通知失败", e);
|
||||
promise.reject("NOTIFICATION_READ_ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bundle 中安全地获取字符串
|
||||
*/
|
||||
private String getStringFromExtras(android.os.Bundle extras, String key) {
|
||||
if (extras == null || key == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Object obj = extras.get(key);
|
||||
if (obj == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* SMS 广播接收器
|
||||
*/
|
||||
public class SmsReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "RNWalletSmsReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// 检查是否启用监听
|
||||
SharedPreferences prefs = context.getSharedPreferences("rnwalletman", Context.MODE_PRIVATE);
|
||||
boolean isEnabled = prefs.getBoolean("sms_enabled", false);
|
||||
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("android.provider.Telephony.SMS_RECEIVED".equals(intent.getAction())) {
|
||||
Bundle bundle = intent.getExtras();
|
||||
if (bundle != null) {
|
||||
Object[] pdus = (Object[]) bundle.get("pdus");
|
||||
if (pdus != null) {
|
||||
for (Object pdu : pdus) {
|
||||
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu);
|
||||
String sender = sms.getDisplayOriginatingAddress();
|
||||
String message = sms.getMessageBody();
|
||||
long timestamp = sms.getTimestampMillis();
|
||||
|
||||
Log.i(TAG, "收到短信: " + sender + " - " + message);
|
||||
|
||||
// 发送事件到 React Native
|
||||
SmsNotificationModule.sendSmsEvent(sender, message, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class TcpOverWebSocketClient {
|
||||
private static final String TAG = "TcpProxy";
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private final String wsUrl;
|
||||
private final String hostname;
|
||||
private final int port;
|
||||
private final String messageId;
|
||||
private final ExecutorService ioPool = Executors.newCachedThreadPool();
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
private final OkHttpClient httpClient = new OkHttpClient.Builder().build();
|
||||
|
||||
private WebSocket ws;
|
||||
private Socket tcpSocket;
|
||||
|
||||
public TcpOverWebSocketClient(String wsUrl, String hostname, int port, String messageId) {
|
||||
this.wsUrl = wsUrl;
|
||||
this.hostname = hostname;
|
||||
this.port = port > 0 ? port : 443;
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Log.d(TAG, "连接WebSocket: " + wsUrl);
|
||||
Request request = new Request.Builder().url(wsUrl).build();
|
||||
ws = httpClient.newWebSocket(request, new ProxyWebSocketListener());
|
||||
}
|
||||
|
||||
private void safeClose(String reason) {
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
Log.d(TAG, "关闭连接: " + reason);
|
||||
|
||||
if (ws != null) {
|
||||
try {
|
||||
ws.close(1000, reason);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "关闭WebSocket失败", e);
|
||||
}
|
||||
ws = null;
|
||||
}
|
||||
|
||||
if (tcpSocket != null) {
|
||||
try {
|
||||
tcpSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "关闭TCP socket失败", e);
|
||||
}
|
||||
tcpSocket = null;
|
||||
}
|
||||
|
||||
closed.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void forwardTcpToWs(Socket socket, WebSocket webSocket) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
try {
|
||||
InputStream input = socket.getInputStream();
|
||||
while (true) {
|
||||
int len = input.read(buffer);
|
||||
if (len == -1) {
|
||||
Log.d(TAG, "TCP连接关闭");
|
||||
webSocket.close(1000, "tcp_end");
|
||||
safeClose("tcp_end");
|
||||
break;
|
||||
}
|
||||
|
||||
if (len > 0) {
|
||||
boolean sent = webSocket.send(ByteString.of(buffer, 0, len));
|
||||
if (!sent) {
|
||||
Log.w(TAG, "WebSocket发送失败");
|
||||
safeClose("ws_send_failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "TCP读取异常", e);
|
||||
safeClose("tcp_read_error");
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyWebSocketListener extends WebSocketListener {
|
||||
@Override
|
||||
public void onOpen(final WebSocket webSocket, Response response) {
|
||||
Log.d(TAG, "WebSocket已连接");
|
||||
|
||||
ioPool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// 连接目标TCP服务器
|
||||
tcpSocket = new Socket(hostname, port);
|
||||
Log.d(TAG, "TCP已连接: " + hostname + ":" + port);
|
||||
|
||||
// 发送open事件
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("event", "open");
|
||||
event.put("messageId", messageId);
|
||||
webSocket.send(event.toString());
|
||||
|
||||
// 启动TCP->WebSocket转发
|
||||
forwardTcpToWs(tcpSocket, webSocket);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "TCP连接失败", e);
|
||||
safeClose("tcp_connect_failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
// WebSocket -> TCP
|
||||
try {
|
||||
if (tcpSocket != null && !tcpSocket.isClosed()) {
|
||||
OutputStream output = tcpSocket.getOutputStream();
|
||||
output.write(bytes.toByteArray());
|
||||
output.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "写入TCP失败", e);
|
||||
safeClose("tcp_write_error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(WebSocket webSocket, int code, String reason) {
|
||||
Log.d(TAG, "WebSocket正在关闭");
|
||||
webSocket.close(1000, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
Log.d(TAG, "WebSocket已关闭: " + code + " " + reason);
|
||||
safeClose("ws_closed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
Log.e(TAG, "WebSocket连接失败", t);
|
||||
safeClose("ws_failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.rnwalletman;
|
||||
|
||||
import android.util.Log;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class TcpProxyModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "TcpProxyModule";
|
||||
|
||||
public TcpProxyModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TcpProxyModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void startProxy(String wsUrl, String host, int port, String messageId, Promise promise) {
|
||||
try {
|
||||
Log.d(TAG, "启动代理: wsUrl=" + wsUrl + " host=" + host + " port=" + port);
|
||||
|
||||
final TcpOverWebSocketClient client = new TcpOverWebSocketClient(wsUrl, host, port, messageId);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.start();
|
||||
}
|
||||
}).start();
|
||||
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "启动代理失败", e);
|
||||
promise.reject("START_PROXY_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.one97.paytm.upi.transaction.common.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/* loaded from: classes.dex */
|
||||
public abstract class a0 implements Serializable {
|
||||
private static final long serialVersionUID = 3744706392290925551L;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.one97.paytm.upi.transaction.common.models;
|
||||
|
||||
/* loaded from: classes.dex */
|
||||
public class o extends t {
|
||||
public static o INSTANCE = new o();
|
||||
private static final long serialVersionUID = 2038457052099164746L;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.one97.paytm.upi.transaction.common.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/* loaded from: classes.dex */
|
||||
public abstract class t implements Serializable {
|
||||
private static final long serialVersionUID = 1973796336737578214L;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.one97.paytm.upi.transaction.common.models;
|
||||
|
||||
/* loaded from: classes.dex */
|
||||
public class v extends a0 {
|
||||
public static v INSTANCE = new v();
|
||||
private static final long serialVersionUID = -5916830127381874761L;
|
||||
}
|
||||
Reference in New Issue
Block a user