commit 3c2bb02e33556e5649c244ae776c64e3a62ac193 Author: leon <916117771@qq.com> Date: Mon Mar 30 15:21:07 2026 +0800 docs: map existing codebase diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md new file mode 100644 index 0000000..8a6aec9 --- /dev/null +++ b/.planning/codebase/ARCHITECTURE.md @@ -0,0 +1,160 @@ +# Architecture + +**Analysis Date:** 2026-03-30 + +## Pattern Overview + +**Overall:** Flutter Plugin Architecture with Platform Interface Pattern + +**Key Characteristics:** +- Flutter plugin wrapping ArcSoft Face Recognition SDK (Android only) +- Platform interface abstraction for cross-platform extensibility +- Method channel communication between Dart and native Android +- Singleton pattern for native FaceEngine management +- Comprehensive error code enumeration for SDK error handling + +## Layers + +**Dart Public API Layer:** +- Purpose: Provides user-facing API for face recognition operations +- Location: `lib/arc.dart` +- Contains: Public methods with detailed documentation +- Depends on: `ArcPlatform` interface +- Used by: Flutter applications consuming this plugin + +**Platform Interface Layer:** +- Purpose: Abstract interface for platform-specific implementations +- Location: `lib/arc_platform_interface.dart` +- Contains: Abstract method definitions, platform instance management +- Depends on: `plugin_platform_interface` package +- Used by: `Arc` class, mock implementations for testing + +**Method Channel Layer:** +- Purpose: Implements platform interface using Flutter method channels +- Location: `lib/arc_method_channel.dart` +- Contains: Method channel invocation logic, parameter marshalling +- Depends on: Flutter `MethodChannel`, `ArcPlatform` interface +- Used by: Default platform instance registration + +**Android Native Plugin Layer:** +- Purpose: Handles method channel calls, dispatches to engine manager +- Location: `android/src/main/java/com/xiarui/arc/ArcPlugin.java` +- Contains: Method call handler, parameter validation, result marshalling +- Depends on: `FaceEngineManager`, Flutter plugin APIs +- Used by: Flutter engine via plugin registration + +**Android Engine Manager Layer:** +- Purpose: Manages ArcSoft FaceEngine singleton, executes SDK operations +- Location: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` +- Contains: SDK activation, engine lifecycle, face detection/extraction/comparison logic +- Depends on: ArcSoft SDK (`arcsoft_face.jar`) +- Used by: `ArcPlugin` handler methods + +**Data Model Layer:** +- Purpose: Encapsulates face information and error codes +- Location: `android/src/main/java/com/xiarui/arc/FaceInfo.java`, `FaceErrorCode.java` +- Contains: Data structures for face results, comprehensive error code enumeration +- Depends on: Android SDK (`Rect` class) +- Used by: All native layers for data transfer + +## Data Flow + +**SDK Activation Flow:** + +1. Flutter app calls `Arc.activeOnline(appId, sdkKey, activeKey)` +2. `Arc` delegates to `ArcPlatform.instance.activeOnline()` +3. `MethodChannelArc` invokes method channel with `'activeOnline'` +4. `ArcPlugin.handleActiveOnline()` validates parameters +5. `FaceEngineManager.getInstance().activeOnline()` calls ArcSoft SDK +6. Error code mapped to `FaceErrorCode` enum +7. Result Map returned through method channel to Flutter + +**Face Detection Flow:** + +1. Flutter app calls `Arc.detectFaces(data, width, height, format)` +2. Method channel invokes `'detectFaces'` with byte array payload +3. `ArcPlugin.handleDetectFaces()` validates image parameters +4. `FaceEngineManager.detectFaces()` executes SDK face detection +5. RGB liveness detection processed via `faceEngine.process()` and `getLiveness()` +6. `FaceInfo` list converted to Map structure via `convertFaceInfoToList()` +7. Result includes faceList, rgbLiveness, isRgbAlive + +**Face Feature Extraction Flow:** + +1. Flutter app calls `Arc.extractFaceFeature()` with face rect from detection +2. Native layer creates `FaceInfo` object from rect coordinates +3. `FaceEngineManager.extractFaceFeature()` calls SDK extract method +4. Feature data (byte array) returned in result Map + +**Feature Comparison Flow:** + +1. Flutter app calls `Arc.compareFaceFeature(featureData1, featureData2, compareModel)` +2. Native layer creates `FaceFeature` objects from byte arrays +3. `FaceEngineManager.compareFaceFeature()` executes SDK comparison +4. Similarity score (0.0-1.0) returned in result Map + +**State Management:** +- Singleton `FaceEngineManager` holds `FaceEngine` instance +- Engine initialization state tracked via `isInitialized` flag +- Last RGB liveness result cached in `lastRgbLiveness` field + +## Key Abstractions + +**ArcPlatform Interface:** +- Purpose: Platform-agnostic interface for face recognition operations +- Examples: `lib/arc_platform_interface.dart` +- Pattern: Platform Interface pattern with token verification + +**FaceEngineManager Singleton:** +- Purpose: Centralized management of ArcSoft FaceEngine lifecycle +- Examples: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` +- Pattern: Singleton with synchronized getInstance() + +**FaceErrorCode Enumeration:** +- Purpose: Maps SDK numeric error codes to human-readable messages +- Examples: `android/src/main/java/com/xiarui/arc/FaceErrorCode.java` +- Pattern: Enum with code lookup via `fromCode()` + +**FaceInfo Data Model:** +- Purpose: Represents single face detection result +- Examples: `android/src/main/java/com/xiarui/arc/FaceInfo.java` +- Pattern: JavaBean with `toMap()` for Flutter serialization + +## Entry Points + +**Flutter Plugin Entry:** +- Location: `lib/arc.dart` +- Triggers: Flutter app imports and instantiates `Arc` class +- Responsibilities: Public API facade, delegates to platform interface + +**Android Plugin Registration:** +- Location: `android/src/main/java/com/xiarui/arc/ArcPlugin.java` +- Triggers: Flutter engine attaches plugin via `onAttachedToEngine()` +- Responsibilities: Method channel setup, call dispatching + +**Example App Entry:** +- Location: `example/lib/main.dart` +- Triggers: App launch via `void main()` +- Responsibilities: Demonstrates SDK activation, engine init, camera-based face detection + +## Error Handling + +**Strategy:** Consistent Map return structure with success/error fields + +**Patterns:** +- All public methods return `Map?` with keys: `success`, `errorCode`, `message` +- Native layer uses `FaceErrorCode.fromCode()` for error mapping +- Custom error codes for internal failures (e.g., `ENGINE_NOT_INITIALIZED = -1`) +- Flutter layer receives structured error info, no exception throwing for SDK errors +- Platform exceptions caught in example app with `PlatformException` handler + +## Cross-Cutting Concerns + +**Logging:** Android native uses `android.util.Log` with tag "FaceEngineManager" +**Validation:** Native methods validate engine initialization, image dimensions (width %4, height %2 for NV21) +**Authentication:** SDK requires online activation with appId/sdkKey/activeKey from ArcSoft console +**Image Format:** NV21 (format code 2050) is primary supported format for camera streams + +--- + +*Architecture analysis: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md new file mode 100644 index 0000000..dbc0536 --- /dev/null +++ b/.planning/codebase/CONCERNS.md @@ -0,0 +1,195 @@ +# Codebase Concerns + +**Analysis Date:** 2026-03-30 + +## Tech Debt + +**iOS Platform Support Missing:** +- Issue: The plugin only supports Android platform; iOS implementation is completely absent +- Files: `pubspec.yaml` (no iOS platform definition), `lib/arc_platform_interface.dart`, `lib/arc_method_channel.dart` +- Impact: Plugin cannot be used in iOS applications; limits market reach +- Fix approach: Create iOS native implementation using Swift/Objective-C with ArcSoft SDK iOS bindings + +**Unimplemented Mock Methods in Tests:** +- Issue: `MockArcPlatform` in test file does not implement `registerFaceFeature` and `registerFaceFeatureBatch` methods +- Files: `test/arc_test.dart` (lines 8-60) +- Impact: Tests will fail when calling registration methods; incomplete test coverage +- Fix approach: Add mock implementations for all missing methods in `MockArcPlatform` + +**Placeholder Documentation:** +- Issue: CHANGELOG.md and LICENSE files contain placeholder TODO text +- Files: `CHANGELOG.md` (line 3), `LICENSE` (line 1) +- Impact: Project appears incomplete; may confuse users about licensing +- Fix approach: Replace placeholder text with actual content + +## Known Bugs + +**No explicit engine disposal:** +- Symptoms: `FaceEngine` instance in `FaceEngineManager` is never nullified after `unInit()` +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (lines 268-278) +- Trigger: Calling `unInit()` then attempting to reuse engine +- Workaround: Re-initialize engine after disposal + +**State inconsistency after initialization failure:** +- Symptoms: If `init()` fails, `isInitialized` remains false but `faceEngine` object still exists +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (lines 141-159) +- Trigger: Engine initialization with invalid parameters +- Workaround: Check both `isInitialized` flag and error code before proceeding + +## Security Considerations + +**Hardcoded SDK Credentials in Example:** +- Risk: Real SDK credentials (appId, sdkKey, activeKey) hardcoded in example application +- Files: `example/lib/main.dart` (lines 41-43) + ```dart + final String _appId = '4nPFuS2TYAQh9werHL2qbKjtTH9nnoixk7G6yqSUyjVH'; + final String _sdkKey = 'aWMTT3coxNQFETg9f3BGHCiBznAApXmcHFF3J5yQbsZ'; + final String _activeKey = 'NEAT7AU4KLCEK682'; + ``` +- Current mitigation: Comment instructs users to replace with own keys (line 286) +- Recommendations: Use environment variables or config file; never commit real credentials; add `.env.example` template + +**Credential Transmission via Method Channel:** +- Risk: Sensitive credentials (appId, sdkKey, activeKey) passed through method channel without encryption +- Files: `lib/arc_method_channel.dart` (lines 24-28), `android/src/main/java/com/xiarui/arc/ArcPlugin.java` (lines 95-97) +- Current mitigation: None +- Recommendations: Credentials are passed to native SDK which handles secure activation; acceptable for current architecture + +**Feature Data Privacy:** +- Risk: Face feature data transmitted between Flutter and native layers could be intercepted +- Files: `lib/arc_method_channel.dart`, `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` +- Current mitigation: Data stays within app boundary +- Recommendations: Ensure no logging of feature data; consider encryption for stored features + +## Performance Bottlenecks + +**Large Binary Data Transfer:** +- Problem: Image data (NV21 format, potentially several MB per frame) transferred through method channel for each detection +- Files: `lib/arc_method_channel.dart` (lines 55-61), `android/src/main/java/com/xiarui/arc/ArcPlugin.java` (lines 154-157) +- Cause: Method channel serialization overhead for large byte arrays +- Improvement path: Consider using FFI for direct memory access; reduce image resolution; implement frame skipping + +**Real-time Detection Frame Rate:** +- Problem: `_isDetecting` flag prevents concurrent processing but may cause frame drops +- Files: `example/lib/main.dart` (lines 406-407, 471-473) +- Cause: Sequential processing - new frames wait until current detection completes +- Improvement path: Implement queue-based processing with configurable frame skip; use dedicated detection thread + +**Feature Extraction Overhead:** +- Problem: Feature extraction called synchronously, blocking UI thread potential +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (lines 365-366) +- Cause: SDK operation runs on method channel thread +- Improvement path: Move heavy processing to background thread pool; provide async callbacks + +## Fragile Areas + +**NV21 Format Constraints:** +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (lines 179-189) +- Why fragile: Image width must be multiple of 4, height must be multiple of 2 for NV21 format - violation causes runtime errors +- Safe modification: Always validate dimensions before calling SDK; add Flutter-side validation +- Test coverage: No tests for invalid dimension handling + +**Singleton State Management:** +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (lines 54-59) +- Why fragile: Singleton pattern with mutable `lastRgbLiveness` field; concurrent access risk +- Safe modification: Add synchronization for mutable fields; or use ThreadLocal for liveness results +- Test coverage: No concurrent access tests + +**Camera Preview Orientation:** +- Files: `example/lib/main.dart` (line 556) +- Why fragile: Fixed `quarterTurns: -1` rotation may not work for all devices/orientations +- Safe modification: Calculate rotation based on device orientation and camera sensor orientation +- Test coverage: No tests for different device orientations + +## Scaling Limits + +**Face Database Size:** +- Current capacity: Depends on ArcSoft SDK limits (typically 50,000 faces) +- Limit: Memory consumption increases with registered faces; search time increases +- Scaling path: Implement pagination; use external database for large-scale deployments; consider face clustering + +**Image Resolution:** +- Current capacity: Default camera resolution (medium preset) +- Limit: Higher resolution = slower detection, more memory usage +- Scaling path: Allow configurable resolution; implement dynamic resolution adjustment based on device capability + +**Concurrent Detection Requests:** +- Current capacity: Single detection at a time (`_isDetecting` flag) +- Limit: Multiple simultaneous requests queue up +- Scaling path: Implement request queue with priority; batch processing; dedicated worker threads + +## Dependencies at Risk + +**ArcSoft Face SDK:** +- Risk: Third-party proprietary SDK; licensing and availability concerns +- Impact: Plugin entirely dependent on ArcSoft SDK availability and compatibility +- Migration plan: Abstract SDK interface; allow alternative face recognition backends (e.g., ML Kit, OpenCV) + +**camera package:** +- Risk: External dependency in example app; version compatibility +- Impact: Example app functionality dependent on camera plugin stability +- Migration plan: Keep camera package updated; abstract camera interface for alternative implementations + +**Flutter SDK Version:** +- Risk: Requires SDK ^3.9.0 which is relatively new +- Files: `pubspec.yaml` (line 7) +- Impact: Older Flutter versions cannot use plugin +- Migration plan: Consider lowering SDK constraint if backward compatibility needed + +## Missing Critical Features + +**Face Search (1:N Matching):** +- Problem: `registerFaceFeature` and `registerFaceFeatureBatch` exist but no `searchFaceFeature` method to query registered faces +- Files: `lib/arc.dart`, `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` +- Blocks: Cannot implement 1:N face recognition workflow (find matching face from database) + +**Engine Uninit from Flutter:** +- Problem: No Flutter API to release engine resources +- Files: `lib/arc.dart` (no unInit method) +- Blocks: Memory leak if engine not properly released; cannot reset engine state + +**Error Code Documentation for Flutter:** +- Problem: Error codes defined in Java but no Flutter-accessible documentation +- Files: `android/src/main/java/com/xiarui/arc/FaceErrorCode.java` (comprehensive), `lib/arc.dart` (no error code constants) +- Blocks: Flutter developers cannot interpret error codes without referencing native code + +**IR Liveness Detection:** +- Problem: Only RGB liveness detection implemented; IR liveness not exposed +- Files: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` (only ASF_LIVENESS mask) +- Blocks: Cannot use more secure IR-based liveness detection if hardware available + +## Test Coverage Gaps + +**Native Unit Tests:** +- What's not tested: All methods except `getPlatformVersion` in `ArcPluginTest.java` +- Files: `android/src/test/java/com/xiarui/arc/ArcPluginTest.java` (only 1 test method) +- Risk: Native method handling bugs undetected +- Priority: High + +**Flutter Integration Tests:** +- What's not tested: Actual face detection with real image data; end-to-end workflows +- Files: No integration test files in `test/` directory +- Risk: Real-world usage patterns not validated +- Priority: High + +**Method Channel Tests:** +- What's not tested: All methods except `getPlatformVersion` in method channel test +- Files: `test/arc_method_channel_test.dart` (lines 24-26) +- Risk: Method channel communication bugs for complex methods undetected +- Priority: Medium + +**Edge Case Tests:** +- What's not tested: Invalid parameters, null data, oversized images, engine not initialized +- Files: All test files lack edge case coverage +- Risk: Runtime crashes on invalid inputs +- Priority: Medium + +**Mock Implementation Completeness:** +- What's not tested: `registerFaceFeature`, `registerFaceFeatureBatch` methods in mock +- Files: `test/arc_test.dart` (`MockArcPlatform` missing methods) +- Risk: Tests fail or skip when new methods are used +- Priority: Low + +--- + +*Concerns audit: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md new file mode 100644 index 0000000..8aaa055 --- /dev/null +++ b/.planning/codebase/CONVENTIONS.md @@ -0,0 +1,239 @@ +# Coding Conventions + +**Analysis Date:** 2026-03-30 + +## Naming Patterns + +**Files:** +- Dart: lowercase with underscores (`arc.dart`, `arc_platform_interface.dart`, `arc_method_channel.dart`) +- Java: PascalCase matching class name (`ArcPlugin.java`, `FaceEngineManager.java`) + +**Classes:** +- PascalCase: `Arc`, `ArcPlatform`, `MethodChannelArc`, `FaceEngineManager`, `FaceErrorCode` + +**Methods/Functions:** +- camelCase: `getPlatformVersion`, `activeOnline`, `detectFaces`, `extractFaceFeature` + +**Variables:** +- Private Dart members: underscore prefix (`_token`, `_instance`, `_isDetecting`) +- Java: `private` keyword with camelCase: `faceEngine`, `isInitialized`, `lastRgbLiveness` + +**Constants:** +- Dart: `static final` with underscore for private: `static final Object _token = Object();` +- Java enum: uppercase with underscores: `MOK`, `MERR_UNKNOWN`, `ENGINE_NOT_INITIALIZED` + +## Code Style + +**Formatting:** +- Dart: `flutter_lints ^5.0.0` via `analysis_options.yaml` +- Config: `include: package:flutter_lints/flutter.yaml` +- Java: Standard Android/Java conventions + +**Linting:** +- Dart: flutter_lints package rules +- Java: No explicit linting config detected + +## Import Organization + +**Dart Order:** +1. SDK imports (`dart:typed_data`) +2. Package imports (`package:plugin_platform_interface/plugin_platform_interface.dart`) +3. Relative imports (`arc_method_channel.dart`) + +**Example:** +```dart +import 'dart:typed_data'; + +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'arc_method_channel.dart'; +``` + +**Java Order:** +1. Package declaration +2. Android SDK imports +3. Third-party imports +4. JUnit/Mockito imports (in tests) + +## Error Handling + +**Dart Patterns:** +- Platform interface throws `UnimplementedError` for abstract methods: +```dart +Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); +} +``` +- Method channel calls wrapped in try-catch with `PlatformException` +- Return `Map?` with `success`, `errorCode`, `message` keys + +**Java Patterns:** +- Parameter validation before processing +- Error codes via `FaceErrorCode` enum +- Result Map with consistent keys: `success`, `errorCode`, `message` +- Logging with `android.util.Log.e()` for errors + +**Return Format:** +All API methods return consistent Map structure: +```dart +{ + 'success': bool, // Operation success status + 'errorCode': int, // Error code (0 = success) + 'message': String, // Error/success message + // Additional data keys based on method +} +``` + +## Documentation + +**Dart Comments:** +- Triple-slash `///` for method documentation +- Chinese descriptions required +- Parameter documentation: `[paramName] description` +- Return documentation after parameters + +**Example:** +```dart +/// 激活 SDK(在线激活) +/// [appId] 应用 ID(从虹软控制台获取) +/// [sdkKey] SDK 密钥(从虹软控制台获取) +/// [activeKey] 激活密钥 +/// 返回包含 success, errorCode, message 的 Map +Future?> activeOnline({ + required String appId, + required String sdkKey, + required String activeKey, +}) +``` + +**Java Comments:** +- Javadoc `/** */` format +- `@param` and `@return` tags +- Chinese descriptions + +**Example:** +```java +/** + * 激活 SDK(在线激活方式) + * @param context Android 上下文 + * @param appId 应用 ID(从虹软控制台获取) + * @param sdkKey SDK 密钥(从虹软控制台获取) + * @param activeKey 激活密钥 + * @return 错误码(0 表示成功) + */ +public int activeOnline(Context context, String appId, String sdkKey, String activeKey) +``` + +## Logging + +**Dart:** +- `debugPrint()` for development logging +- Located in: `example/lib/main.dart` + +**Java:** +- `android.util.Log.i()` for info +- `android.util.Log.e()` for errors +- `android.util.Log.w()` for warnings +- Tag: `"FaceEngineManager"` + +**Example:** +```java +android.util.Log.i("FaceEngineManager", "引擎初始化成功!"); +android.util.Log.e("FaceEngineManager", "引擎初始化失败,错误码: " + result); +``` + +## Function Design + +**Parameters:** +- Dart: Named parameters with `required` keyword +- Optional parameters have default values + +**Example:** +```dart +Future?> detectFaces({ + required Uint8List data, + required int width, + required int height, + int format = 2050, // Optional with default +}) +``` + +**Return Values:** +- All async methods return `Future?>` +- Null-safe with nullable type annotation `?` + +## Module Design + +**Platform Interface Pattern:** +- Abstract class extends `PlatformInterface` +- Static instance getter/setter +- Token verification for platform registration + +**Example Structure:** +```dart +abstract class ArcPlatform extends PlatformInterface { + ArcPlatform() : super(token: _token); + static final Object _token = Object(); + static ArcPlatform _instance = MethodChannelArc(); + static ArcPlatform get instance => _instance; + static set instance(ArcPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } +} +``` + +**Method Channel Implementation:** +- Class extends platform interface +- `@visibleForTesting` annotation for test exposure +- Method channel with constant name: `'arc'` + +**Singleton Pattern (Java):** +```java +public class FaceEngineManager { + private static FaceEngineManager instance; + private FaceEngineManager() {} + public static synchronized FaceEngineManager getInstance() { + if (instance == null) { + instance = new FaceEngineManager(); + } + return instance; + } +} +``` + +## State Management + +**Flutter Widget State:** +- Private state variables with underscore prefix +- `StatefulWidget` with corresponding `State` class +- `mounted` check before `setState` + +**Example:** +```dart +class _HomePageState extends State { + String _platformVersion = 'Unknown'; + bool _isActivated = false; + + if (!mounted) return; + setState(() { + _platformVersion = platformVersion; + }); +} +``` + +## Key Files + +**Core Dart Files:** +- `lib/arc.dart` - Main API class +- `lib/arc_platform_interface.dart` - Abstract platform interface +- `lib/arc_method_channel.dart` - Method channel implementation + +**Core Java Files:** +- `android/src/main/java/com/xiarui/arc/ArcPlugin.java` - Flutter plugin entry +- `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` - Engine singleton +- `android/src/main/java/com/xiarui/arc/FaceErrorCode.java` - Error code enum + +--- + +*Convention analysis: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md new file mode 100644 index 0000000..bc7ae55 --- /dev/null +++ b/.planning/codebase/INTEGRATIONS.md @@ -0,0 +1,158 @@ +# External Integrations + +**Analysis Date:** 2026-03-30 + +## APIs & External Services + +**人脸识别 SDK:** +- 虹软 (ArcSoft) 人脸识别 SDK - 离线人脸检测、特征提取、活体检测 + - SDK 库: `arcsoft_face.jar`, `libarcsoft_face.so` + - 版本: 基于 2024年1月版本 + - Auth: 需要 AppId、SdkKey、ActiveKey(从虹软开放平台获取) + - 激活方式: 在线激活(`FaceEngine.activeOnline()`) + - 官方文档: https://ai.arcsoft.com.cn + +**图像处理工具:** +- 虹软图像处理工具库 - 图像格式转换和处理 + - SDK 库: `arcsoft_image_util.jar`, `libarcsoft_image_util.so` + +## Data Storage + +**Databases:** +- 无数据库集成 - 人脸特征数据存储在内存中(人脸库) + +**Face Feature Database (In-Memory):** +- 人脸库存储: 通过 `FaceEngine.registerFaceFeature()` 注册到 SDK 内部内存 +- 数据结构: `FaceFeatureInfo(searchId, featureData, faceTag)` +- 搜索能力: 1:N 人脸搜索(SDK 内置) +- 注意: 人脸库数据在引擎释放后丢失,不持久化 + +**File Storage:** +- 虹软 SDK 激活文件存储于设备本地(SDK 自动管理) +- 位置: Android 设备内部存储(SDK 控制) + +**Caching:** +- None - 无缓存层 + +## Authentication & Identity + +**Auth Provider:** +- 虹软 SDK 激活机制 - 设备绑定激活 + - Implementation: 在线激活验证 AppId + SdkKey + ActiveKey + - 激活文件: 设备指纹绑定,单设备单激活 + - 错误处理: `FaceErrorCode` 枚举定义完整错误码映射 + +**激活状态检查:** +```dart +// 激活成功条件: errorCode == 0 或 errorCode == 90114 (已激活) +result['success'] = errorCode == 0 || errorCode == 90114; +``` + +**激活流程:** +1. 调用 `Arc.activeOnline(appId, sdkKey, activeKey)` +2. Flutter MethodChannel 传递参数到 Android +3. Android 调用 `FaceEngine.activeOnline(context, activeKey, appId, sdkKey)` +4. SDK 与虹软服务器验证并生成激活文件 +5. 返回激活结果 Map + +## Monitoring & Observability + +**Error Tracking:** +- 错误码枚举系统 - `FaceErrorCode.java`(完整错误码映射) +- 分类: 通用错误、SDK基础错误、人脸识别错误、激活错误、网络错误、离线授权错误 + +**Logs:** +- Android Logcat 输出(`FaceEngineManager.java` 使用 `android.util.Log`) +- Flutter debugPrint 输出(示例应用) +- 日志级别: Info, Error + +**关键日志点:** +- SDK 初始化结果: `FaceEngineManager.init()` +- 人脸检测结果: `FaceEngineManager.detectFaces()` +- RGB 活体检测结果: `FaceEngineManager.getLiveness()` +- 特征提取结果: `FaceEngineManager.extractFaceFeature()` +- 人脸比对结果: `FaceEngineManager.compareFaceFeature()` + +## CI/CD & Deployment + +**Hosting:** +- 本地 Flutter 插件包 - 不发布到 pub.dev(私有包) +- `publish_to: 'none'`(示例应用) + +**CI Pipeline:** +- None - 无 CI 配置文件 + +**Build Commands:** +```bash +flutter pub get # 获取依赖 +flutter test # 运行 Flutter 单元测试 +flutter build apk # 构建 Android APK +cd android && ./gradlew test # 运行 Android 单元测试 +``` + +## Environment Configuration + +**Required env vars (SDK 激活):** +- AppId: 虹软开放平台申请的应用 ID +- SdkKey: 虹软开放平台申请的 SDK 密钥 +- ActiveKey: 激活密钥(可选,部分场景需要) + +**示例应用配置(硬编码):** +```dart +// example/lib/main.dart +final String _appId = '4nPFuS2TYAQh9werHL2qbKjtTH9nnoixk7G6yqSUyjVH'; +final String _sdkKey = 'aWMTT3coxNQFETg9f3BGHCiBznAApXmcHFF3J5yQbsZ'; +final String _activeKey = 'NEAT7AU4KLCEK682'; +``` + +**Secrets location:** +- 当前: 硬编码在示例应用(不推荐生产使用) +- 建议: 通过环境变量或安全存储传递 + +## Webhooks & Callbacks + +**Incoming:** +- None - 无外部 webhook + +**Outgoing:** +- None - 无外部 API 调用(SDK 激活除外) + +## Flutter-Native Communication + +**Method Channel:** +- Channel 名称: `'arc'` +- 定义文件: `lib/arc_method_channel.dart` +- 实现: `MethodChannelArc` 类 + +**支持的方法:** +| 方法名 | 功能 | 参数 | +|--------|------|------| +| `getPlatformVersion` | 获取平台版本 | 无 | +| `activeOnline` | SDK 在线激活 | appId, sdkKey, activeKey | +| `init` | 初始化人脸引擎 | detectMode, orient, maxFaceNum, combinedMask | +| `detectFaces` | 人脸检测+RGB活体 | data, width, height, format | +| `extractFaceFeature` | 特征提取 | data, width, height, rect, faceId, extractType, mask | +| `compareFaceFeature` | 特征比对 | featureData1, featureData2, compareModel | +| `registerFaceFeature` | 单张人脸注册 | searchId, featureData, faceTag | +| `registerFaceFeatureBatch` | 批量人脸注册 | faceList | + +**数据格式:** +- 图像数据: NV21 格式(`Uint8List`) +- 特征数据: SDK 定义长度(`Uint8List`) +- 返回值: `Map` 格式统一 + +## Image Format Requirements + +**NV21 格式约束:** +- 图像宽度必须是 4 的倍数 +- 图像高度必须是 2 的倍数(NV21 格式) +- 格式码: 2050 (`FaceEngine.CP_PAF_NV21`) + +**Camera Integration (Example):** +- 使用 `camera` 插件获取摄像头图像流 +- `ImageFormatGroup.nv21` 设置图像格式 +- `CameraPreviewScreen` 处理实时图像流检测 + +--- + +*Integration audit: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md new file mode 100644 index 0000000..53924b8 --- /dev/null +++ b/.planning/codebase/STACK.md @@ -0,0 +1,109 @@ +# Technology Stack + +**Analysis Date:** 2026-03-30 + +## Languages + +**Primary:** +- Dart 3.9.0+ - Flutter 插件层代码(`lib/*.dart`) +- Java 11 - Android 原生实现(`android/src/main/java/**/*.java`) + +**Secondary:** +- Kotlin - 未使用(纯 Java 实现) +- C/C++ (Native) - 虹软 SDK native 库(`libarcsoft_face.so`, `libarcsoft_face_engine.so`, `libarcsoft_image_util.so`) + +## Runtime + +**Environment:** +- Flutter SDK 3.18.0+ (stable channel) +- Dart SDK 3.9.0+ (<4.0.0) +- Android SDK compileSdk 36, minSdk 24 + +**Package Manager:** +- pub (Flutter/Dart 包管理器) +- Gradle 8.9.1 (Android 构建) +- Lockfile: `pubspec.lock` (present), `pubspec.yaml` (present) + +## Frameworks + +**Core:** +- Flutter SDK - 跨平台移动应用框架 +- Flutter Plugin Architecture - 平台通道通信机制 + +**Testing:** +- flutter_test - Flutter 单元测试框架 +- flutter_lints 5.0.0 - Dart/Flutter 代码规范检查 +- JUnit 4.13.2 - Java 单元测试 +- Mockito 5.0.0 - Java mock 测试框架 + +**Build/Dev:** +- Gradle 8.9.1 - Android 构建系统 +- Android Gradle Plugin 8.9.1 - Android 构建插件 +- Flutter CLI - Flutter 命令行工具 + +## Key Dependencies + +**Critical:** +- `plugin_platform_interface` 2.0.2 - Flutter 插件平台接口抽象层 +- `arcsoft_face.jar` - 虹软人脸识别核心 SDK +- `arcsoft_image_util.jar` - 虹软图像处理工具库 +- `libarcsoft_face.so` (~87MB) - 虹软人脸识别 native 库(arm64-v8a) +- `libarcsoft_face_engine.so` (~1.3MB) - 虹软人脸引擎 native 库 +- `libarcsoft_image_util.so` (~80KB) - 虹软图像处理 native 库 + +**Infrastructure (Example App):** +- `camera` 0.11.1 - 摄像头访问插件(用于实时人脸检测) +- `cupertino_icons` 1.0.8 - iOS 风格图标 + +## Configuration + +**Environment:** +- Dart SDK 版本约束: `^3.9.0`(`pubspec.yaml`) +- Flutter SDK 版本约束: `>=3.3.0`(`pubspec.yaml`) +- Java 版本: 11(`android/build.gradle`) +- Pub 仓库源: `https://pub.flutter-io.cn`(中国镜像) + +**Build:** +- `pubspec.yaml` - Dart/Flutter 包配置 +- `pubspec.lock` - 依赖锁定文件 +- `analysis_options.yaml` - Dart 代码分析配置(继承 flutter_lints) +- `android/build.gradle` - Android 构建配置 +- `android/settings.gradle` - Android 项目设置 + +## Platform Requirements + +**Development:** +- Flutter SDK 3.18.0+ 安装 +- Dart SDK 3.9.0+ 安装 +- Android Studio / Android SDK(compileSdk 36) +- JDK 11 + +**Production:** +- Android 设备(API 24+,即 Android 7.0+) +- ARM64-v8a 或 armeabi-v7a 架构支持 +- 摄像头权限(用于人脸检测) +- 虹软 SDK 有效激活(AppId、SdkKey、ActiveKey) + +## Architecture Support + +**Native Libraries:** +- `arm64-v8a` - 64位 ARM 设备(主流现代设备) +- `armeabi-v7a` - 32位 ARM 设备(兼容旧设备) + +**SDK 功能掩码常量:** +```java +ASF_FACE_DETECT = 0x00000001 // 人脸检测 +ASF_FACE_RECOGNITION = 0x00000004 // 人脸特征 +ASF_AGE = 0x00000008 // 年龄检测 +ASF_GENDER = 0x00000010 // 性别检测 +ASF_LIVENESS = 0x00000080 // RGB 活体检测 +ASF_IMAGEQUALITY = 0x00000200 // 图像质量检测 +ASF_MASK_DETECT = 0x00001000 // 口罩检测 +ASF_UPDATE_FACEDATA = 0x00002000 // 人脸信息更新 +``` + +**默认功能掩码:** 0x85 = 人脸检测 + 人脸特征 + RGB活体检测 + +--- + +*Stack analysis: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md new file mode 100644 index 0000000..2e392d7 --- /dev/null +++ b/.planning/codebase/STRUCTURE.md @@ -0,0 +1,175 @@ +# Codebase Structure + +**Analysis Date:** 2026-03-30 + +## Directory Layout + +``` +D:\code\new_git_code\arc/ +├── lib/ # Flutter Dart source code +├── android/ # Android native implementation +├── test/ # Unit tests for Dart code +├── example/ # Example Flutter application +├── docs/ # Documentation files +├── .dart_tool/ # Dart tool cache (generated) +├── .planning/ # Planning documents (generated) +└── pubspec.yaml # Flutter package manifest +``` + +## Directory Purposes + +**lib:** +- Purpose: Flutter plugin Dart source code +- Contains: Public API, platform interface, method channel implementation +- Key files: `arc.dart`, `arc_platform_interface.dart`, `arc_method_channel.dart` + +**android:** +- Purpose: Android native implementation for ArcSoft SDK integration +- Contains: Java source, ArcSoft SDK libraries, Gradle build config +- Key files: `src/main/java/com/xiarui/arc/ArcPlugin.java`, `libs/arcsoft_face.jar` + +**test:** +- Purpose: Unit tests for Dart plugin code +- Contains: Mock platform implementations, method channel tests +- Key files: `arc_test.dart`, `arc_method_channel_test.dart` + +**example:** +- Purpose: Demonstrates plugin usage with camera-based face detection +- Contains: Complete Flutter app with camera preview and detection UI +- Key files: `lib/main.dart`, `integration_test/plugin_integration_test.dart` + +**docs:** +- Purpose: External SDK documentation +- Contains: ArcSoft face recognition API documentation +- Key files: `虹软人脸识别接口文档.md` + +**android/libs:** +- Purpose: ArcSoft native SDK JAR files +- Contains: `arcsoft_face.jar` (face recognition), `arcsoft_image_util.jar` (image utilities) +- Generated: No (third-party SDK) +- Committed: Yes + +**android/src/main/jniLibs:** +- Purpose: Native library files for different CPU architectures +- Contains: `.so` files for arm64-v8a, armeabi-v7a +- Generated: No (from ArcSoft SDK) +- Committed: Yes + +## Key File Locations + +**Entry Points:** +- `lib/arc.dart`: Public API entry point for plugin consumers +- `android/src/main/java/com/xiarui/arc/ArcPlugin.java`: Android plugin registration and method handling +- `example/lib/main.dart`: Example app entry point + +**Configuration:** +- `pubspec.yaml`: Flutter package configuration, SDK version constraints +- `android/build.gradle`: Android library build configuration, SDK dependencies +- `android/src/main/AndroidManifest.xml`: Android permissions (CAMERA, INTERNET, STORAGE) + +**Core Logic:** +- `lib/arc_platform_interface.dart`: Platform interface abstract class +- `lib/arc_method_channel.dart`: Method channel implementation +- `android/src/main/java/com/xiarui/arc/FaceEngineManager.java`: Singleton managing FaceEngine lifecycle + +**Data Models:** +- `android/src/main/java/com/xiarui/arc/FaceInfo.java`: Face detection result model +- `android/src/main/java/com/xiarui/arc/FaceErrorCode.java`: Error code enumeration (586 lines) + +**Testing:** +- `test/arc_test.dart`: Mock platform tests +- `test/arc_method_channel_test.dart`: Method channel unit tests +- `android/src/test/java/com/xiarui/arc/ArcPluginTest.java`: Android unit tests + +## Naming Conventions + +**Files:** +- Dart: lowercase with underscores, e.g., `arc_platform_interface.dart` +- Java: PascalCase for classes, e.g., `ArcPlugin.java`, `FaceEngineManager.java` + +**Classes:** +- Dart: PascalCase, e.g., `Arc`, `ArcPlatform`, `MethodChannelArc` +- Java: PascalCase, e.g., `FaceEngineManager`, `FaceErrorCode` + +**Methods:** +- Dart: camelCase with descriptive names, e.g., `activeOnline`, `detectFaces`, `extractFaceFeature` +- Java: camelCase with handler prefix for dispatch, e.g., `handleActiveOnline`, `handleDetectFaces` + +**Variables:** +- Dart: camelCase for local/instance variables +- Java: camelCase, enum constants use UPPER_SNAKE_CASE, e.g., `MERR_ASF_ACTIVATION_FAIL` + +## Where to Add New Code + +**New Feature (Dart API):** +- Primary code: `lib/arc.dart` - add public method +- Interface: `lib/arc_platform_interface.dart` - add abstract method +- Implementation: `lib/arc_method_channel.dart` - add method channel invocation + +**New Feature (Android Native):** +- Handler: `android/src/main/java/com/xiarui/arc/ArcPlugin.java` - add case in `onMethodCall()` switch +- Logic: `android/src/main/java/com/xiarui/arc/FaceEngineManager.java` - add SDK operation method + +**New Platform Support (iOS/Web):** +- Create platform-specific implementation in appropriate directory +- Register in `pubspec.yaml` under `plugin.platforms` + +**New Data Model:** +- Location: `android/src/main/java/com/xiarui/arc/` alongside existing models +- Pattern: JavaBean style with getters/setters and `toMap()` method + +**Tests:** +- Dart tests: `test/` directory, named `*_test.dart` +- Android tests: `android/src/test/java/com/xiarui/arc/` directory + +**Utilities:** +- Shared helpers: `android/src/main/java/com/xiarui/arc/` as utility classes + +## Special Directories + +**android/libs:** +- Purpose: ArcSoft SDK JAR files (third-party) +- Generated: No +- Committed: Yes (required for build) +- Note: Contains `arcsoft_face.jar`, `arcsoft_image_util.jar` + +**android/src/main/jniLibs:** +- Purpose: Native `.so` libraries for CPU architectures +- Generated: No (from ArcSoft SDK distribution) +- Committed: Yes +- Architectures: arm64-v8a, armeabi-v7a + +**example/build:** +- Purpose: Flutter/Gradle build artifacts +- Generated: Yes +- Committed: No (should be in .gitignore) + +**.dart_tool:** +- Purpose: Dart package config and tool cache +- Generated: Yes +- Committed: No + +**.planning:** +- Purpose: GSD planning documents +- Generated: Yes (by GSD tools) +- Committed: Yes (for team visibility) + +## Package Structure + +**Flutter Package:** +- Package name: `arc` +- Version: 0.0.1 +- SDK constraint: `^3.9.0` +- Flutter constraint: `>=3.3.0` +- Platforms: Android only (current) + +**Android Package:** +- Package name: `com.xiarui.arc` +- Plugin class: `ArcPlugin` +- Min SDK: 24 (Android 7.0) +- Compile SDK: 36 +- Java version: 11 + +--- + +*Structure analysis: 2026-03-30* \ No newline at end of file diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md new file mode 100644 index 0000000..d8714f8 --- /dev/null +++ b/.planning/codebase/TESTING.md @@ -0,0 +1,284 @@ +# Testing Patterns + +**Analysis Date:** 2026-03-30 + +## Test Framework + +**Runner:** +- Flutter Test Framework (flutter_test SDK) +- Dart SDK: ^3.9.0 +- Config: `analysis_options.yaml` (linting only, no test config file) + +**Assertion Library:** +- flutter_test package: `expect()`, `findsOneWidget`, `isInstanceOf()` + +**Run Commands:** +```bash +flutter test # Run all tests +flutter test test/arc_test.dart # Run specific test file +flutter test --coverage # Run with coverage +``` + +## Test File Organization + +**Location:** +- Plugin tests: `test/` directory (co-located with lib) +- Example tests: `example/test/` directory +- Java tests: `android/src/test/java/` + +**Naming:** +- Dart: `*_test.dart` pattern (`arc_test.dart`, `arc_method_channel_test.dart`, `widget_test.dart`) +- Java: `*Test.java` pattern (`ArcPluginTest.java`) + +**Structure:** +``` +arc/ +├── test/ +│ ├── arc_test.dart # Platform interface tests +│ └── arc_method_channel_test.dart # Method channel tests +├── example/ +│ └── test/ +│ └── widget_test.dart # Widget tests +└── android/ + └── src/ + └── test/ + └── java/ + └── com/xiarui/arc/ + └── ArcPluginTest.java +``` + +## Test Structure + +**Dart Unit Tests:** +```dart +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final ArcPlatform initialPlatform = ArcPlatform.instance; + + test('$MethodChannelArc is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + Arc arcPlugin = Arc(); + MockArcPlatform fakePlatform = MockArcPlatform(); + ArcPlatform.instance = fakePlatform; + + expect(await arcPlugin.getPlatformVersion(), '42'); + }); +} +``` + +**Java Unit Tests:** +```java +public class ArcPluginTest { + @Test + public void onMethodCall_getPlatformVersion_returnsExpectedValue() { + ArcPlugin plugin = new ArcPlugin(); + + final MethodCall call = new MethodCall("getPlatformVersion", null); + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + plugin.onMethodCall(call, mockResult); + + verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE); + } +} +``` + +## Mocking + +**Dart Mocking:** +- `MockPlatformInterfaceMixin` from plugin_platform_interface +- Custom mock class implementing platform interface + +**Example:** +```dart +class MockArcPlatform + with MockPlatformInterfaceMixin + implements ArcPlatform { + + @override + Future getPlatformVersion() => Future.value('42'); + + @override + Future?> activeOnline({ + required String appId, + required String sdkKey, + required String activeKey, + }) => Future.value({'success': true, 'errorCode': 0, 'message': 'success'}); +} +``` + +**Method Channel Mocking:** +```dart +setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); +}); + +tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); +}); +``` + +**Java Mocking:** +- Mockito framework +- `mock(Class.class)` for mock objects +- `verify(mock).method(args)` for verification + +**What to Mock:** +- Platform interfaces for unit testing +- Method channels for integration testing +- Native engine calls + +**What NOT to Mock:** +- Data transformation logic (test directly) +- Simple utility functions + +## Fixtures and Factories + +**Test Data:** +- Mock return values defined inline +- Simple string/int values: `'42'`, `0`, `true` + +**Example Mock Data:** +```dart +@override +Future?> detectFaces({ + required Uint8List data, + required int width, + required int height, + int format = 2050, +}) => Future.value({ + 'success': true, + 'errorCode': 0, + 'faceList': [], + 'rgbLiveness': 1, + 'isRgbAlive': true +}); +``` + +**Location:** +- Mock implementations in test files +- No separate fixture files + +## Coverage + +**Requirements:** None enforced + +**View Coverage:** +```bash +flutter test --coverage +genhtml coverage/lcov.info -o coverage/html +``` + +## Test Types + +**Unit Tests:** +- Platform interface instantiation +- Method channel default instance verification +- Mock platform behavior testing +- File: `test/arc_test.dart`, `test/arc_method_channel_test.dart` + +**Integration Tests:** +- Method channel handler testing +- Native plugin method call verification +- File: `android/src/test/java/com/xiarui/arc/ArcPluginTest.java` + +**Widget Tests:** +- Basic widget rendering verification +- File: `example/test/widget_test.dart` + +**E2E Tests:** Not used + +## Common Patterns + +**Async Testing:** +```dart +test('getPlatformVersion', () async { + expect(await arcPlugin.getPlatformVersion(), '42'); +}); +``` + +**Platform Instance Mocking:** +```dart +test('methodName', () async { + Arc arcPlugin = Arc(); + MockArcPlatform fakePlatform = MockArcPlatform(); + ArcPlatform.instance = fakePlatform; // Override instance + + expect(await arcPlugin.method(), expectedResult); +}); +``` + +**Setup/Teardown:** +```dart +setUp(() { + // Initialize test binding, set up mocks + TestWidgetsFlutterBinding.ensureInitialized(); +}); + +tearDown(() { + // Clean up mocks + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); +}); +``` + +**Error Testing:** +Current tests do not explicitly test error scenarios. Recommended pattern for future: +```dart +test('throws on invalid params', () async { + expect( + () => arcPlugin.detectFaces(data: Uint8List(0), width: 0, height: 0), + throwsA(isA()), + ); +}); +``` + +## Test Naming Convention + +**Dart:** +- Simple descriptive names: `'getPlatformVersion'` +- Interpolated class names: `'$MethodChannelArc is the default instance'` + +**Java:** +- Pattern: `methodName_scenario_expectedResult` +- Example: `onMethodCall_getPlatformVersion_returnsExpectedValue` + +## Key Test Files + +**Plugin Core Tests:** +- `test/arc_test.dart` - Platform interface and Arc class tests +- `test/arc_method_channel_test.dart` - Method channel implementation tests + +**Example App Tests:** +- `example/test/widget_test.dart` - Basic widget test (template, needs updating) + +**Native Tests:** +- `android/src/test/java/com/xiarui/arc/ArcPluginTest.java` - Android plugin tests + +## Test Coverage Gaps + +**Untested Methods:** +- `activeOnline()` - No test coverage +- `init()` - No test coverage +- `detectFaces()` - No test coverage +- `extractFaceFeature()` - No test coverage +- `compareFaceFeature()` - No test coverage +- `registerFaceFeature()` - No test coverage +- `registerFaceFeatureBatch()` - No test coverage + +**Untested Scenarios:** +- Error handling (invalid parameters) +- Edge cases (null data, empty arrays) +- RGB liveness detection flow +- Face feature extraction and comparison + +--- + +*Testing analysis: 2026-03-30* \ No newline at end of file