package com.makeit.callservice.tts.util; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Message; import androidx.core.content.ContextCompat; import com.baidu.tts.client.SpeechSynthesizer; import com.baidu.tts.client.TtsMode; import com.makeit.callservice.tts.control.InitConfig; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeSet; /** * Created by fujiayi on 2017/12/28. */ /** * 自动排查工具,用于集成后发现错误。 *

* 可以检测如下错误: * 1. PermissionCheck : AndroidManifest,xml 需要的部分权限 * 2. JniCheck: 检测so文件是否安装在指定目录 * 3. AppInfoCheck: 联网情况下 , 检测appId appKey secretKey是否正确 * 4. ApplicationIdCheck: 显示包名applicationId, 提示用户手动去官网检查 * 5. ParamKeyExistCheck: 检查key是否存在,目前检查 SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE * 和PARAM_TTS_SPEECH_MODEL_FILE * 6. OfflineResourceFileCheck 检查离线资源文件(需要从assets目录下复制),是否存在 *

*

* 示例使用代码: * AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() { * * @Override public void handleMessage(Message msg) { * if (msg.what == 100) { * AutoCheck autoCheck = (AutoCheck) msg.obj; * synchronized (autoCheck) { * String message = autoCheck.obtainDebugMessage(); * toPrint(message); // 可以用下面一行替代,在logcat中查看代码 * //Log.w("AutoCheckMessage",message); * } * } * } *

* }); */ public class AutoCheck { private static AutoCheck instance; private LinkedHashMap checks; private static Context context; private boolean hasError = false; volatile boolean isFinished = false; /** * 获取实例,非线程安全 * * @return */ public static AutoCheck getInstance(Context context) { if (instance == null || AutoCheck.context != context) { instance = new AutoCheck(context); } return instance; } public void check(final InitConfig initConfig, final Handler handler) { Thread t = new Thread(new Runnable() { @Override public void run() { AutoCheck obj = innerCheck(initConfig); isFinished = true; synchronized (obj) { // 偶发,同步线程信息 Message msg = handler.obtainMessage(100, obj); handler.sendMessage(msg); } } }); t.start(); } private AutoCheck innerCheck(InitConfig config) { checks.put("检查申请的Android权限", new PermissionCheck(context)); checks.put("检查4个so文件是否存在", new JniCheck(context)); checks.put("检查AppId AppKey SecretKey", new AppInfoCheck(config.getAppId(), config.getAppKey(), config.getSecretKey())); checks.put("检查包名", new ApplicationIdCheck(context, config.getAppId())); if (TtsMode.MIX.equals(config.getTtsMode())) { Map params = config.getParams(); String fileKey = SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE; checks.put("检查离线资TEXT文件参数", new ParamKeyExistCheck(params, fileKey, "SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE未设置 ,")); checks.put("检查离线资源TEXT文件", new OfflineResourceFileCheck(params.get(fileKey))); fileKey = SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE; checks.put("检查离线资Speech文件参数", new ParamKeyExistCheck(params, fileKey, "SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE未设置 ,")); checks.put("检查离线资源Speech文件", new OfflineResourceFileCheck(params.get(fileKey))); } for (Map.Entry e : checks.entrySet()) { Check check = e.getValue(); check.check(); if (check.hasError()) { break; } } return this; } public String obtainErrorMessage() { PrintConfig config = new PrintConfig(); return formatString(config); } public String obtainDebugMessage() { PrintConfig config = new PrintConfig(); config.withInfo = true; return formatString(config); } public String obtainAllMessage() { PrintConfig config = new PrintConfig(); config.withLog = true; config.withInfo = true; return formatString(config); } public String formatString(PrintConfig config) { StringBuilder sb = new StringBuilder(); hasError = false; for (HashMap.Entry entry : checks.entrySet()) { Check check = entry.getValue(); String testName = entry.getKey(); if (check.hasError()) { if (!hasError) { hasError = true; } sb.append("【错误】【").append(testName).append(" 】 ").append(check.getErrorMessage()).append("\n"); if (check.hasFix()) { sb.append("【修复方法】【").append(testName).append(" 】 ").append(check.getFixMessage()).append("\n"); } } if (config.withInfo && check.hasInfo()) { sb.append("【请手动检查】【").append(testName).append("】 ").append(check.getInfoMessage()).append("\n"); } if (config.withLog && (config.withLogOnSuccess || hasError) && check.hasLog()) { sb.append("【log】:" + check.getLogMessage()).append("\n"); } } if (!hasError) { sb.append("集成自动排查工具: 恭喜没有检测到任何问题\n"); } return sb.toString(); } public void clear() { checks.clear(); hasError = false; } private AutoCheck(Context context) { this.context = context; checks = new LinkedHashMap(); } private static class PrintConfig { public boolean withFix = true; public boolean withInfo = false; public boolean withLog = false; public boolean withLogOnSuccess = false; } private static class PermissionCheck extends Check { private Context context; public PermissionCheck(Context context) { this.context = context; } @Override public void check() { String[] permissions = { Manifest.permission.INTERNET, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.MODIFY_AUDIO_SETTINGS, // Manifest.permission.WRITE_EXTERNAL_STORAGE, // Manifest.permission.WRITE_SETTINGS, Manifest.permission.READ_PHONE_STATE, Manifest.permission.ACCESS_WIFI_STATE, // Manifest.permission.CHANGE_WIFI_STATE }; ArrayList toApplyList = new ArrayList(); for (String perm : permissions) { if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(context, perm)) { toApplyList.add(perm); // 进入到这里代表没有权限. } } if (!toApplyList.isEmpty()) { errorMessage = "缺少权限:" + toApplyList; fixMessage = "请从AndroidManifest.xml复制相关权限"; } } } private static class JniCheck extends Check { private Context context; private String[] soNames; public JniCheck(Context context) { this.context = context; soNames = new String[]{"libbd_etts.so", "libBDSpeechDecoder_V1.so", "libgnustl_shared.so"}; } @Override public void check() { String path = context.getApplicationInfo().nativeLibraryDir; appendLogMessage("Jni so文件目录 " + path); File[] files = new File(path).listFiles(); TreeSet set = new TreeSet<>(); if (files != null) { for (File file : files) { if (file.canRead()) { set.add(file.getName()); } } } appendLogMessage("Jni目录内文件: " + set.toString()); for (String name : soNames) { if (!set.contains(name)) { errorMessage = "Jni目录" + path + " 缺少可读的so文件:" + name + ", 该目录文件列表: " + set.toString(); fixMessage = "如果您的app内没有其它so文件,请复制demo里的src/main/jniLibs至同名目录。" + " 如果app内有so文件,请合并目录放一起(注意目录取交集,多余的目录删除)。"; break; } } } } private static class ParamKeyExistCheck extends Check { private Map params; private String key; private String prefixErrorMessage; public ParamKeyExistCheck(Map params, String key, String prefixErrorMessage) { this.params = params; this.key = key; this.prefixErrorMessage = prefixErrorMessage; } @Override public void check() { if (params == null || !params.containsKey(key)) { errorMessage = prefixErrorMessage + " 参数中没有设置:" + key; fixMessage = "请参照demo在设置 " + key + "参数"; } } } private static class OfflineResourceFileCheck extends Check { private String filename; private String nullMessage; public OfflineResourceFileCheck(String filename) { this.filename = filename; this.nullMessage = nullMessage; } @Override public void check() { File file = new File(filename); boolean isSuccess = true; if (!file.exists()) { errorMessage = "资源文件不存在:" + filename; isSuccess = false; } else if (!file.canRead()) { errorMessage = "资源文件不可读:" + filename; isSuccess = false; } if (!isSuccess) { fixMessage = "请将demo中src/main/assets目录下同名文件复制到 " + filename; } } } private static class ApplicationIdCheck extends Check { private String appId; private Context context; public ApplicationIdCheck(Context context, String appId) { this.appId = appId; this.context = context; } @Override public void check() { infoMessage = "如果您集成过程中遇见离线合成初始化问题,请检查网页上appId:" + appId + " 应用是否开通了合成服务,并且网页上的应用填写了Android包名:" + getApplicationId(); } private String getApplicationId() { return context.getPackageName(); } } private static class AppInfoCheck extends Check { private String appId; private String appKey; private String secretKey; public AppInfoCheck(String appId, String appKey, String secretKey) { this.appId = appId; this.appKey = appKey; this.secretKey = secretKey; } @Override public void check() { do { appendLogMessage("try to check appId " + appId + " ,appKey=" + appKey + " ,secretKey" + secretKey); if (appId == null || appId.isEmpty()) { errorMessage = "appId 为空"; fixMessage = "填写appID"; break; } if (appKey == null || appKey.isEmpty()) { errorMessage = "appKey 为空"; fixMessage = "填写appID"; break; } if (secretKey == null || secretKey.isEmpty()) { errorMessage = "secretKey 为空"; fixMessage = "secretKey"; break; } } while (false); try { checkOnline(); } catch (UnknownHostException e) { infoMessage = "无网络或者网络不连通,忽略检测 : " + e.getMessage(); } catch (Exception e) { errorMessage = e.getClass().getCanonicalName() + ":" + e.getMessage(); fixMessage = " 重新检测appId, appKey, appSecret是否正确"; } } public void checkOnline() throws Exception { String urlpath = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + appKey + "&client_secret=" + secretKey; URL url = new URL(urlpath); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(1000); InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder result = new StringBuilder(); String line = ""; do { line = reader.readLine(); if (line != null) { result.append(line); } } while (line != null); String res = result.toString(); appendLogMessage("openapi return " + res); JSONObject jsonObject = new JSONObject(res); String error = jsonObject.optString("error"); if (error != null && !error.isEmpty()) { throw new Exception("appkey secretKey 错误" + ", error:" + error + ", json is" + result); } String token = jsonObject.getString("access_token"); if (token == null || !token.endsWith("-" + appId)) { throw new Exception("appId 与 appkey及 appSecret 不一致。appId = " + appId + " ,token = " + token); } } } private abstract static class Check { protected String errorMessage = null; protected String fixMessage = null; protected String infoMessage = null; protected StringBuilder logMessage; public Check() { logMessage = new StringBuilder(); } public abstract void check(); public boolean hasError() { return errorMessage != null; } public boolean hasFix() { return fixMessage != null; } public boolean hasInfo() { return infoMessage != null; } public boolean hasLog() { return !logMessage.toString().isEmpty(); } public void appendLogMessage(String message) { logMessage.append(message + "\n"); } public String getErrorMessage() { return errorMessage; } public String getFixMessage() { return fixMessage; } public String getInfoMessage() { return infoMessage; } public String getLogMessage() { return logMessage.toString(); } } }