001package com.aispeech.dui.dds;
002
003import android.content.Context;
004import android.content.IntentFilter;
005import android.os.Build;
006import android.os.Handler;
007import android.text.TextUtils;
008
009import com.aispeech.dds.libdaemon.DaemonUtil;
010import com.aispeech.dui.BaseNode;
011import com.aispeech.dui.BusClient;
012import com.aispeech.dui.dds.agent.Agent;
013import com.aispeech.dui.dds.agent.MessageObserver;
014import com.aispeech.dui.dds.auth.AccessTokenManager;
015import com.aispeech.dui.dds.auth.AuthType;
016import com.aispeech.dui.dds.exceptions.DDSNotInitCompleteException;
017import com.aispeech.dui.dds.update.DDSUpdater;
018import com.aispeech.dui.dds.utils.AuthUtil;
019import com.aispeech.dui.dds.utils.CheckCrashUtil;
020import com.aispeech.dui.dds.utils.Engines;
021import com.aispeech.dui.dds.utils.FileUtils;
022import com.aispeech.dui.manager.AIJavaException;
023import com.aispeech.dui.manager.AILog;
024import com.aispeech.dui.manager.BusManager;
025import com.aispeech.dui.manager.ThreadName;
026import com.aispeech.dui.oauth.AuthorizationManager;
027import com.aispeech.dui.oauth.OAuthSdk;
028import com.aispeech.dui.oauth.TokenListener;
029import com.aispeech.interceptor.DDSInterceptor;
030import com.aispeech.interceptor.IInterceptor;
031import com.aispeech.libbase.AIGsonUtil;
032import com.aispeech.libbase.AIUtil;
033import com.aispeech.libbase.debug.DebugBean;
034import com.aispeech.libbase.debug.DebugConstant;
035import com.aispeech.libcomm.abslite.EngineProxy;
036import com.aispeech.libcomm.abslite.IEngine;
037import com.aispeech.libcomm.business.LocalKeys;
038import com.aispeech.libcomm.business.LocalKeysUtil;
039import com.aispeech.libcomm.business.call.AuthCallUtil;
040import com.aispeech.libcomm.business.config.PlayerConfig;
041import com.aispeech.libcomm.business.topic.AuthTopicUtil;
042import com.aispeech.libcomm.business.topic.Topic;
043import com.aispeech.libcomm.business.util.PcmUtil;
044import com.aispeech.libcomm.business.util.VersionUtil;
045
046import org.json.JSONException;
047import org.json.JSONObject;
048
049import java.io.IOException;
050import java.net.InetSocketAddress;
051import java.net.ServerSocket;
052import java.util.Random;
053import java.util.regex.Matcher;
054import java.util.regex.Pattern;
055
056
057/**
058 * Disclaim
059 * <p>
060 * This program is the property of AI Speech Ltd. It shall be communicated to
061 * authorized personnel only. It is not to be disclosed outside the group without
062 * prior written consent. If you are not sure if you’re authorized to read this
063 * program, please contact info@aispeech.com before reading.
064 * <p>
065 * Created by jinrui.gan on 17-3-12.
066 */
067
068public class DDS {
069    /**
070     * Extract dds.bin error.
071     */
072    public static final int ERROR_EXTRACT_DDS_BIN = 0x00000001;
073    /**
074     * Lasa execute bootloader error.
075     * Contact us.
076     */
077    public static final int ERROR_LASA_EXECUTE_FAILED = 0x00000002;
078    /**
079     * Waiting sys.kernel.ready timeout error.
080     * Contact us.
081     */
082    public static final int ERROR_KERNEL_READY_TIMEOUT = 0x00000003;
083    /*
084     * No enough space left on the disk.
085     */
086    public static final int ERROR_NO_SPACE_LEFT = 0x00000004;
087
088    /**
089     * md5sum not exist. will break DDS
090     */
091    public static final int ERROR_MD5SUM_NOT_EXIST = 0x00000005;
092
093    public static final String ERROR_MD5SUM_NOT_EXIST_MSG = "md5sum file not exist";
094
095    private static final String TAG = "DDS";
096    public static final int INIT_COMPLETE_NONE = 0;
097    public static final int INIT_COMPLETE_NOT_FULL = 1;
098    public static final int INIT_COMPLETE_FULL = 2;
099    public static final int INIT_COMPLETE_WAIT_REFRESH_TOKEN = 3;// 等待刷新token结果
100    private static final int STATE_IDLE = 0;
101    private static final int STATE_BUSY = 1;
102    public static final int AUTH_COMPLETE_NONE = 0;
103    public static final int AUTH_COMPLETE_FULL = 2;
104    private volatile static DDS mInstance;
105    private Agent agent;
106    private DDSUpdater updater;
107    private AIBootloader aiBootloader;
108    private Handler workerHandler;
109    public Context mContext;
110    private String[] bootErrorMsg = new String[]{
111            "解压dds.bin失败",
112            "内核执行出错",
113            "系统启动超时",
114            "存储空间不足",
115            "IO异常"
116    };
117    /**
118     * Indicate that core and hybrid nodes has been boot up.
119     */
120    private int isInitComplete = INIT_COMPLETE_NONE;
121
122    /**
123     * Indicate that auth has been boot up.
124     */
125    private int isAuthComplete = -1;
126
127    private boolean isError = false;
128    private int state = STATE_IDLE;
129//    private CountDownLatch countDownLatch = new CountDownLatch(1);
130    public static final String DNS_SERVER = "127.0.0.1:5353";
131    public static String BUS_SERVER_ADDR = "127.0.0.1:50001";
132    private static int INIT_TIMEOUT = 30;
133    private long mInitTimeStamp = 0;//初始化时间戳
134    private DDSBroadcastReceiver mDDSBroadcastReceiver = new DDSBroadcastReceiver();
135    private Random mPortRandom = new Random();
136
137    private DDS() {
138    }
139
140    /**
141     * 获取DDS实例
142     *
143     * @return DDS  DDS实例
144     */
145    public static synchronized DDS getInstance() {
146        DDS localResource = mInstance;
147        if (localResource == null) {
148            synchronized (DDS.class) {
149                localResource = mInstance;
150                if (localResource == null) {
151                    mInstance = localResource = new DDS();
152                }
153            }
154        }
155        return localResource;
156    }
157
158    /**
159     * 获取DDS SDK的版本号
160     *
161     * @return String 版本号
162     */
163    public String getVersionName() {
164        AILog.userI(TAG, "getVersionName result =", Version.DDS_SDK_VERSION_NAME);
165        return Version.DDS_SDK_VERSION_NAME;
166    }
167
168    /**
169     * 初始化DDS SDK
170     *
171     * @param context         Android context.
172     * @param configs         初始化的配置
173     * @param ddsInitListener 初始化回调方法
174     * @param ddsAuthListener 授权回调方法
175     */
176    public synchronized void init(final Context context, final DDSConfig configs, final
177    DDSInitListener ddsInitListener, DDSAuthListener ddsAuthListener) {
178        AILog.userI(TAG, "DDSInit config =", configs.toString());
179        AILog.i(TAG, "====> init. Version: " + getVersionName());
180        mInitTimeStamp = System.currentTimeMillis();
181        mContext = context.getApplicationContext();
182//        CrashUtils.init(mContext);
183
184        if (state == STATE_BUSY) {
185            AILog.e(TAG, "already running, ignore.");
186            return;
187        }
188        AuthorizationManager.getInstance().setContext(mContext);
189        AccessTokenManager.getInstance(mContext).clearTasks();
190        AIUtil.init(mContext);
191        CheckCrashUtil.getInstance().startInitDDS(mContext);
192        boolean useInnerLog = configs.getBooleanConfig("USE_INNER_LOG",true);
193        EngineProxy.createDebug().setUseInnerLog(useInnerLog);
194        EngineProxy.createDebug().init();
195
196        state = STATE_BUSY;
197        if (workerHandler == null) {
198            workerHandler = BusManager.getInstance().getInnerHandler();
199        }
200        workerHandler.post(new InitRunnable(mContext, configs.copyDeep(), ddsInitListener,
201                ddsAuthListener));
202
203        registerBroadcast(mContext);
204    }
205
206    private void registerBroadcast(Context context) {
207        IntentFilter intentFilter = new IntentFilter();
208        intentFilter.addAction(DebugConstant.Action.INTENT_BUS_ADDRESS_GET);
209        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
210            context.registerReceiver(mDDSBroadcastReceiver, intentFilter,Context.RECEIVER_EXPORTED);
211        } else {
212            context.registerReceiver(mDDSBroadcastReceiver, intentFilter);
213        }
214    }
215
216    /**
217     * 销毁DDS SDK
218     */
219
220    public synchronized void release() {
221        // release方法调用同步方法, 此方法已降到100ms以内, 可以使用同步方法
222        AILog.userI(TAG, "release");
223        releaseSync();
224    }
225
226    /**
227     * 同步销毁 DDS SDK
228     */
229    public synchronized void releaseSync() {
230        AILog.userI(TAG, "releaseSync");
231        isAuthComplete = AUTH_COMPLETE_NONE;
232        AILog.i(TAG, "=====> release sync");
233        if (state == STATE_IDLE) {
234            AILog.e(TAG, "already release, ignore.");
235            return;
236        }
237        try {
238            AccessTokenManager.getInstance(mContext).release();
239            mContext.unregisterReceiver(mDDSBroadcastReceiver);
240        } catch (Exception e) {
241            AIJavaException.printException(e);
242        }
243
244
245        if (agent != null) {
246            agent.stop();
247        }
248        if (aiBootloader != null) {
249            aiBootloader.release();
250        }
251        if (updater != null) {
252            updater.release();
253        }
254        releaseSyncActions();
255        AILog.releaseLogThread();
256        state = STATE_IDLE;
257        mInstance = null;
258    }
259
260
261    /**
262     * 获取Agent
263     *
264     * @return Agent agent实例
265     */
266    public Agent getAgent() {
267        return agent;
268    }
269
270    /**
271     * 获取DDSUpdater
272     *
273     * @return DDSUpdater DDSUpdater实例
274     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
275     */
276    public DDSUpdater getUpdater() throws DDSNotInitCompleteException {
277        checkInitPartComplete();
278        AILog.userI(TAG, "getUpdater");
279        return updater;
280    }
281
282    /**
283     * 执行设备授权操作
284     * 异步返回,见 {@link DDSAuthListener}
285     */
286    public void doAuth() throws DDSNotInitCompleteException {
287        BusClient bc = getBusClient();
288        AILog.userI(TAG, "doAuth");
289        if (bc != null) {
290            AuthTopicUtil.publishAuthStart(bc);
291        } else {
292            AILog.e(TAG, "BusClent is null when call doAuth!");
293        }
294    }
295
296    /**
297     * 返回Profile授权方式下的deviceName信息
298     *
299     * @return deviceName 未授权则该值为空字符串""
300     * @throws DDSNotInitCompleteException
301     */
302    public String getDeviceName() throws DDSNotInitCompleteException {
303        checkInitPartComplete();
304        BusClient bc = getBusClient();
305        String result = "";
306        if (bc != null) {
307            result = AuthCallUtil.callAuthDeviceName(bc);
308        } else {
309            AILog.e(TAG, "BusClent is null when call getDeviceName!");
310        }
311        AILog.userI(TAG, "getDeviceName result =", result);
312        return result;
313    }
314
315    private BusClient getBusClient() {
316        if (getAgent() != null) {
317            return getAgent().getBusClient();
318        }
319        return null;
320    }
321
322    /**
323     * 授权是否成功,该方法适用于api key授权
324     *
325     * @return boolean true:成功  false:没有成功
326     */
327    public boolean isAuthSuccess() {
328        boolean ok = isAuthComplete == AUTH_COMPLETE_FULL;
329        AILog.userI(TAG, "isAuthSuccess result =", ok);
330        return ok;
331    }
332
333    /**
334     * 更新accessToken,该方法适用于oauth授权,可以调用oauth sdk获取accessToken
335     *
336     * @param accessToken oauth授权获取到的accessToken
337     * @return boolean true:更新成功 false:更新失败
338     * @throws DDSNotInitCompleteException 此方法已经弃用,oauth授权的最新 api 参见{@link Agent#setAuthCode(com.aispeech.dui.dds.auth.AuthInfo, TokenListener)}
339     */
340    @Deprecated
341    private boolean updateAccessToken(String accessToken) throws DDSNotInitCompleteException {
342        checkInitPartComplete();
343        AuthCallUtil.callAuthUpdateAccessToken(getAgent().getBusClient(), accessToken);
344        return true;
345    }
346
347    /**
348     * 初始化是否成功
349     *
350     * @return boolean true:成功  false:没有成功
351     * {@link DDS#getInitStatus() }
352     */
353    @Deprecated
354    public boolean isInitComplete() {
355        boolean ok = (isInitComplete == INIT_COMPLETE_FULL || isInitComplete == INIT_COMPLETE_NOT_FULL);
356        AILog.userI(TAG, "isInitComplete result =", ok);
357        return ok;
358    }
359
360
361    /**
362     * 获取当前初始化的状态
363     *
364     * @return DDS.INIT_COMPLETE_NONE     还未初始化完成,表示DDS正在初始化
365     * DDS.INIT_COMPLETE_NOT_FULL 部分初始化完成,表示DDS已经初始化完成,但还没有完成更新
366     * DDS.INIT_COMPLETE_FULL     完全初始化完成,已经完成更新
367     */
368    public int getInitStatus() {
369        switch (isInitComplete) {
370            case INIT_COMPLETE_NOT_FULL:
371                return INIT_COMPLETE_NOT_FULL;
372            case INIT_COMPLETE_FULL:
373                return INIT_COMPLETE_FULL;
374            default:
375                return INIT_COMPLETE_NONE;
376        }
377    }
378
379
380    /**
381     * 设置日志模式,支持在任何时候调用
382     *
383     * @param level 可选值2,3,4,5,6 默认值:4
384     */
385    public boolean setDebugMode(int level) {
386        if (level <= 0) return false;
387        AILog.userI(TAG, "setDebugMode =", level);
388        AILog.setLogLevel(level);
389        EngineProxy.createDebug().setLogLevel(level);
390        return true;
391    }
392
393    /**
394     * 设置日志模式,支持在任何时候调用
395     *
396     * @param level 可选值2,3,4,5,6 默认值:4
397     */
398    public boolean setDebugMode(int level, String logCachePath) {
399        if (level <= 0) return false;
400        AILog.userI(TAG, "setDebugMode =", level, ", logCachePath = ", logCachePath);
401        AILog.setLogLevel(level);
402        EngineProxy.createDebug().setLogLevel(level);
403        return true;
404    }
405
406    /**
407     * 设置是否支持音频调试,支持在任何时候调用,打开之后会保存调试音频
408     *
409     * @param enable true/false
410     * @return
411     */
412    public boolean setAudioDebug(boolean enable) {
413        AILog.userI(TAG, "setAudioDebug =", enable);
414        setAudioDebug(enable, Engines.ALL);
415        return true;
416    }
417
418    /**
419     * 动态修改音频保存路径,调用后以此路径为准
420     *
421     * @param path 支持动态修改音频保存路径,默认为cache目录
422     * @return
423     */
424    public boolean setAudioSavePath(String path) {
425        AILog.userI(TAG, "setAudioSavePath =", path);
426        EngineProxy.createDebug().setAudioSavePath(path);
427        return true;
428    }
429
430    /**
431     * 设置是否支持音频调试,支持在任何时候调用,打开之后会保存调试音频
432     * @param enable true: 开始保存音频; false: 关闭保存音频;
433     * @param engines 与 enable 有关联,如果enable=true,表示开启指定引擎保存音频;如果enable=false,表示不保存指定引擎音频;
434     * */
435    public boolean setAudioDebug(boolean enable, int engines) {
436        String mAudioSavePath = EngineProxy.createDebug().getAudioSavePath();
437        if(TextUtils.isEmpty(mAudioSavePath)) {
438            throw new IllegalStateException("Please call setAudioSavePath before init DDS");
439        }
440        AILog.userI(TAG, "setAudioDebug =", enable, ", engines =", engines);
441        EngineProxy.createDebug().setAudioDebug(enable, engines);
442
443        if((engines & Engines.DDS_DMS) > 0){
444            PcmUtil.setEnable(enable);
445        }
446        return true;
447    }
448
449    private void checkInitComplete() throws DDSNotInitCompleteException {
450        if (getInitStatus() != DDS.INIT_COMPLETE_FULL) {
451            throw new DDSNotInitCompleteException();
452        }
453    }
454
455    private void checkInitPartComplete() throws DDSNotInitCompleteException {
456        if (getInitStatus() == DDS.INIT_COMPLETE_NONE) {
457            throw new DDSNotInitCompleteException();
458        }
459    }
460
461    private class InitRunnable implements Runnable {
462
463        private DDSInitListener ddsInitListener;
464        private DDSAuthListener ddsAuthListener;
465        private String filesDir;
466
467        private int getPort() {
468            int port = mPortRandom.nextInt(10000) + 50000;
469
470            if (isPortConnectable(port)) {
471                AILog.d(TAG, "端口生成成功");
472                return port;
473            } else {
474                AILog.d(TAG, "端口被占用--正在尝试重新生成端口");
475                return getPort();
476            }
477        }
478
479        private boolean isPortConnectable(int port) {
480            ServerSocket socket = null;
481            try {
482                socket = new ServerSocket();
483                socket.setReuseAddress(true);
484                socket.bind(new InetSocketAddress("127.0.0.1", port));
485            } catch (Exception e) {
486                AIJavaException.printException(e);
487                return false;
488            } finally {
489                if (socket != null) {
490                    try {
491                        socket.close();
492                        AILog.d(TAG, "=====socket关闭");
493                    } catch (Exception e) {
494                        AIJavaException.printException(e);
495                    }
496                }
497            }
498            return true;
499        }
500
501        private boolean isContainChinese(String str) {
502            Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
503            Matcher m = p.matcher(str);
504            if (m.find()) {
505                return true;
506            }
507            return false;
508        }
509
510        private void callbackInitListener(int isInitComplete) {
511            boolean isInitCompleteBoo = isInitComplete == INIT_COMPLETE_FULL;
512            if (isInitCompleteBoo) {
513                CheckCrashUtil.getInstance().initDDSSuccess();
514            }
515            ddsInitListener.onInitComplete(isInitCompleteBoo);
516        }
517
518        private void prepareConfig(DDSConfig configs) {
519            if (TextUtils.equals("none", configs.getConfig(DDSConfig.K_AUTH_TYPE))) {
520                configs.addConfig(DDSConfig.K_ACCESS_TOKEN, "geComesHere");
521            }
522            configs.addConfig(DDSConfig.K_AUTH_SIGNATURE, AuthUtil.getKeyHash(mContext));
523            String securityCode = mContext.getPackageName() + ":" + AuthUtil.getKeyHash(mContext);
524            configs.addConfig(DDSConfig.K_SECURITY_CODE, securityCode);
525            String apiKey = configs.getConfig(DDSConfig.K_API_KEY);
526            if (mContext.getPackageName().equals("com.aispeech.dui.dds.demo")
527                    && apiKey != null && apiKey.equals("4ff3171a1ef1f122381692fa5a2f52ea")) {
528                //dui dev app
529                configs.addConfig(DDSConfig.K_DEVICE_INFO, AuthUtil.getDeviceData(mContext,false));
530            } else {
531                configs.addConfig(DDSConfig.K_DEVICE_INFO, AuthUtil.getDeviceData(mContext,true));
532            }
533
534            if (TextUtils.equals("true", configs.getConfig(DDSConfig.K_ENABLE_DYNAMIC_PORT))) {// 使用动态端口号
535                BUS_SERVER_ADDR = "127.0.0.1:" + getPort();
536            }
537            configs.addConfig(DDSConfig.K_LBRIDGE_ADDR, BUS_SERVER_ADDR);
538
539            if (configs.containsConfig(DDSConfig.K_INIT_TIMEOUT)) {
540                INIT_TIMEOUT = Integer.valueOf(configs.getConfig(DDSConfig.K_INIT_TIMEOUT));
541            }
542            AILog.i(TAG, "BUS_SERVER_ADDR is " + BUS_SERVER_ADDR);
543            AILog.i(TAG, "config->" + configs.toString());
544        }
545
546        public InitRunnable(final Context context, final DDSConfig configs, final
547        DDSInitListener ddsInitListener, DDSAuthListener ddsAuthListener) {
548            this.ddsInitListener = ddsInitListener;
549            this.ddsAuthListener = ddsAuthListener;
550            prepareConfig(configs);
551            agent = new Agent(context);
552            aiBootloader = new AIBootloader(context, configs);
553            updater = new DDSUpdater(context, configs, agent);
554            filesDir = context.getFilesDir().toString();
555        }
556
557        @Override
558        public void run() {
559            int errId = aiBootloader.start();
560            if (errId != 0) {
561                AILog.userO(TAG, "ddsInitListener.onError result =", errId);
562                ddsInitListener.onError(errId, bootErrorMsg[errId - 1]);
563                isError = true;
564            }
565            agent.subscribe(new String[]{
566                    Topic.Sys.MSG_SYS_HYBRID_MISSING,
567                    Topic.Sys.MSG_SYS_KERNEL_READY,
568                    Topic.Auth.AuthFinish.TOPIC_NAME,
569                    Topic.Sys.SysTokenInvalid.TOPIC_NAME,
570                    Topic.Auth.TokenRefreshFail.TOPIC_NAME,
571                    Topic.Auth.TokenRefreshFinish.TOPIC_NAME,
572                    Topic.Sys.SysDebug.TOPIC_NAME
573            }, new MessageObserver() {
574
575                @Override
576                public void onMessage(String message, String data) {
577                    AILog.d(TAG, "agent: " + message);
578                    BusClient bc = getBusClient();
579                    if (message.equals(Topic.Sys.MSG_SYS_HYBRID_MISSING)) {
580                        if (bc != null) {
581                            bc.removeSticky(message);
582                        }
583                        isInitComplete = INIT_COMPLETE_NOT_FULL;
584                        AILog.userO(TAG, "ddsInitListener.onInitComplete result =", false);
585                        callbackInitListener(isInitComplete);
586                    } else if (message.equals(Topic.Sys.MSG_SYS_KERNEL_READY)) {
587                        VersionUtil.dumpVersion();
588
589                        AILog.d(TAG, "STARTUP.COST " + (System.currentTimeMillis() - mInitTimeStamp));
590                        if (bc != null) {
591                            bc.removeSticky(message);
592                        }
593                        // 初始化成功之后去刷新token
594                        String auth_type = LocalKeysUtil.getInstance().getString(LocalKeys.Auth.LOCAL_KEY_AUTH_TYPE, "");
595                        String refreshToken = AuthorizationManager.getInstance().getRefreshToken();
596                        boolean needWaitRefreshToken = PlayerConfig.getInstance().getBooleanConfig(DDSConfig.K_INIT_WAIT_OAUTH_REFRESH_TOKEN);
597                        AILog.i(TAG, "kernel.ready auth_type = " + auth_type + ", refreshToken = " + refreshToken + ", needWaitRefreshToken = " + needWaitRefreshToken);
598                        if (TextUtils.equals(auth_type, AuthType.AISPEECH_ID) && !TextUtils.isEmpty(refreshToken)) {
599                            AILog.d(TAG, "start refreshToken timer...");
600                            String cliendId = LocalKeysUtil.getInstance().getString(LocalKeys.OAuth.LOCAL_AUTH_TYPE_CLIEND_ID, "");
601                            OAuthSdk.initialize(mContext, cliendId, "dds");
602                            AccessTokenManager.getInstance(mContext).checkRefreshToken(true);
603                            if (needWaitRefreshToken) {
604                                isInitComplete = INIT_COMPLETE_WAIT_REFRESH_TOKEN;
605                                AILog.d(TAG, "wait refresh token...");
606                                return;
607                            }
608                        }
609
610                        isInitComplete = INIT_COMPLETE_FULL;
611                        AILog.d(TAG, "sys.kernel.ready countdown latch");
612                        AILog.d(TAG, "onInitComplete = true");
613                        AILog.userO(TAG, "ddsInitListener.onInitComplete result =", (isInitComplete == INIT_COMPLETE_FULL));
614                        callbackInitListener(isInitComplete);
615                    } else if (message.equals(Topic.Auth.AuthFinish.TOPIC_NAME)) {
616                        if (ddsAuthListener == null) {
617                            return;
618                        }
619                        try {
620                            JSONObject jsonData = new JSONObject(data);
621                            String state = jsonData.optString(Topic.Auth.AuthFinish.STATE);
622                            if (TextUtils.equals(state, "success")) {
623                                isAuthComplete = AUTH_COMPLETE_FULL;
624                                AILog.userO(TAG, "ddsAuthListener.onAuthSuccess");
625                                ddsAuthListener.onAuthSuccess();
626                            } else if (TextUtils.equals(state, "failed")) {
627                                String errId = jsonData.optString("errId");
628                                String error = jsonData.optString("error");
629                                StringBuilder builder = new StringBuilder();
630                                builder.append("\n==================================================");
631                                builder.append("\n====================授权失败=======================");
632                                builder.append("\n=============apk 版本 -> ");
633                                builder.append(AuthUtil.getBuildVariant(mContext));
634                                builder.append("\n============apk SHA256-> ");
635                                builder.append(AuthUtil.getKeyHash(mContext));
636                                builder.append("\n============apk packageName -> ");
637                                builder.append(mContext.getPackageName());
638                                builder.append("\n============errorId -> ");
639                                builder.append(errId);
640                                builder.append("\n============errorInfo -> ");
641                                builder.append(error);
642                                builder.append("\n==================================================");
643                                AILog.e(TAG, builder.toString());
644                                isAuthComplete = AUTH_COMPLETE_NONE;
645                                ddsAuthListener.onAuthFailed(errId, error);
646                                AILog.userO(TAG, "ddsAuthListener.onAuthFailed result =", errId);
647                            }
648                        } catch (JSONException e) {
649                            AIJavaException.printException(e);
650                        }
651                    } else if (message.equals(Topic.Sys.SysTokenInvalid.TOPIC_NAME)) {
652                        AILog.e(TAG, message + ", will call refreshForInit");
653                        AccessTokenManager.getInstance(mContext).refreshForInit();
654                        if (ddsAuthListener == null) {
655                            AILog.e(TAG, "ddsAuthListener is null return");
656                            return;
657                        }
658                        isAuthComplete = AUTH_COMPLETE_NONE;
659                        AILog.userO(TAG, "ddsAuthListener.onAuthFailed result = 070611, access token is invalid");
660                        ddsAuthListener.onAuthFailed("070611", "access token is invalid");
661                    } else if (message.equals(Topic.Auth.TokenRefreshFinish.TOPIC_NAME)) {
662                        AILog.d(TAG, "token_refresh.finish isInitComplete = " + isInitComplete);
663                        if (isInitComplete == INIT_COMPLETE_WAIT_REFRESH_TOKEN) {
664                            isInitComplete = INIT_COMPLETE_FULL;
665                            AILog.d(TAG, "token_refresh.finish sys.kernel.ready countdown latch");
666                            AILog.d(TAG, "token_refresh.finish onInitComplete = true");
667                            AILog.userO(TAG, "ddsInitListener.onInitComplete token_refresh.finish result =", (isInitComplete == INIT_COMPLETE_FULL));
668                            callbackInitListener(isInitComplete);
669                        }
670                    } else if (message.equals(Topic.Auth.TokenRefreshFail.TOPIC_NAME)) {
671                        try {
672                            AILog.d(TAG, "token_refresh.fail isInitComplete = " + isInitComplete);
673                            JSONObject jsonData = new JSONObject(data);
674                            int errId = jsonData.optInt(Topic.Auth.TokenRefreshFail.ERR_ID);
675                            AILog.d(TAG, "刷新Token失败 errId = " + errId);
676                            if (errId == 70624) {
677                                if (ddsAuthListener != null) {
678                                    isAuthComplete = AUTH_COMPLETE_NONE;
679                                    AILog.userO(TAG, "ddsAuthListener.onAuthFailed result = 070611, access token is invalid");
680                                    ddsAuthListener.onAuthFailed("070611", "access token is invalid");
681                                }
682                            }
683                            if (isInitComplete == INIT_COMPLETE_WAIT_REFRESH_TOKEN) {
684                                isInitComplete = INIT_COMPLETE_FULL;
685                                AILog.d(TAG, "token_refresh.fail onInitComplete = true");
686                                callbackInitListener(isInitComplete);
687                            }
688                            if (errId == 70624) {
689                                AILog.d(TAG, "刷新Token失败,此Token不可用,需要重新绑定,clearAuthCode。。。");
690                                agent.clearAuthCode();
691                            }
692                        } catch (Exception e) {
693                            e.printStackTrace();
694                        }
695                    } else if (TextUtils.equals(message, Topic.Sys.SysDebug.TOPIC_NAME)) {
696                        if (bc != null) {
697                            bc.removeSticky(message);
698                        }
699                        parseDebugInfo(data);
700                    }
701                }
702            });
703            agent.start();
704            workerHandler.postDelayed(new Runnable() {
705                @Override
706                public void run() {
707                    if(getInitStatus() == INIT_COMPLETE_NONE) {
708                        ddsInitListener.onError(ERROR_KERNEL_READY_TIMEOUT, "初始化超时");
709                    }
710                }
711            },INIT_TIMEOUT * 1000L);
712            AILog.d(TAG,"start init...");
713        }
714    }
715
716
717    private void parseDebugInfo(String data) {
718        AILog.e(TAG, "debug date is -> " + data);
719        DebugBean debugBean = (DebugBean) AIGsonUtil.getInstance().jsonToBean(data, DebugBean.class);
720        if (debugBean == null) return;
721        AILog.e(TAG, "debugBean is -> " + debugBean.toString());
722        int debugType = debugBean.getDebugType();
723        switch (debugType) {
724            case DebugBean.TYPE_LOG_DEBUG_START:
725                setDebugMode(2);
726                break;
727            case DebugBean.TYPE_AUDIO_DEBUG_START:
728                setAudioDebug(true);
729                break;
730            case DebugBean.TYPE_DEBUG_STOP:
731                stopDebug();
732                break;
733            default:
734        }
735    }
736
737    private class ReleaseRunnable implements Runnable {
738        @Override
739        public void run() {
740            isInitComplete = INIT_COMPLETE_NONE;
741            DaemonUtil.getInstance().release();
742            isError = false;
743
744            AILog.w(TAG, "dds shutdown");
745        }
746    }
747
748    private void releaseSyncActions() {
749        isInitComplete = INIT_COMPLETE_NONE;
750        DaemonUtil.getInstance().release();
751        isError = false;
752        AILog.w(TAG, "dds shutdown");
753    }
754
755    private void resetExitFlag(String dir) {
756        try {
757            FileUtils.writeFile(String.valueOf(System.currentTimeMillis()), dir);
758        } catch (IOException e) {
759            AIJavaException.printException(e);
760        }
761    }
762
763    /**
764     * 设置DDS异常监听器,如果DDS有不可逆的异常会通过此接口抛出,客户收到此异常之后可以消毁并重启DDS
765     *
766     * @param listener
767     */
768    public void setDDSErrorListener(final DDSErrorListener listener) {
769//        AIJavaException.setExceptionCallback(new AIJavaException.ExceptionCallback() {
770//            @Override
771//            public void onException(String e) {
772//                if (listener != null) {
773//                    listener.onError(404, JSONObjectUtil.create()
774//                            .put("error", e)
775//                            .build());
776//                }
777//            }
778//        });
779    }
780
781    /**
782     * 设置录音机音频反转监听器
783     *
784     * @param listener
785     */
786    public boolean setReverseRecorderDataListener(ReverseRecorderDataListener listener) {
787        AILog.userI(TAG, "setReverseRecorderDataListener");
788        if (aiBootloader != null && aiBootloader.mRecorderNode != null) {
789            return aiBootloader.mRecorderNode.setReverseRecorderDataListener(listener);
790        }
791        return false;
792    }
793
794    /**
795     * 开启调试模式:
796     * 1. 日志级别调为V
797     * 2. 保存调试音频
798     */
799    public void startDebug() {
800        AILog.userI(TAG, "startDebug");
801        setDebugMode(2);
802        setAudioDebug(true);
803    }
804
805    /**
806     * 关闭调试模式:
807     * 1. 日志级别修改为E
808     * 2. 停止保存调试音频
809     */
810    public void stopDebug() {
811        AILog.userI(TAG, "stopDebug");
812        setDebugMode(6);
813        setAudioDebug(false);
814    }
815
816    public void addCustomNode(BaseNode node) {
817        AILog.userI(TAG, "addCustomNode node =", (node != null ? node.getName() : "null"));
818        DaemonUtil.getInstance().addNode(node);
819    }
820
821    /**
822     * 用户设置自己实现的单个功能,目前支持 wakeup 和 vad
823     * <p>
824     * 必须在 {@link #init } 方法前调用本方法
825     * </p>
826     * <ul>
827     *      <li>IEngine.Name.WAKEUP_SINGLE_MIC 实现接口 {@link com.aispeech.libcomm.abslite.IWakeupEngine}</li>
828     *      <li>IEngine.Name.WAKEUP_MULTIPLE_MIC 实现接口 {@link com.aispeech.libcomm.abslite.IWakeupEngine}</li>
829     *      <li>IEngine.Name.WAKEUP_INCREMENT 实现接口 {@link com.aispeech.libcomm.abslite.IWakeupEngine}</li>
830     *      <li>IEngine.Name.VAD 实现接口 {@link com.aispeech.libcomm.abslite.IVadEngine}</li>
831     * </ul>
832     *
833     * @param name        名称
834     * @param engineClazz 对应的实现类, 为 null 时表示将之前的设置取消
835     * @return true 设置成功,false engineClazz 实现的类不对
836     */
837    public boolean setOutsideEngine(IEngine.Name name, Class<? extends IEngine> engineClazz) {
838        return EngineProxy.setOutsideEngine(name, engineClazz);
839    }
840
841
842    /**
843     * 添加dds拦截器
844     *
845     * @param iInterceptor
846     */
847    public void addInterceptor(IInterceptor iInterceptor) {
848        AILog.userI(TAG, "addInterceptor iInterceptor =", (iInterceptor != null ? iInterceptor.getName() : "null"));
849        DDSInterceptor.getInstance().addInterceptor(iInterceptor);
850    }
851}