Files
system-design/software-copyright/11-writech-sdk/android/WritechSDK.java
T
2026-03-22 15:24:40 +08:00

376 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 自然写互动课堂应用开发SDK软件 V1.0
* WritechSDK - SDK初始化与鉴权入口
*
* 功能说明:
* 1. SDK全局初始化(配置加载、模块注册)
* 2. 应用鉴权(AppKey/AppSecret验证)
* 3. 各子模块生命周期管理
* 4. 全局配置管理(服务器地址、超时、日志级别)
* 5. SDK版本信息与功能授权查询
*/
package com.writech.sdk.android;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 自然写SDK主入口类
* 使用前必须先调用 init() 方法进行初始化和鉴权
*
* 典型使用流程:
* 1. WritechSDK.init(context, config)
* 2. WritechSDK.getInstance().getPenManager().startScan()
* 3. WritechSDK.getInstance().getOCREngine().recognizeHandwriting(...)
*/
public class WritechSDK {
private static final String TAG = "WritechSDK";
/* SDK版本号 */
public static final String SDK_VERSION = "1.0.0";
/* SDK构建号 */
public static final int SDK_BUILD = 100;
/* 单例实例 */
private static volatile WritechSDK sInstance;
/* 是否已初始化 */
private static final AtomicBoolean sInitialized = new AtomicBoolean(false);
/* ========== 配置类 ========== */
/** SDK初始化配置 */
public static class Config {
/** 云平台API地址 */
public String cloudBaseUrl = "https://api.writech.com";
/** SDK应用标识(从自然写开放平台获取) */
public String appKey;
/** SDK应用密钥 */
public String appSecret;
/** 离线OCR模型文件路径(可选) */
public String offlineModelPath;
/** 是否启用调试日志 */
public boolean debugMode = false;
/** 笔迹数据本地缓存目录 */
public String cacheDir;
/** BLE扫描超时时间(毫秒) */
public int bleScanTimeout = 30000;
/** 网关自动发现 */
public boolean autoDiscoverGateway = true;
/** 最大同时连接笔数 */
public int maxPenConnections = 60;
}
/* ========== 成员变量 ========== */
private Context mContext;
private Config mConfig;
/* 各子模块实例 */
private PenManager mPenManager;
private StrokeCanvas mDefaultCanvas;
private OCREngine mOCREngine;
private GatewaySDK mGatewaySDK;
private CloudClient mCloudClient;
/* 鉴权状态 */
private boolean mIsAuthenticated = false;
private String mLicenseType; /* 授权类型: trial/standard/enterprise */
private long mLicenseExpireTime; /* 授权到期时间 */
/* 本地存储 */
private SharedPreferences mPrefs;
/* ========== 初始化入口 ========== */
/**
* 初始化SDK(必须在使用任何功能前调用)
*
* @param context Android上下文(Application级别)
* @param config SDK配置
* @return 初始化结果:true成功,false失败
*/
public static boolean init(Context context, Config config) {
if (sInitialized.getAndSet(true)) {
Log.w(TAG, "SDK已初始化,忽略重复调用");
return true;
}
if (context == null || config == null) {
Log.e(TAG, "初始化失败:context或config为null");
sInitialized.set(false);
return false;
}
if (config.appKey == null || config.appSecret == null) {
Log.e(TAG, "初始化失败:appKey或appSecret未配置");
sInitialized.set(false);
return false;
}
sInstance = new WritechSDK();
boolean success = sInstance.doInit(context, config);
if (!success) {
sInstance = null;
sInitialized.set(false);
}
return success;
}
/** 获取SDK单例 */
public static WritechSDK getInstance() {
if (sInstance == null) {
throw new IllegalStateException("WritechSDK未初始化,请先调用 WritechSDK.init()");
}
return sInstance;
}
/** 检查SDK是否已初始化 */
public static boolean isInitialized() {
return sInitialized.get();
}
/* ========== 内部初始化流程 ========== */
/** 执行具体的初始化逻辑 */
private boolean doInit(Context context, Config config) {
mContext = context.getApplicationContext();
mConfig = config;
mPrefs = mContext.getSharedPreferences("writech_sdk", Context.MODE_PRIVATE);
Log.i(TAG, "=== 自然写SDK V" + SDK_VERSION + " 初始化开始 ===");
Log.i(TAG, "云平台地址: " + config.cloudBaseUrl);
Log.i(TAG, "AppKey: " + config.appKey.substring(0, 8) + "****");
Log.i(TAG, "调试模式: " + config.debugMode);
/* 步骤1:应用鉴权(验证AppKey和AppSecret */
if (!authenticate(config.appKey, config.appSecret)) {
Log.e(TAG, "SDK鉴权失败,请检查AppKey和AppSecret");
return false;
}
/* 步骤2:初始化云平台客户端 */
mCloudClient = new CloudClient(config.cloudBaseUrl, config.appKey, config.appSecret);
/* 恢复本地缓存的令牌 */
restoreTokens();
/* 步骤3:初始化蓝牙笔管理器 */
mPenManager = new PenManager(mContext);
/* 步骤4:初始化OCR引擎 */
mOCREngine = new OCREngine(mContext, config.cloudBaseUrl, null);
if (config.offlineModelPath != null) {
mOCREngine.loadOfflineModel(config.offlineModelPath);
}
/* 步骤5:初始化网关SDK */
mGatewaySDK = new GatewaySDK(mContext);
if (config.autoDiscoverGateway) {
mGatewaySDK.startDiscovery();
}
Log.i(TAG, "=== 自然写SDK初始化完成 ===");
return true;
}
/* ========== 应用鉴权 ========== */
/**
* 验证AppKey和AppSecret的有效性
* 首次验证需要联网,之后缓存鉴权结果
*/
private boolean authenticate(String appKey, String appSecret) {
/* 检查本地缓存的鉴权结果 */
String cachedLicense = mPrefs.getString("license_type", null);
long cachedExpire = mPrefs.getLong("license_expire", 0);
if (cachedLicense != null && cachedExpire > System.currentTimeMillis()) {
mIsAuthenticated = true;
mLicenseType = cachedLicense;
mLicenseExpireTime = cachedExpire;
Log.i(TAG, "使用缓存鉴权结果: " + mLicenseType
+ ",到期: " + new java.util.Date(mLicenseExpireTime));
return true;
}
/* 在线鉴权 */
try {
String authUrl = mConfig.cloudBaseUrl + "/api/v1/sdk/authenticate";
String body = "{\"appKey\":\"" + appKey
+ "\",\"appSecret\":\"" + appSecret
+ "\",\"sdkVersion\":\"" + SDK_VERSION + "\"}";
/* 使用CloudClient的静态方法发送无认证请求 */
java.net.HttpURLConnection conn =
(java.net.HttpURLConnection) new java.net.URL(authUrl).openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(10000);
conn.getOutputStream().write(body.getBytes(java.nio.charset.StandardCharsets.UTF_8));
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
java.io.InputStream is = conn.getInputStream();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
String response = baos.toString("UTF-8");
is.close();
conn.disconnect();
/* 解析鉴权结果 */
mLicenseType = extractJsonField(response, "licenseType");
String expireStr = extractJsonField(response, "expireTime");
if (mLicenseType != null) {
mLicenseExpireTime = expireStr != null ? Long.parseLong(expireStr)
: System.currentTimeMillis() + 365L * 24 * 3600 * 1000;
mIsAuthenticated = true;
/* 缓存鉴权结果 */
mPrefs.edit()
.putString("license_type", mLicenseType)
.putLong("license_expire", mLicenseExpireTime)
.apply();
Log.i(TAG, "在线鉴权成功: " + mLicenseType);
return true;
}
}
conn.disconnect();
} catch (Exception e) {
Log.w(TAG, "在线鉴权异常: " + e.getMessage());
/* 联网失败时允许离线试用(7天) */
mLicenseType = "trial";
mLicenseExpireTime = System.currentTimeMillis() + 7L * 24 * 3600 * 1000;
mIsAuthenticated = true;
Log.i(TAG, "离线模式,试用授权7天");
return true;
}
return false;
}
/** 恢复本地缓存的认证令牌 */
private void restoreTokens() {
String accessToken = mPrefs.getString("access_token", null);
String refreshToken = mPrefs.getString("refresh_token", null);
long expireTime = mPrefs.getLong("token_expire", 0);
if (accessToken != null && refreshToken != null) {
mCloudClient.setTokens(accessToken, refreshToken, expireTime);
Log.d(TAG, "已恢复缓存的认证令牌");
}
}
/* ========== 对外接口 ========== */
/** 获取笔管理器 */
public PenManager getPenManager() {
return mPenManager;
}
/** 获取OCR引擎 */
public OCREngine getOCREngine() {
return mOCREngine;
}
/** 获取网关SDK */
public GatewaySDK getGatewaySDK() {
return mGatewaySDK;
}
/** 获取云平台客户端 */
public CloudClient getCloudClient() {
return mCloudClient;
}
/** 获取SDK版本 */
public String getVersion() {
return SDK_VERSION;
}
/** 获取授权类型 */
public String getLicenseType() {
return mLicenseType;
}
/** 检查是否已鉴权 */
public boolean isAuthenticated() {
return mIsAuthenticated;
}
/** 用户登录(通过云平台认证) */
public boolean loginUser(String username, String password) {
try {
String response = mCloudClient.login(username, password);
String accessToken = extractJsonField(response, "accessToken");
String refreshToken = extractJsonField(response, "refreshToken");
if (accessToken != null) {
long expireTime = System.currentTimeMillis() + 30 * 60 * 1000;
mCloudClient.setTokens(accessToken, refreshToken, expireTime);
/* 缓存令牌 */
mPrefs.edit()
.putString("access_token", accessToken)
.putString("refresh_token", refreshToken)
.putLong("token_expire", expireTime)
.apply();
return true;
}
} catch (IOException e) {
Log.e(TAG, "登录失败: " + e.getMessage());
}
return false;
}
/* ========== 资源释放 ========== */
/** 释放SDK所有资源 */
public static void destroy() {
if (sInstance != null) {
if (sInstance.mGatewaySDK != null) sInstance.mGatewaySDK.destroy();
if (sInstance.mOCREngine != null) sInstance.mOCREngine.destroy();
if (sInstance.mPenManager != null) sInstance.mPenManager.destroy();
sInstance = null;
}
sInitialized.set(false);
Log.i(TAG, "WritechSDK已释放所有资源");
}
/** 从JSON提取字段值 */
private String extractJsonField(String json, String key) {
if (json == null) return null;
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) return null;
int start = json.indexOf("\"", idx + search.length() + 1) + 1;
int end = json.indexOf("\"", start);
return (start > 0 && end > start) ? json.substring(start, end) : null;
}
}