AutoCheck.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package com.makeit.callservice.tts.util;
  2. import android.Manifest;
  3. import android.content.Context;
  4. import android.content.pm.PackageManager;
  5. import android.os.Handler;
  6. import android.os.Message;
  7. import androidx.core.content.ContextCompat;
  8. import com.baidu.tts.client.SpeechSynthesizer;
  9. import com.baidu.tts.client.TtsMode;
  10. import com.makeit.callservice.tts.control.InitConfig;
  11. import org.json.JSONObject;
  12. import java.io.BufferedReader;
  13. import java.io.File;
  14. import java.io.InputStream;
  15. import java.io.InputStreamReader;
  16. import java.net.HttpURLConnection;
  17. import java.net.URL;
  18. import java.net.UnknownHostException;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.LinkedHashMap;
  22. import java.util.Map;
  23. import java.util.TreeSet;
  24. /**
  25. * Created by fujiayi on 2017/12/28.
  26. */
  27. /**
  28. * 自动排查工具,用于集成后发现错误。
  29. * <p>
  30. * 可以检测如下错误:
  31. * 1. PermissionCheck : AndroidManifest,xml 需要的部分权限
  32. * 2. JniCheck: 检测so文件是否安装在指定目录
  33. * 3. AppInfoCheck: 联网情况下 , 检测appId appKey secretKey是否正确
  34. * 4. ApplicationIdCheck: 显示包名applicationId, 提示用户手动去官网检查
  35. * 5. ParamKeyExistCheck: 检查key是否存在,目前检查 SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE
  36. * 和PARAM_TTS_SPEECH_MODEL_FILE
  37. * 6. OfflineResourceFileCheck 检查离线资源文件(需要从assets目录下复制),是否存在
  38. * <p>
  39. * <p>
  40. * 示例使用代码:
  41. * AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
  42. *
  43. * @Override public void handleMessage(Message msg) {
  44. * if (msg.what == 100) {
  45. * AutoCheck autoCheck = (AutoCheck) msg.obj;
  46. * synchronized (autoCheck) {
  47. * String message = autoCheck.obtainDebugMessage();
  48. * toPrint(message); // 可以用下面一行替代,在logcat中查看代码
  49. * //Log.w("AutoCheckMessage",message);
  50. * }
  51. * }
  52. * }
  53. * <p>
  54. * });
  55. */
  56. public class AutoCheck {
  57. private static AutoCheck instance;
  58. private LinkedHashMap<String, Check> checks;
  59. private static Context context;
  60. private boolean hasError = false;
  61. volatile boolean isFinished = false;
  62. /**
  63. * 获取实例,非线程安全
  64. *
  65. * @return
  66. */
  67. public static AutoCheck getInstance(Context context) {
  68. if (instance == null || AutoCheck.context != context) {
  69. instance = new AutoCheck(context);
  70. }
  71. return instance;
  72. }
  73. public void check(final InitConfig initConfig, final Handler handler) {
  74. Thread t = new Thread(new Runnable() {
  75. @Override
  76. public void run() {
  77. AutoCheck obj = innerCheck(initConfig);
  78. isFinished = true;
  79. synchronized (obj) { // 偶发,同步线程信息
  80. Message msg = handler.obtainMessage(100, obj);
  81. handler.sendMessage(msg);
  82. }
  83. }
  84. });
  85. t.start();
  86. }
  87. private AutoCheck innerCheck(InitConfig config) {
  88. checks.put("检查申请的Android权限", new PermissionCheck(context));
  89. checks.put("检查4个so文件是否存在", new JniCheck(context));
  90. checks.put("检查AppId AppKey SecretKey",
  91. new AppInfoCheck(config.getAppId(), config.getAppKey(), config.getSecretKey()));
  92. checks.put("检查包名", new ApplicationIdCheck(context, config.getAppId()));
  93. if (TtsMode.MIX.equals(config.getTtsMode())) {
  94. Map<String, String> params = config.getParams();
  95. String fileKey = SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE;
  96. checks.put("检查离线资TEXT文件参数", new ParamKeyExistCheck(params, fileKey,
  97. "SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE未设置 ,"));
  98. checks.put("检查离线资源TEXT文件", new OfflineResourceFileCheck(params.get(fileKey)));
  99. fileKey = SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE;
  100. checks.put("检查离线资Speech文件参数", new ParamKeyExistCheck(params, fileKey,
  101. "SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE未设置 ,"));
  102. checks.put("检查离线资源Speech文件", new OfflineResourceFileCheck(params.get(fileKey)));
  103. }
  104. for (Map.Entry<String, Check> e : checks.entrySet()) {
  105. Check check = e.getValue();
  106. check.check();
  107. if (check.hasError()) {
  108. break;
  109. }
  110. }
  111. return this;
  112. }
  113. public String obtainErrorMessage() {
  114. PrintConfig config = new PrintConfig();
  115. return formatString(config);
  116. }
  117. public String obtainDebugMessage() {
  118. PrintConfig config = new PrintConfig();
  119. config.withInfo = true;
  120. return formatString(config);
  121. }
  122. public String obtainAllMessage() {
  123. PrintConfig config = new PrintConfig();
  124. config.withLog = true;
  125. config.withInfo = true;
  126. return formatString(config);
  127. }
  128. public String formatString(PrintConfig config) {
  129. StringBuilder sb = new StringBuilder();
  130. hasError = false;
  131. for (HashMap.Entry<String, Check> entry : checks.entrySet()) {
  132. Check check = entry.getValue();
  133. String testName = entry.getKey();
  134. if (check.hasError()) {
  135. if (!hasError) {
  136. hasError = true;
  137. }
  138. sb.append("【错误】【").append(testName).append(" 】 ").append(check.getErrorMessage()).append("\n");
  139. if (check.hasFix()) {
  140. sb.append("【修复方法】【").append(testName).append(" 】 ").append(check.getFixMessage()).append("\n");
  141. }
  142. }
  143. if (config.withInfo && check.hasInfo()) {
  144. sb.append("【请手动检查】【").append(testName).append("】 ").append(check.getInfoMessage()).append("\n");
  145. }
  146. if (config.withLog && (config.withLogOnSuccess || hasError) && check.hasLog()) {
  147. sb.append("【log】:" + check.getLogMessage()).append("\n");
  148. }
  149. }
  150. if (!hasError) {
  151. sb.append("集成自动排查工具: 恭喜没有检测到任何问题\n");
  152. }
  153. return sb.toString();
  154. }
  155. public void clear() {
  156. checks.clear();
  157. hasError = false;
  158. }
  159. private AutoCheck(Context context) {
  160. this.context = context;
  161. checks = new LinkedHashMap<String, Check>();
  162. }
  163. private static class PrintConfig {
  164. public boolean withFix = true;
  165. public boolean withInfo = false;
  166. public boolean withLog = false;
  167. public boolean withLogOnSuccess = false;
  168. }
  169. private static class PermissionCheck extends Check {
  170. private Context context;
  171. public PermissionCheck(Context context) {
  172. this.context = context;
  173. }
  174. @Override
  175. public void check() {
  176. String[] permissions = {
  177. Manifest.permission.INTERNET,
  178. Manifest.permission.ACCESS_NETWORK_STATE,
  179. Manifest.permission.MODIFY_AUDIO_SETTINGS,
  180. // Manifest.permission.WRITE_EXTERNAL_STORAGE,
  181. // Manifest.permission.WRITE_SETTINGS,
  182. Manifest.permission.READ_PHONE_STATE,
  183. Manifest.permission.ACCESS_WIFI_STATE,
  184. // Manifest.permission.CHANGE_WIFI_STATE
  185. };
  186. ArrayList<String> toApplyList = new ArrayList<String>();
  187. for (String perm : permissions) {
  188. if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(context, perm)) {
  189. toApplyList.add(perm);
  190. // 进入到这里代表没有权限.
  191. }
  192. }
  193. if (!toApplyList.isEmpty()) {
  194. errorMessage = "缺少权限:" + toApplyList;
  195. fixMessage = "请从AndroidManifest.xml复制相关权限";
  196. }
  197. }
  198. }
  199. private static class JniCheck extends Check {
  200. private Context context;
  201. private String[] soNames;
  202. public JniCheck(Context context) {
  203. this.context = context;
  204. soNames = new String[]{"libbd_etts.so", "libBDSpeechDecoder_V1.so", "libgnustl_shared.so"};
  205. }
  206. @Override
  207. public void check() {
  208. String path = context.getApplicationInfo().nativeLibraryDir;
  209. appendLogMessage("Jni so文件目录 " + path);
  210. File[] files = new File(path).listFiles();
  211. TreeSet<String> set = new TreeSet<>();
  212. if (files != null) {
  213. for (File file : files) {
  214. if (file.canRead()) {
  215. set.add(file.getName());
  216. }
  217. }
  218. }
  219. appendLogMessage("Jni目录内文件: " + set.toString());
  220. for (String name : soNames) {
  221. if (!set.contains(name)) {
  222. errorMessage = "Jni目录" + path + " 缺少可读的so文件:" + name + ", 该目录文件列表: " + set.toString();
  223. fixMessage = "如果您的app内没有其它so文件,请复制demo里的src/main/jniLibs至同名目录。"
  224. + " 如果app内有so文件,请合并目录放一起(注意目录取交集,多余的目录删除)。";
  225. break;
  226. }
  227. }
  228. }
  229. }
  230. private static class ParamKeyExistCheck extends Check {
  231. private Map<String, String> params;
  232. private String key;
  233. private String prefixErrorMessage;
  234. public ParamKeyExistCheck(Map<String, String> params, String key, String prefixErrorMessage) {
  235. this.params = params;
  236. this.key = key;
  237. this.prefixErrorMessage = prefixErrorMessage;
  238. }
  239. @Override
  240. public void check() {
  241. if (params == null || !params.containsKey(key)) {
  242. errorMessage = prefixErrorMessage + " 参数中没有设置:" + key;
  243. fixMessage = "请参照demo在设置 " + key + "参数";
  244. }
  245. }
  246. }
  247. private static class OfflineResourceFileCheck extends Check {
  248. private String filename;
  249. private String nullMessage;
  250. public OfflineResourceFileCheck(String filename) {
  251. this.filename = filename;
  252. this.nullMessage = nullMessage;
  253. }
  254. @Override
  255. public void check() {
  256. File file = new File(filename);
  257. boolean isSuccess = true;
  258. if (!file.exists()) {
  259. errorMessage = "资源文件不存在:" + filename;
  260. isSuccess = false;
  261. } else if (!file.canRead()) {
  262. errorMessage = "资源文件不可读:" + filename;
  263. isSuccess = false;
  264. }
  265. if (!isSuccess) {
  266. fixMessage = "请将demo中src/main/assets目录下同名文件复制到 " + filename;
  267. }
  268. }
  269. }
  270. private static class ApplicationIdCheck extends Check {
  271. private String appId;
  272. private Context context;
  273. public ApplicationIdCheck(Context context, String appId) {
  274. this.appId = appId;
  275. this.context = context;
  276. }
  277. @Override
  278. public void check() {
  279. infoMessage = "如果您集成过程中遇见离线合成初始化问题,请检查网页上appId:" + appId
  280. + " 应用是否开通了合成服务,并且网页上的应用填写了Android包名:"
  281. + getApplicationId();
  282. }
  283. private String getApplicationId() {
  284. return context.getPackageName();
  285. }
  286. }
  287. private static class AppInfoCheck extends Check {
  288. private String appId;
  289. private String appKey;
  290. private String secretKey;
  291. public AppInfoCheck(String appId, String appKey, String secretKey) {
  292. this.appId = appId;
  293. this.appKey = appKey;
  294. this.secretKey = secretKey;
  295. }
  296. @Override
  297. public void check() {
  298. do {
  299. appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey);
  300. if (appId == null || appId.isEmpty()) {
  301. errorMessage = "appId 为空";
  302. fixMessage = "填写appID";
  303. break;
  304. }
  305. if (appKey == null || appKey.isEmpty()) {
  306. errorMessage = "appKey 为空";
  307. fixMessage = "填写appID";
  308. break;
  309. }
  310. if (secretKey == null || secretKey.isEmpty()) {
  311. errorMessage = "secretKey 为空";
  312. fixMessage = "secretKey";
  313. break;
  314. }
  315. } while (false);
  316. try {
  317. checkOnline();
  318. } catch (UnknownHostException e) {
  319. infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage();
  320. } catch (Exception e) {
  321. errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage();
  322. fixMessage = " 重新检测appId, appKey, appSecret是否正确";
  323. }
  324. }
  325. public void checkOnline() throws Exception {
  326. String urlpath = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id="
  327. + appKey + "&client_secret=" + secretKey;
  328. URL url = new URL(urlpath);
  329. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  330. conn.setRequestMethod("GET");
  331. conn.setConnectTimeout(1000);
  332. InputStream is = conn.getInputStream();
  333. BufferedReader reader = new BufferedReader(new InputStreamReader(is));
  334. StringBuilder result = new StringBuilder();
  335. String line = "";
  336. do {
  337. line = reader.readLine();
  338. if (line != null) {
  339. result.append(line);
  340. }
  341. } while (line != null);
  342. String res = result.toString();
  343. appendLogMessage("openapi return " + res);
  344. JSONObject jsonObject = new JSONObject(res);
  345. String error = jsonObject.optString("error");
  346. if (error != null && !error.isEmpty()) {
  347. throw new Exception("appkey secretKey 错误" + ", error:" + error + ", json is" + result);
  348. }
  349. String token = jsonObject.getString("access_token");
  350. if (token == null || !token.endsWith("-" + appId)) {
  351. throw new Exception("appId 与 appkey及 appSecret 不一致。appId = " + appId + " ,token = " + token);
  352. }
  353. }
  354. }
  355. private abstract static class Check {
  356. protected String errorMessage = null;
  357. protected String fixMessage = null;
  358. protected String infoMessage = null;
  359. protected StringBuilder logMessage;
  360. public Check() {
  361. logMessage = new StringBuilder();
  362. }
  363. public abstract void check();
  364. public boolean hasError() {
  365. return errorMessage != null;
  366. }
  367. public boolean hasFix() {
  368. return fixMessage != null;
  369. }
  370. public boolean hasInfo() {
  371. return infoMessage != null;
  372. }
  373. public boolean hasLog() {
  374. return !logMessage.toString().isEmpty();
  375. }
  376. public void appendLogMessage(String message) {
  377. logMessage.append(message + "\n");
  378. }
  379. public String getErrorMessage() {
  380. return errorMessage;
  381. }
  382. public String getFixMessage() {
  383. return fixMessage;
  384. }
  385. public String getInfoMessage() {
  386. return infoMessage;
  387. }
  388. public String getLogMessage() {
  389. return logMessage.toString();
  390. }
  391. }
  392. }