/* * 自然写互动课堂应用开发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; } }