feat(ch934x_serial): 添加CH934X串口设备权限管理和端口切换功能
- 新增 setActivePort 方法用于在同一USB设备内部切换活跃串口 - 新增 requestUsbPermission 方法用于主动申请指定设备的USB权限 - 实现Android侧USB权限申请广播接收器和同步等待机制 - 在示例应用中集成权限预申请和端口切换UI逻辑 - 优化设备打开后自动拉取真实串口列表的功能 - 更新端口选择下拉框在打开状态下支持实时切换串口
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
package com.xiarui.ch934x_serial;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -12,6 +18,8 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cn.wch.ch934xlib.CH934XManager;
|
||||
import cn.wch.ch934xlib.callback.IDataCallback;
|
||||
@@ -25,6 +33,8 @@ import cn.wch.ch934xlib.exception.UartLibException;
|
||||
import cn.wch.ch934xlib.gpio.GPIO_DIR;
|
||||
import cn.wch.ch934xlib.gpio.GPIO_VALUE;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
@@ -39,14 +49,24 @@ import io.flutter.plugin.common.MethodChannel.Result;
|
||||
* {@code UsbSerial} 是 SDK 的概念性命名,真实 API 在
|
||||
* {@code cn.wch.ch934xlib.CH934XManager}。
|
||||
*/
|
||||
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
|
||||
|
||||
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
|
||||
private static final String CHANNEL_NAME = "ch934x_serial";
|
||||
|
||||
/** USB 权限申请广播的 Action,需在 AndroidManifest 中注册对应 Receiver。 */
|
||||
private static final String ACTION_USB_PERMISSION = "com.xiarui.ch934x_serial.USB_PERMISSION";
|
||||
|
||||
private MethodChannel channel;
|
||||
private Context applicationContext;
|
||||
|
||||
@Nullable
|
||||
private Activity activity;
|
||||
@Nullable
|
||||
private BroadcastReceiver permissionReceiver;
|
||||
@Nullable
|
||||
private CompletableFuture<Boolean> pendingPermissionRequest;
|
||||
|
||||
/** 当前 Dart 侧选中的 (deviceId, serialNumber) 对应的 UsbDevice 引用。 */
|
||||
@Nullable
|
||||
private UsbDevice activeDevice;
|
||||
@@ -138,6 +158,30 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
exceptionCallbackEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
registerPermissionReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivityForConfigChanges() {
|
||||
unregisterPermissionReceiver();
|
||||
activity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
registerPermissionReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {
|
||||
unregisterPermissionReceiver();
|
||||
activity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
@@ -157,9 +201,15 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
case "openPort":
|
||||
result.success(handleOpenPort(call));
|
||||
break;
|
||||
case "requestUsbPermission":
|
||||
result.success(handleRequestUsbPermission(call));
|
||||
break;
|
||||
case "closePort":
|
||||
result.success(handleClosePort());
|
||||
break;
|
||||
case "setActivePort":
|
||||
result.success(handleSetActivePort(call));
|
||||
break;
|
||||
case "read":
|
||||
result.success(handleRead(call));
|
||||
break;
|
||||
@@ -187,8 +237,6 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
}
|
||||
} catch (ChipException e) {
|
||||
result.error("CHIP_ERROR", e.getMessage(), e.getClass().getName());
|
||||
} catch (NoPermissionException e) {
|
||||
result.error("NO_PERMISSION", e.getMessage(), e.getClass().getName());
|
||||
} catch (UartLibException e) {
|
||||
result.error("UART_LIB_ERROR", e.getMessage(), e.getClass().getName());
|
||||
} catch (Throwable t) {
|
||||
@@ -286,9 +334,13 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
// 设备打开 / 关闭(对应文档 5.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 5.1.1。 */
|
||||
/** 文档 5.1.1。
|
||||
*
|
||||
* <p>若 SDK 抛 {@link NoPermissionException},自动弹出系统
|
||||
* 授权对话框,等待用户选择后再重试一次。
|
||||
*/
|
||||
private boolean handleOpenPort(@NonNull MethodCall call)
|
||||
throws ChipException, NoPermissionException, UartLibException {
|
||||
throws ChipException, UartLibException {
|
||||
Integer deviceId = call.argument("deviceId");
|
||||
Integer serialPortIndex = call.argument("serialPortIndex");
|
||||
if (deviceId == null || serialPortIndex == null) {
|
||||
@@ -298,9 +350,24 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
boolean opened = CH934XManager.getInstance().openDevice(device);
|
||||
if (!opened) {
|
||||
return false;
|
||||
try {
|
||||
boolean opened = openDeviceInternal(device);
|
||||
if (!opened) {
|
||||
return false;
|
||||
}
|
||||
} catch (NoPermissionException npe) {
|
||||
// 没有权限,自动弹出系统授权框,等用户选择。
|
||||
if (!requestUsbPermissionBlocking(device)) {
|
||||
return false;
|
||||
}
|
||||
// 重新尝试打开。
|
||||
try {
|
||||
if (!openDeviceInternal(device)) {
|
||||
return false;
|
||||
}
|
||||
} catch (NoPermissionException retryFail) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
activeDevice = device;
|
||||
activeSerialNumber = serialPortIndex;
|
||||
@@ -309,6 +376,104 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 内部真正调 SDK 的打开,让 [handleOpenPort] 能在外层捕获权限异常。 */
|
||||
private boolean openDeviceInternal(@NonNull UsbDevice device)
|
||||
throws ChipException, NoPermissionException, UartLibException {
|
||||
return CH934XManager.getInstance().openDevice(device);
|
||||
}
|
||||
|
||||
/** 处理 Dart 主动发起的"申请 USB 权限"调用。 */
|
||||
private boolean handleRequestUsbPermission(@NonNull MethodCall call) {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
return requestUsbPermissionBlocking(device);
|
||||
}
|
||||
|
||||
/** 同步等待用户对 USB 权限对话框的回应,返回是否授权。 */
|
||||
private boolean requestUsbPermissionBlocking(@NonNull UsbDevice device) {
|
||||
if (activity == null) {
|
||||
return false;
|
||||
}
|
||||
UsbManager usbManager = (UsbManager) applicationContext
|
||||
.getSystemService(Context.USB_SERVICE);
|
||||
if (usbManager == null) {
|
||||
return false;
|
||||
}
|
||||
if (usbManager.hasPermission(device)) {
|
||||
return true;
|
||||
}
|
||||
ensurePermissionReceiver();
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
pendingPermissionRequest = future;
|
||||
|
||||
int flags = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
||||
: PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
PendingIntent intent = PendingIntent.getBroadcast(
|
||||
activity, 0, new Intent(ACTION_USB_PERMISSION), flags);
|
||||
usbManager.requestPermission(device, intent);
|
||||
|
||||
try {
|
||||
Boolean result = future.get(60, TimeUnit.SECONDS);
|
||||
return result != null && result;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
} finally {
|
||||
pendingPermissionRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册用于接收 USB 授权结果的 BroadcastReceiver(仅注册一次)。 */
|
||||
private void ensurePermissionReceiver() {
|
||||
if (permissionReceiver != null || activity == null) {
|
||||
return;
|
||||
}
|
||||
permissionReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!ACTION_USB_PERMISSION.equals(intent.getAction())) {
|
||||
return;
|
||||
}
|
||||
boolean granted = intent.getBooleanExtra(
|
||||
UsbManager.EXTRA_PERMISSION_GRANTED, false);
|
||||
CompletableFuture<Boolean> future = pendingPermissionRequest;
|
||||
if (future != null) {
|
||||
future.complete(granted);
|
||||
}
|
||||
}
|
||||
};
|
||||
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// 13+ 强制要求显式 flag;USB 授权响应只来自系统,使用 NOT_EXPORTED。
|
||||
activity.registerReceiver(permissionReceiver, filter,
|
||||
Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
activity.registerReceiver(permissionReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPermissionReceiver() {
|
||||
// Receiver 在真正需要时(申请权限前)才注册,避免在 manifest 中额外声明。
|
||||
}
|
||||
|
||||
private void unregisterPermissionReceiver() {
|
||||
if (permissionReceiver != null && activity != null) {
|
||||
try {
|
||||
activity.unregisterReceiver(permissionReceiver);
|
||||
} catch (Throwable ignored) {
|
||||
// Receiver 未注册时 ignore。
|
||||
}
|
||||
}
|
||||
permissionReceiver = null;
|
||||
CompletableFuture<Boolean> future = pendingPermissionRequest;
|
||||
if (future != null) {
|
||||
future.complete(false);
|
||||
pendingPermissionRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 文档 5.2.1。 */
|
||||
private boolean handleClosePort() {
|
||||
if (activeDevice == null) {
|
||||
@@ -327,6 +492,19 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 切换当前活跃串口索引(无需重新 open 设备)。 */
|
||||
private boolean handleSetActivePort(@NonNull MethodCall call) {
|
||||
if (activeDevice == null) {
|
||||
return false;
|
||||
}
|
||||
Integer idx = call.argument("serialPortIndex");
|
||||
if (idx == null || idx < 0) {
|
||||
return false;
|
||||
}
|
||||
activeSerialNumber = idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 串口读写(对应文档 6.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user