001package com.aispeech.dui.dds.agent;
002
003import android.annotation.SuppressLint;
004import android.content.BroadcastReceiver;
005import android.content.Context;
006import android.content.Intent;
007import android.content.IntentFilter;
008import android.net.ConnectivityManager;
009import android.net.NetworkInfo;
010import android.os.Build;
011import android.os.Handler;
012import android.os.HandlerThread;
013import android.os.Message;
014import android.os.SystemClock;
015import android.telephony.TelephonyManager;
016import android.text.TextUtils;
017
018import com.aispeech.dds.libprocessor.ProcessorConfig;
019import com.aispeech.dds.libprocessor.ProcessorJsonBuilder;
020import com.aispeech.dui.BaseNode;
021import com.aispeech.dui.BusClient;
022import com.aispeech.dui.dds.DDS;
023import com.aispeech.dui.dds.DDSConfig;
024import com.aispeech.dui.dds.DDSMode;
025import com.aispeech.dui.dds.agent.tts.TTSEngine;
026import com.aispeech.dui.dds.agent.tts.TTSRequestListener;
027import com.aispeech.dui.dds.agent.vprint.VprintEngine;
028import com.aispeech.dui.dds.agent.wakeup.WakeupEngine;
029import com.aispeech.dui.dds.auth.AccessTokenManager;
030import com.aispeech.dui.dds.auth.AuthInfo;
031import com.aispeech.dui.dds.auth.AuthType;
032import com.aispeech.dui.dds.exceptions.DDSNotInitCompleteException;
033import com.aispeech.dui.dds.nodes.PlayerNode;
034import com.aispeech.dui.dds.nodes.RecorderExNode;
035import com.aispeech.dui.dds.nodes.TtsExNode;
036import com.aispeech.dui.dds.utils.DeviceUtil;
037import com.aispeech.dui.dds.utils.FileUtils;
038import com.aispeech.dui.dds.utils.NetworkUtils;
039import com.aispeech.dui.dds.utils.URLUtils;
040import com.aispeech.dui.dsk.duiwidget.CommandObserver;
041import com.aispeech.dui.dsk.duiwidget.DuiWidget;
042import com.aispeech.dui.dsk.duiwidget.NativeApiObserver;
043import com.aispeech.dui.dsk.duiwidget.NativeCommandAsyncObserver;
044import com.aispeech.dui.dsk.duiwidget.NativeCommandObserver;
045import com.aispeech.dui.manager.AIJavaException;
046import com.aispeech.dui.manager.AILog;
047import com.aispeech.dui.manager.BusManager;
048import com.aispeech.dui.manager.ThreadName;
049import com.aispeech.dui.oauth.OAuthSdk;
050import com.aispeech.dui.oauth.TokenListener;
051import com.aispeech.dui.oauth.TokenResult;
052import com.aispeech.libbase.AIGsonUtil;
053import com.aispeech.libbase.StringUtil;
054import com.aispeech.libbase.UUIDUtil;
055import com.aispeech.libbase.bussiness.DDSConstant;
056import com.aispeech.libbase.bussiness.JSONObjectUtil;
057
058import com.aispeech.libcomm.abslite.impl.InterceptorImpl;
059import com.aispeech.libcomm.business.CommonUtil;
060import com.aispeech.libcomm.business.CostTimeUtil;
061import com.aispeech.libcomm.business.LocalKeys;
062import com.aispeech.libcomm.business.LocalKeysUtil;
063import com.aispeech.libcomm.business.NodeName;
064import com.aispeech.libcomm.business.call.AuthCallUtil;
065import com.aispeech.libcomm.business.call.ProcessorCallUtil;
066import com.aispeech.libcomm.business.config.PlayerConfig;
067import com.aispeech.libcomm.business.topic.AgentTopicUtil;
068import com.aispeech.libcomm.business.topic.DmsTopicUtil;
069import com.aispeech.libcomm.business.topic.PlayerTopicUtil;
070import com.aispeech.libcomm.business.topic.ProcessorTopicUtil;
071import com.aispeech.libcomm.business.topic.RecorderTopicUtil;
072import com.aispeech.libcomm.business.topic.SysTopicUtil;
073import com.aispeech.libcomm.business.topic.Topic;
074import com.aispeech.libcomm.business.topic.UploadTopicUtil;
075import com.aispeech.libcomm.business.topic.VoipTopicUtil;
076import com.aispeech.libcomm.business.topic.WakeupTopicUtil;
077import com.aispeech.libcomm.callback.CallbackManager;
078import com.aispeech.libcomm.callback.OnDmTaskCallback;
079
080import org.json.JSONArray;
081import org.json.JSONException;
082import org.json.JSONObject;
083
084import java.io.File;
085import java.io.IOException;
086import java.util.ArrayList;
087import java.util.HashMap;
088import java.util.Arrays;
089import java.util.Iterator;
090import java.util.List;
091import java.util.Map;
092import java.util.Set;
093import java.util.concurrent.ConcurrentSkipListSet;
094import java.util.concurrent.CountDownLatch;
095import java.util.concurrent.FutureTask;
096import java.util.concurrent.TimeUnit;
097
098import static com.aispeech.libbase.bussiness.DDSConstant.Auth.KEY_AUTH_CODE;
099import static com.aispeech.libbase.bussiness.DDSConstant.Auth.KEY_CLIENT_ID;
100import static com.aispeech.libbase.bussiness.DDSConstant.Auth.KEY_CODE_VERIFIER;
101import static com.aispeech.libbase.bussiness.DDSConstant.Auth.KEY_REDIRECT_URI;
102import static com.aispeech.libbase.bussiness.DDSConstant.Auth.KEY_USER_ID;
103import static com.aispeech.libcomm.business.topic.Topic.Player.TOPIC_PLAYER_AUDIOFOCUS_CALLBACK;
104
105public class Agent extends BaseNode implements ReferenceCountMap.OnReferenceCountChangeListener {
106
107    public static final String TAG = "Agent";
108
109    private ReferenceCountMap<NativeApiObserver> nativeApiObservers;
110
111    private ReferenceCountMap<CommandObserver> commandObservers;
112
113    private ReferenceCountMap<NativeCommandObserver> mNativeCommandObservers;
114    private Object mNativeCommandLock = new Object();
115
116    private ReferenceCountMap<MessageObserver> messageObservers;
117
118    private Set<String> subscribedTopics = new ConcurrentSkipListSet<>();
119
120    private Context context;
121
122    private final Object topicsLock = new Object();
123
124    private ConnectionChangeReceiver connectionChangeReceiver;
125
126    private Handler mHandler;
127
128    private DMCallback mDMCallback;
129    private DMTaskCallback mDMTaskCallback;
130
131    private AudioFocusCallback mAudioFocusCallback;
132    private DMWkupRetCallback mDmWkupRetCallback;
133
134    private HandlerThread mHandlerThread;
135    private Handler mHandler2;
136    //区分于阻塞线程,规避因为线程阻塞导致的dds逻辑运行异常问题
137    private HandlerThread mHandlerThreadAsyn;
138    private Handler mHandlerAsyn;
139    private CountDownLatch mNativeCmdCountDownLatch;
140    private Map<String, JSONObject> mNativeCmdResultMap = new HashMap<>();
141
142    public enum DuplexMode {
143        HALF_DUPLEX,
144        FULL_DUPLEX
145    }
146
147    public enum RecorderMode {
148        PCM, SBC, OPUS
149    }
150
151    private String[] mTopicList = new String[]{
152            Topic.Processor.TaskCallback.TOPIC_NAME,
153            Topic.Dla.SetAuthCode.TOPIC_NAME,
154            Topic.Processor.WkupCallback.TOPIC_NAME
155    };
156
157    private String[] mCallList = new String[]{
158            ProcessorCallUtil.CALL_GET_DM_OPERATION,
159            Topic.Agent.CALL_PLAYER_AUDIO_FOCUS_NOTIFY.TOPIC_NAME
160    };
161
162    /**
163     * 构造方法,开发者可以不关注该构造方法,
164     * 而是可以通过{@link com.aispeech.dui.dds.DDS#getAgent}获取Agent实例
165     *
166     * @param context context
167     */
168    public Agent(Context context) {
169        this.context = context;
170        nativeApiObservers = new ReferenceCountMap<>();
171        commandObservers = new ReferenceCountMap<>();
172        mNativeCommandObservers = new ReferenceCountMap<>();
173        messageObservers = new ReferenceCountMap<>();
174        nativeApiObservers.setListener(this);
175        commandObservers.setListener(this);
176        mNativeCommandObservers.setListener(this);
177        messageObservers.setListener(this);
178        ASREngine.getInstance();
179        WakeupEngine.getInstance();
180        TTSEngine.getInstance();
181        initThread();
182        initThreadAsyn();
183    }
184
185    private void initThread() {
186        mHandlerThread = new HandlerThread(ThreadName.AGENT);
187        mHandlerThread.start();
188        mHandler2 = new Handler(mHandlerThread.getLooper()) {
189            @Override
190            public void handleMessage(Message msg) {
191                super.handleMessage(msg);
192            }
193        };
194    }
195
196    private void initThreadAsyn() {
197        mHandlerThreadAsyn = new HandlerThread(ThreadName.AGENT + "asyn");
198        mHandlerThreadAsyn.start();
199        mHandlerAsyn = new Handler(mHandlerThreadAsyn.getLooper());
200    }
201
202    private void asyncExecuteNoBlocking(Runnable runnable) {
203        if (mHandlerThreadAsyn != null && !mHandlerThreadAsyn.isAlive()) {
204            mHandlerThreadAsyn.start();
205        }
206        if (mHandlerAsyn != null) {
207            mHandlerAsyn.post(runnable);
208        }
209    }
210
211    private void syncExecute(FutureTask futureTask) {
212        try {
213            if (mHandlerThread != null && !mHandlerThread.isAlive()) {
214                mHandlerThread.start();
215            }
216            if (mHandler2 != null) {
217                mHandler2.post(futureTask);
218            }
219            futureTask.get();
220        } catch (Exception e) {
221            AIJavaException.printException(e);
222        }
223    }
224
225    private void asyncExecute(Runnable runnable) {
226        if (mHandlerThread != null && !mHandlerThread.isAlive()) {
227            mHandlerThread.start();
228        }
229        if (mHandler2 != null) {
230            mHandler2.post(runnable);
231        }
232    }
233
234    @SuppressLint("WrongConstant")
235    @Override
236    public void onCreate() {
237        super.onCreate();
238        boolean disableInnerNetworkCheck = PlayerConfig.getInstance().getBooleanConfig("DISABLE_INNER_NETWORK_CHECK", false);
239        AILog.d(TAG, "disableInnerNetworkCheck =", disableInnerNetworkCheck);
240        if(!disableInnerNetworkCheck) {
241            connectionChangeReceiver = new ConnectionChangeReceiver();
242            IntentFilter filter = new IntentFilter();
243            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
244            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
245                context.registerReceiver(connectionChangeReceiver, filter, Context.RECEIVER_EXPORTED);
246            } else {
247                context.registerReceiver(connectionChangeReceiver, filter);
248            }
249
250        }
251    }
252
253
254    @Override
255    public void onJoin() {
256        synchronized (topicsLock) {
257            for (String topic : subscribedTopics) {
258                bc.subscribe(topic);
259            }
260            bc.subscribe(mTopicList);
261            bc.subscribeCall(mCallList);
262        }
263        uploadData();
264        super.onJoin();
265    }
266
267    @Override
268    public void onExit() {
269        //just for dm callback
270        if (bc != null) {
271            bc.unsubscribe(mTopicList);
272            bc.unsubscribeCall(mCallList);
273        }
274        super.onExit();
275    }
276
277    @Override
278    public void onDestroy() {
279        super.onDestroy();
280        mHandler = null;
281        mHandler2 = null;
282
283        try {
284            if (context != null && connectionChangeReceiver != null) {
285                context.unregisterReceiver(connectionChangeReceiver);
286            }
287        } catch (Exception e) {
288        }
289        if (TTSEngine.getInstanceSnapshot() != null) {
290            TTSEngine.getInstanceSnapshot().destroy();
291        }
292        if (ASREngine.getInstanceSnapshot() != null) {
293            ASREngine.getInstanceSnapshot().destroy();
294        }
295        if (WakeupEngine.getInstanceSnapshot() != null) {
296            WakeupEngine.getInstanceSnapshot().destroy();
297        }
298        if (VprintEngine.getInstanceSnapshot() != null) {
299            VprintEngine.getInstanceSnapshot().destroy();
300        }
301
302        if (mHandlerThread != null) {
303            mHandlerThread.quit();
304            mHandlerThread = null;
305        }
306        if (mHandlerThreadAsyn != null) {
307            mHandlerThreadAsyn.quit();
308            mHandlerThreadAsyn = null;
309        }
310        if (subscribedTopics != null) {
311            subscribedTopics.clear();
312        }
313    }
314
315    @Override
316    public String getName() {
317        return TAG;
318    }
319
320    @Override
321    public String getAddress() {
322        return DDS.BUS_SERVER_ADDR;
323    }
324
325    @Override
326    public BusClient.RPCResult onCall(String url, Object... args) throws Exception {
327        if (url.equals(ProcessorCallUtil.CALL_GET_DM_OPERATION)) {
328            if (args.length > 0) {
329                JSONObject dmJsonObject = new JSONObject(args[0].toString());
330                if (mDMCallback != null) {
331                    AILog.userO(TAG, "DMCallback.onDMResult result =", dmJsonObject.toString());
332                    JSONObject dmOperationJson = mDMCallback.onDMResult(dmJsonObject);
333                    AILog.userI(TAG, "DMCallback.onDMResult input =", (dmOperationJson != null ? dmOperationJson.toString() : "null"));
334                    if (dmOperationJson != null) {
335                        return new BusClient.RPCResult(dmOperationJson.toString());
336                    }
337                } else {
338                    return new BusClient.RPCResult(dmJsonObject.toString());
339                }
340            }
341        } else if (TextUtils.equals(url, Topic.Agent.CALL_PLAYER_AUDIO_FOCUS_NOTIFY.TOPIC_NAME)) {
342            if (args.length > 0) {
343                JSONObject audioFocusNotify = new JSONObject(args[0].toString());
344                if (mAudioFocusCallback != null) {
345                    String action = audioFocusNotify.optString("action");
346                    AILog.userO(TAG, "AudioFocusCallback result =", action);
347                    if (TextUtils.equals(action, "request")) {
348                        mAudioFocusCallback.onRequest();
349                    } else if (TextUtils.equals(action, "abandon")) {
350                        mAudioFocusCallback.onAbandon();
351                    }
352                } else {
353                    AILog.w(TAG, "url : " + url + "  mAudioFocusCallback is null");
354                }
355            }
356        }
357        return null;
358    }
359
360    private void handleCost(String topic, String result) {
361        try {
362            if (TextUtils.equals(topic, "context.input.text")) {
363                JSONObject dataObj = new JSONObject(result);
364                String innerTopic = dataObj.optString("topic");
365                if (TextUtils.equals("asr.speech.result", innerTopic)) {
366                    CostTimeUtil.getInstance().mCloudAsrCost.agentSendInputTextResult(result);
367                } else {
368                    CostTimeUtil.getInstance().mCloudAsrCost.agentSendInputVarResult(result);
369                }
370            } else if (TextUtils.equals(topic, "context.output.text")) {
371                CostTimeUtil.getInstance().mCloudAsrCost.agentSendOutputTextResult(result);
372            }
373        } catch (Exception e) {
374            AIJavaException.printException(e);
375        }
376    }
377
378    public void feedbackNativeCommandResult(String nativeCommand, JSONObject resultObj) {
379        if (mNativeCmdResultMap.containsKey(nativeCommand)) {
380            mNativeCmdResultMap.put(nativeCommand, resultObj);
381            if (mNativeCmdCountDownLatch != null) {
382                mNativeCmdCountDownLatch.countDown();
383                mNativeCmdCountDownLatch = null;
384            }
385            setNativeState(false);
386        }
387    }
388
389    private JSONObject doNativeCommandCall(final NativeCommandObserver observer, final String topic, final String paramStr) {
390        setNativeState(true);
391        if (observer instanceof NativeCommandAsyncObserver) {
392            // 异步实现方法
393            mNativeCmdCountDownLatch = new CountDownLatch(1);
394            mNativeCmdResultMap.put(topic, new JSONObject());
395            ((NativeCommandAsyncObserver) observer).onNativeCommandAsync(topic, paramStr);
396            try {
397                mNativeCmdCountDownLatch.await(10, TimeUnit.SECONDS);
398            } catch (Exception e) {
399            }
400            JSONObject result = JSONObjectUtil.deepCopy(mNativeCmdResultMap.get(topic));
401            mNativeCmdResultMap.remove(topic);
402            setNativeState(false);
403            return result;
404        } else {
405            // 同步实现方法
406            JSONObject nativeResult = observer.onNativeCommandCall(topic, paramStr);
407            setNativeState(false);
408            return nativeResult;
409        }
410    }
411
412    private void setNativeState(boolean enable) {
413        if (PlayerConfig.getInstance().getBooleanConfig(DDSConfig.K_DROP_AUDIO_WHEN_NATIVE)) {
414            try {
415                publishSticky(Topic.Dms.UpdateNativeState.TOPIC_NAME,
416                        new JSONObject().put(Topic.Dms.UpdateNativeState.ENABLE, enable));
417            } catch (JSONException e) {
418                e.printStackTrace();
419            }
420        }
421    }
422
423    private void doNativeCommand(JSONObject dmJsonObject) {
424        try {
425            JSONObject commandObj = dmJsonObject.optJSONObject("command");
426            AILog.d(TAG, "doNativeCommand = " + commandObj.toString());
427            String topic = commandObj.optString("api").replaceFirst(CommandObserver.COMMAND_SCHEME, "");
428            JSONObject param = commandObj.optJSONObject("param");
429            String paramStr = param != null ? param.toString() : "{}";
430            ArrayList<NativeCommandObserver> nativeObservers = mNativeCommandObservers.get(topic);
431            if (nativeObservers != null && nativeObservers.size() > 0) {
432                for (int i = 0; i < nativeObservers.size(); i++) {
433                    NativeCommandObserver observer = i < nativeObservers.size() ? nativeObservers.get(i) : null;
434                    if (observer == null) {
435                        break;
436                    }
437                    AILog.userO(TAG, "doNativeCommand onNativeCommandCall topic =", topic, ", paramStr =", paramStr);
438                    JSONObject resultObj = doNativeCommandCall(observer, topic.replaceFirst(CommandObserver.COMMAND_SCHEME, ""), paramStr);
439                    AILog.userI(TAG, "doNativeCommand onNativeCommandCall topic =", topic, ", resultObj =", (resultObj != null ? resultObj.toString() : "null"));
440                    if (resultObj != null) {
441                        // onNativeCommandCall方法返回不为null,就认为处理了 NativeCommand
442                        dmJsonObject.remove("command");
443                        String nlg = resultObj.optString("nlg");
444                        boolean shouldEndSession = resultObj.optBoolean("shouldEndSession");
445                        JSONObject customNativeCommandResult = resultObj.optJSONObject("customNativeCommandResult");
446                        if (!TextUtils.isEmpty(nlg)) {
447                            CommonUtil.changeTaskNlg(dmJsonObject, nlg);
448                        }
449                        if (shouldEndSession) {
450                            CommonUtil.changeTaskShouldEndSession(dmJsonObject, true);
451                        }
452                        if (customNativeCommandResult != null) {
453                            dmJsonObject.put("customNativeCommandResult", customNativeCommandResult);
454                        }
455                    }
456                }
457            }
458            ProcessorTopicUtil.publishTaskOperation(bc, dmJsonObject);
459        } catch (Exception e) {
460            e.printStackTrace();
461        }
462    }
463
464    private void doInspire(JSONObject dmJsonObject) {
465        try {
466            JSONArray inspireArr = dmJsonObject.optJSONArray("inspire");
467            JSONObject inspireSpeakObj = dmJsonObject.optJSONObject("inspireSpeak");
468            AILog.d(TAG, "doInspire = " + inspireArr.toString());
469            JSONObject inspireObj = JSONObjectUtil.create()
470                    .put("inspire", inspireArr)
471                    .put("speak", inspireSpeakObj)
472                    .put("sessionId",dmJsonObject.optString("sessionId"))
473                    .build();
474            String topic = Topic.Sys.MSG_SYS_NATIVE_INSPIRE;
475            ArrayList<NativeCommandObserver> nativeObservers = mNativeCommandObservers.get(topic);
476            boolean isCustomDeal = false;// 判断用户是否已处理此消息
477            if (nativeObservers != null && nativeObservers.size() > 0) {
478                for (int i = 0; i < nativeObservers.size(); i++) {
479                    NativeCommandObserver observer = nativeObservers.get(i);
480                    if (observer == null) {
481                        break;
482                    }
483                    isCustomDeal = true;
484                    AILog.userO(TAG, "doInspire onNativeCommandCall topic =", topic, ", paramStr =", inspireObj.toString());
485                    JSONObject resultObj = doNativeCommandCall(observer, topic, inspireObj.toString());
486                    AILog.userI(TAG, "doInspire onNativeCommandCall topic =", topic, ", resultObj =", (resultObj != null ? resultObj.toString() : "null"));
487                    if (resultObj != null) {
488                        String nlg = resultObj.optString("nlg");
489                        JSONObject selectInspireItemObj = resultObj.optJSONObject("selectInspireItem");
490                        if (!TextUtils.isEmpty(nlg)) {
491                            CommonUtil.changeTaskNlg(dmJsonObject, nlg);
492                        }
493                        if (selectInspireItemObj != null) {
494                            CommonUtil.changeTaskInspireItem(dmJsonObject, selectInspireItemObj);
495                        }
496                        // 一句话多意图V2 使用数据拦截返回的结果
497                        if (ProcessorConfig.ENABLE_MULTIINTENT_SEQUENCE) {
498                            ProcessorTopicUtil.publishTaskOperation(bc, dmJsonObject.put("inspire", resultObj.optJSONArray("inspire")));
499                            return;
500                        }
501                    }
502                }
503            }
504            if (isCustomDeal && !ProcessorConfig.ENABLE_MULTIINTENT_SEQUENCE) {
505                dmJsonObject.remove("inspire");
506            }
507            ProcessorTopicUtil.publishTaskOperation(bc, dmJsonObject);
508        } catch (Exception e) {
509            e.printStackTrace();
510        }
511    }
512
513    private void doTaskCallback(JSONObject dmJsonObject) {
514        try {
515            DMTaskCallback.Type type = DMTaskCallback.Type.getTypeByVal(dmJsonObject.optString("from"));
516            if (mDMTaskCallback != null) {
517                AILog.userO(TAG, "doTaskCallback result =", dmJsonObject.toString());
518                InterceptorImpl.doDMTaskCallbackBegin(dmJsonObject);
519                JSONObject dmTaskOperationJson = mDMTaskCallback.onDMTaskResult(dmJsonObject, type);
520                InterceptorImpl.doDMTaskCallbackEnd(dmTaskOperationJson);
521                AILog.userI(TAG, "doTaskCallback input =", (dmTaskOperationJson != null ? dmTaskOperationJson.toString() : "null"));
522                if (dmTaskOperationJson != null) {
523                    dmTaskOperationJson.put("watchId", dmJsonObject.optString("watchId", ""));
524                }
525                ProcessorTopicUtil.publishTaskOperation(bc, dmTaskOperationJson != null ? dmTaskOperationJson : dmJsonObject);
526            } else {
527                //return raw obj
528                ProcessorTopicUtil.publishTaskOperation(bc, dmJsonObject);
529            }
530        } catch (Exception e) {
531            e.printStackTrace();
532        }
533    }
534
535    private void onInnerThreadMessage(String topic, Object... parts) throws Exception {
536        AILog.d(TAG, "onInnerThreadMessage topic = " + topic);
537        if (topic.startsWith(CommandObserver.COMMAND_SCHEME)) {
538            ArrayList<CommandObserver> observers = commandObservers.get(topic);
539            if (observers != null && observers.size() > 0) {
540                for (int i = 0; i < observers.size(); i++) {
541                    CommandObserver observer = i < observers.size() ? observers.get(i) : null;
542                    if (observer == null) {
543                        break;
544                    }
545                    try {
546                        AILog.userO(TAG, "observer.command topic =", topic, ", params = ", ((parts != null && parts.length > 0) ? parts[0].toString() : "null"));
547                        observer.onCall(topic.replaceFirst(CommandObserver.COMMAND_SCHEME, ""), (parts != null && parts.length > 0) ? parts[0].toString() : null);
548                        AILog.d(TAG, "observer.onCall topic = " + topic + ", end...");
549                    } catch (Exception e) {
550                        throw new BusClient.BusClientImplementException(e);
551                    }
552                }
553            }
554        } else if (topic.startsWith(NativeApiObserver.NATIVE_API_SCHEME)) {
555            ArrayList<NativeApiObserver> observers = nativeApiObservers.get(topic);
556            if (observers != null) {
557                for (int i = 0; i < observers.size(); i++) {
558                    NativeApiObserver observer = i < observers.size() ? observers.get(i) : null;
559                    if (observer == null) {
560                        break;
561                    }
562                    try {
563                        AILog.userO(TAG, "observer.nativeApi topic =", topic, ", params = ", ((parts != null && parts.length > 0) ? parts[0].toString() : "null"));
564                        observer.onQuery(topic.replaceFirst(NativeApiObserver.NATIVE_API_SCHEME, ""), (parts != null && parts.length > 0) ? parts[0].toString() : null);
565                    } catch (Exception e) {
566                        throw new BusClient.BusClientImplementException(e);
567                    }
568                }
569            }
570        } else if (topic.equals(Topic.Processor.TaskCallback.TOPIC_NAME)) {
571            JSONObject dmJsonObject = (JSONObject) parts[0];
572            String watchType = dmJsonObject.optString("watchType");
573            if (TextUtils.equals(watchType, DDSConstant.DialogTaskWatchType.COMMAND_TASK)) {
574                doNativeCommand(dmJsonObject);
575            } else if (TextUtils.equals(watchType, DDSConstant.DialogTaskWatchType.COMM_TASK)) {
576                doTaskCallback(dmJsonObject);
577            } else if (TextUtils.equals(watchType, DDSConstant.DialogTaskWatchType.INSPIRE_TASK)) {
578                doInspire(dmJsonObject);
579            }
580        } else if (topic.equals(Topic.Dla.SetAuthCode.TOPIC_NAME)) {
581            if (bc != null) {
582                bc.removeSticky(topic);
583            }
584            parseAuthBean(parts[0].toString());
585        } else if (topic.equals(Topic.Processor.WkupCallback.TOPIC_NAME)) {
586            JSONObject wkupRetJsonObject = (JSONObject) parts[0];
587            if (mDmWkupRetCallback != null) {
588                AILog.userO(TAG, "onDMWkupRetCallback result =", wkupRetJsonObject.toString());
589                JSONObject wkupRetOperationJson = mDmWkupRetCallback.onDMWkupRetCallback(wkupRetJsonObject);
590                AILog.userI(TAG, "onDMWkupRetCallback input =", (wkupRetOperationJson != null ? wkupRetOperationJson.toString() : "null"));
591                if (wkupRetOperationJson != null) {
592                    wkupRetOperationJson.put("watchId", wkupRetJsonObject.optString("watchId", ""));
593                }
594                ProcessorTopicUtil.publishWkupOperationRet(bc, wkupRetOperationJson != null ? wkupRetOperationJson : wkupRetJsonObject);
595            } else {
596                //return raw obj
597                AILog.d(TAG, "return raw obj publishWkupOperationRet wkupRetJsonObject");
598                ProcessorTopicUtil.publishWkupOperationRet(bc, wkupRetJsonObject);
599            }
600        } else {
601            ArrayList<MessageObserver> observers = messageObservers.get(topic);
602            try {
603                if (observers != null) {
604                    Object obj = null;
605                    if (parts != null && parts.length > 0) {
606                        obj = parts[0];
607                    }
608                    AILog.d(TAG, "observer.onMessage topic = " + topic);
609                    String result = obj != null ? obj.toString() : null;
610
611                    for (int i = 0; i < observers.size(); i++) {
612                        MessageObserver observer = i < observers.size() ? observers.get(i) : null;
613                        if (observer == null) {
614                            break;
615                        }
616                        handleCost(topic, result);
617                        observer.onMessage(topic, result);
618                        AILog.d(TAG, "observer.onMessage topic = " + topic + ", end...");
619                    }
620                }
621            } catch (Exception e) {
622                AIJavaException.printException(e);
623            }
624        }
625    }
626
627    @Override
628    public void onMessage(final String topic, final Object... parts) throws Exception {
629        super.onMessage(topic, parts);
630        Runnable runnable = new Runnable() {
631            @Override
632            public void run() {
633                try {
634                    onInnerThreadMessage(topic, parts);
635                } catch (Exception e) {
636                    AIJavaException.printException(e);
637                }
638            }
639        };
640        if (isTopicNoBlocking(topic)) {
641            asyncExecuteNoBlocking(runnable);
642        } else {
643            asyncExecute(runnable);
644        }
645    }
646
647    public boolean isTopicNoBlocking(String topic) {
648        return topic.equals(Topic.Processor.WkupCallback.TOPIC_NAME);
649    }
650
651    /**
652     * 用于DLA模块内部进行setAuthCode操作
653     */
654    private void parseAuthBean(String data) throws JSONException, DDSNotInitCompleteException {
655        JSONObject authBeanJson = new JSONObject(data);
656        AuthInfo authInfo = new AuthInfo();
657        authInfo.setClientId(authBeanJson.optString(KEY_CLIENT_ID));
658        authInfo.setAuthCode(authBeanJson.optString(KEY_AUTH_CODE));
659        authInfo.setUserId(authBeanJson.optString(KEY_USER_ID));
660        authInfo.setCodeVerifier(authBeanJson.optString(KEY_CODE_VERIFIER));
661        authInfo.setRedirectUri(authBeanJson.optString(KEY_REDIRECT_URI));
662        setAuthCode(authInfo, new TokenListener() {
663            @Override
664            public void onSuccess(TokenResult tokenResult) {
665                AILog.d(TAG, "DLA setAuthCode successful, result is -> " + tokenResult.toString());
666            }
667
668            @Override
669            public void onError(int errId, String errMsg) {
670                String errInfo = String.format("errId=%s, errMsg=%s", errId, errMsg);
671                AILog.e(TAG, "DLA setAuthCode failed, errInfo -> " + errInfo);
672            }
673        });
674    }
675
676    /**
677     * 订阅command
678     *
679     * @param commands        commands数组
680     * @param commandObserver 当订阅的command被触发了,会通知到commandObserver
681     */
682    public void subscribe(final String[] commands, final CommandObserver commandObserver) {
683        AILog.userI(TAG, "subscribe commands =", (commands != null ? Arrays.toString(commands) : "null"));
684        Runnable runnable = new Runnable() {
685            @Override
686            public void run() {
687                for (String command : commands) {
688                    subscribeInner(command, commandObserver);
689                }
690            }
691        };
692        asyncExecute(runnable);
693    }
694
695    /**
696     * 订阅nativeCommand, 此command支持带返回值
697     *
698     * @param nativeCommands        commands数组
699     * @param nativeCommandObserver 当订阅的command被触发了,会通知到commandObserver
700     */
701    public void subscribeSync(final String[] nativeCommands, final NativeCommandObserver nativeCommandObserver) {
702        AILog.userI(TAG, "subscribeSync nativeCommands =", (nativeCommands != null ? Arrays.toString(nativeCommands) : "null"));
703        synchronized (mNativeCommandLock) {
704            for (String command : nativeCommands) {
705                mNativeCommandObservers.put(command, nativeCommandObserver);
706            }
707        }
708    }
709
710    /**
711     * 注销NativeCommandObserver. 注销后,这个observer将不再会接受到command的消息
712     *
713     * @param observer observer
714     */
715    public void unSubscribeSync(final NativeCommandObserver observer) {
716        AILog.userI(TAG, "unSubscribeSync NativeCommandObserver");
717        synchronized (mNativeCommandLock) {
718            mNativeCommandObservers.remove(observer);
719        }
720    }
721
722    /**
723     * 订阅command
724     *
725     * @param commands        commands数组
726     * @param commandObserver 当订阅的command被触发了,会通知到commandObserver
727     */
728    public void subscribeSync(final String[] commands, final CommandObserver commandObserver) {
729        AILog.userI(TAG, "subscribeSync commands =", (commands != null ? Arrays.toString(commands) : "null"));
730        final Object lock = new Object();
731        synchronized (lock) {
732            Runnable runnable = new Runnable() {
733                @Override
734                public void run() {
735                    synchronized (lock) {
736                        for (String command : commands) {
737                            subscribeInner(command, commandObserver);
738                        }
739                        lock.notifyAll();
740                    }
741                }
742            };
743            asyncExecute(runnable);
744            try {
745                lock.wait();
746            } catch (Exception e) {
747                AIJavaException.printException(e);
748            }
749        }
750    }
751
752
753    /**
754     * 订阅command
755     *
756     * @param command         单个commad
757     * @param commandObserver 当订阅的command被触发了,会通知到commandObserver
758     */
759    public void subscribe(final String command, final CommandObserver commandObserver) {
760        AILog.userI(TAG, "subscribe command =", command);
761        Runnable runnable = new Runnable() {
762            @Override
763            public void run() {
764                subscribeInner(command, commandObserver);
765            }
766        };
767        asyncExecute(runnable);
768    }
769
770    /**
771     * 订阅command
772     */
773    private void subscribeInner(String command, CommandObserver commandObserver) {
774        String topic = CommandObserver.COMMAND_SCHEME + command;
775        commandObservers.put(topic, commandObserver);
776    }
777
778    /**
779     * 订阅nativeApi
780     *
781     * @param nativeApis        nativeApi数组
782     * @param nativeApiObserver 当订阅的cnativeApi被触发了,会通知到nativeApiObserver
783     */
784    public void subscribe(final String[] nativeApis, final NativeApiObserver nativeApiObserver) {
785        AILog.userI(TAG, "subscribe nativeApis =", (nativeApis != null ? Arrays.toString(nativeApis) : "null"));
786        Runnable runnable = new Runnable() {
787            @Override
788            public void run() {
789                for (String nativeApi : nativeApis) {
790                    subscribeInner(nativeApi, nativeApiObserver);
791                }
792            }
793        };
794        asyncExecute(runnable);
795    }
796
797    /**
798     * 订阅nativeApi
799     *
800     * @param nativeApis        nativeApi数组
801     * @param nativeApiObserver 当订阅的cnativeApi被触发了,会通知到nativeApiObserver
802     */
803    public void subscribeSync(final String[] nativeApis, final NativeApiObserver nativeApiObserver) {
804        AILog.userI(TAG, "subscribeSync nativeApis =", (nativeApis != null ? Arrays.toString(nativeApis) : "null"));
805        final Object lock = new Object();
806        synchronized (lock) {
807            Runnable runnable = new Runnable() {
808                @Override
809                public void run() {
810                    synchronized (lock) {
811                        for (String nativeApi : nativeApis) {
812                            subscribeInner(nativeApi, nativeApiObserver);
813                        }
814                        lock.notifyAll();
815                    }
816                }
817            };
818            asyncExecute(runnable);
819            try {
820                lock.wait();
821            } catch (Exception e) {
822                AIJavaException.printException(e);
823            }
824        }
825    }
826
827    /**
828     * 订阅nativeApi
829     *
830     * @param nativeApi         单个的nativeApi
831     * @param nativeApiObserver 当订阅的cnativeApi被触发了,会通知到nativeApiObserver
832     */
833    public void subscribe(final String nativeApi, final NativeApiObserver nativeApiObserver) {
834        AILog.userI(TAG, "subscribe nativeApi =", nativeApi);
835        Runnable runnable = new Runnable() {
836            @Override
837            public void run() {
838                subscribeInner(nativeApi, nativeApiObserver);
839            }
840        };
841        asyncExecute(runnable);
842    }
843
844    /**
845     * 订阅nativeApi
846     */
847    private void subscribeInner(String nativeApi, NativeApiObserver nativeApiObserver) {
848        String topic = NativeApiObserver.NATIVE_API_SCHEME + nativeApi;
849        nativeApiObservers.put(topic, nativeApiObserver);
850    }
851
852
853    /**
854     * 订阅messages
855     *
856     * @param messages        message数组
857     * @param messageObserver 当订阅的messages被触发了,会通知到messageObserver
858     */
859    public void subscribe(final String[] messages, final MessageObserver messageObserver) {
860        AILog.userI(TAG, "subscribe messages =", (messages != null ? Arrays.toString(messages) : "null"));
861        Runnable runnable = new Runnable() {
862            @Override
863            public void run() {
864                for (String message : messages) {
865                    subscribeInner(message, messageObserver);
866                }
867            }
868        };
869        asyncExecute(runnable);
870    }
871
872    /**
873     * 订阅messages
874     *
875     * @param messages        message数组
876     * @param messageObserver 当订阅的messages被触发了,会通知到messageObserver
877     */
878    public void subscribeSync(final String[] messages, final MessageObserver messageObserver) {
879        AILog.userI(TAG, "subscribeSync messages =", (messages != null ? Arrays.toString(messages) : "null"));
880        final Object lock = new Object();
881        synchronized (lock) {
882            Runnable runnable = new Runnable() {
883                @Override
884                public void run() {
885                    synchronized (lock) {
886                        for (String message : messages) {
887                            subscribeInner(message, messageObserver);
888                        }
889                        lock.notifyAll();
890                    }
891                }
892            };
893            asyncExecute(runnable);
894            try {
895                lock.wait();
896            } catch (Exception e) {
897                AIJavaException.printException(e);
898            }
899        }
900    }
901
902    /**
903     * 订阅message
904     *
905     * @param message         单个的message
906     * @param messageObserver 当订阅的message被触发了,会通知到messageObserver
907     */
908    public void subscribe(final String message, final MessageObserver messageObserver) {
909        AILog.userI(TAG, "subscribe message =", message);
910        Runnable runnable = new Runnable() {
911            @Override
912            public void run() {
913                subscribeInner(message, messageObserver);
914            }
915        };
916        asyncExecute(runnable);
917    }
918
919    /**
920     * 订阅message
921     */
922    private void subscribeInner(String message, MessageObserver messageObserver) {
923        messageObservers.put(message, messageObserver);
924    }
925
926    /**
927     * 注销CommandObserver. 注销后,这个observer将不再会接受到command的消息
928     *
929     * @param observer observer
930     */
931    public void unSubscribe(final CommandObserver observer) {
932        AILog.userI(TAG, "unSubscribe CommandObserver");
933        Runnable runnable = new Runnable() {
934            @Override
935            public void run() {
936                commandObservers.remove(observer);
937            }
938        };
939        asyncExecute(runnable);
940    }
941
942    /**
943     * 注销CommandObserver. 注销后,这个observer将不再会接受到command的消息
944     *
945     * @param observer observer
946     */
947    public void unSubscribeSync(final CommandObserver observer) {
948        AILog.userI(TAG, "unSubscribeSync CommandObserver");
949        final Object lock = new Object();
950        synchronized (lock) {
951            Runnable runnable = new Runnable() {
952                @Override
953                public void run() {
954                    synchronized (lock) {
955                        commandObservers.remove(observer);
956                        lock.notifyAll();
957                    }
958                }
959            };
960            asyncExecute(runnable);
961            try {
962                lock.wait();
963            } catch (Exception e) {
964                AIJavaException.printException(e);
965            }
966        }
967    }
968
969    /**
970     * 注销NativeApiObserver. 注销后,这个observer将不再会接受到NativeApi的消息
971     *
972     * @param observer observer
973     */
974    public void unSubscribe(final NativeApiObserver observer) {
975        AILog.userI(TAG, "unSubscribe NativeApiObserver");
976        Runnable runnable = new Runnable() {
977            @Override
978            public void run() {
979                nativeApiObservers.remove(observer);
980            }
981        };
982        asyncExecute(runnable);
983    }
984
985    /**
986     * 注销NativeApiObserver. 注销后,这个observer将不再会接受到NativeApi的消息
987     *
988     * @param observer observer
989     */
990    public void unSubscribeSync(final NativeApiObserver observer) {
991        AILog.userI(TAG, "unSubscribeSync NativeApiObserver");
992        final Object lock = new Object();
993        synchronized (lock) {
994            Runnable runnable = new Runnable() {
995                @Override
996                public void run() {
997                    synchronized (lock) {
998                        nativeApiObservers.remove(observer);
999                        lock.notifyAll();
1000                    }
1001                }
1002            };
1003            asyncExecute(runnable);
1004            try {
1005                lock.wait();
1006            } catch (Exception e) {
1007                AIJavaException.printException(e);
1008            }
1009        }
1010    }
1011
1012
1013    /**
1014     * 注销MessageObserver. 注销后,这个observer将不再会接受到messages的消息
1015     *
1016     * @param observer observer
1017     */
1018    public void unSubscribe(final MessageObserver observer) {
1019        AILog.userI(TAG, "unSubscribe MessageObserver");
1020        Runnable runnable = new Runnable() {
1021            @Override
1022            public void run() {
1023                messageObservers.remove(observer);
1024            }
1025        };
1026        asyncExecute(runnable);
1027    }
1028
1029    /**
1030     * 注销MessageObserver. 注销后,这个observer将不再会接受到messages的消息
1031     *
1032     * @param observer observer
1033     */
1034    public void unSubscribeSync(final MessageObserver observer) {
1035        AILog.userI(TAG, "unSubscribeSync MessageObserver");
1036        final Object lock = new Object();
1037        synchronized (lock) {
1038            Runnable runnable = new Runnable() {
1039                @Override
1040                public void run() {
1041                    synchronized (lock) {
1042                        messageObservers.remove(observer);
1043                        lock.notifyAll();
1044                    }
1045                }
1046            };
1047            asyncExecute(runnable);
1048            try {
1049                lock.wait();
1050            } catch (Exception e) {
1051                AIJavaException.printException(e);
1052            }
1053        }
1054    }
1055
1056
1057    /**
1058     * 反馈native api的执行结果
1059     *
1060     * @param nativeApi dui平台上技能定义的nativeapi
1061     * @param duiWidget duiwidget实例
1062     */
1063    public void feedbackNativeApiResult(String nativeApi, DuiWidget duiWidget) {
1064        AILog.userI(TAG, "feedbackNativeApiResult nativeApi =", nativeApi, ", duiWidget =", (duiWidget != null ? duiWidget.getJsonObject().toString() : "null"));
1065        if (bc == null) {
1066            return;
1067        }
1068        if (null == duiWidget) {
1069            JSONObject object = JSONObjectUtil.create()
1070                    .put("errId", "080012")
1071                    .put("errMsg", "查询异常,暂时不能为你提供服务")
1072                    .put(Topic.Processor.InputDmError.NATIVE_API, NativeApiObserver.NATIVE_API_SCHEME + nativeApi)
1073                    .build();
1074
1075            ProcessorTopicUtil.publishInputDmError(bc, object);
1076        } else {
1077            JSONObject jsonObject = duiWidget.getJsonObject();
1078            try {
1079                jsonObject.put(Topic.Processor.InputDmData.NATIVE_API, NativeApiObserver.NATIVE_API_SCHEME + nativeApi);
1080                ProcessorTopicUtil.publishInputDmData(bc, jsonObject);
1081            } catch (JSONException e) {
1082                AIJavaException.printException(e);
1083            }
1084        }
1085    }
1086
1087    public int getUiagentProt() {
1088        boolean enalbeDynamicUiagentPort = PlayerConfig.getInstance().getBooleanConfig("ENABLE_DYNAMIC_UIAGENT_PORT");
1089        int port = 50002;
1090        if (enalbeDynamicUiagentPort) {
1091            String version = LocalKeysUtil.getInstance().getString("/local_keys/upload/productVersion");
1092            if (!TextUtils.isEmpty(version)) {
1093                port += Integer.parseInt(version);
1094                return port;
1095            }
1096        }
1097        AILog.userI(TAG, "getUiagentProt result =", port);
1098        return port;
1099    }
1100
1101    /**
1102     * 获取资源包中H5资源的路径
1103     *
1104     * @return String H5资源index.html的路径
1105     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1106     */
1107    public String getValidH5Path() throws DDSNotInitCompleteException {
1108        checkInitComplete();
1109        String customHome = LocalKeysUtil.getInstance().getString(LocalKeys.Daemon.LOCAL_KEY_CUSTOM_HOME);
1110        String uiPath = null;
1111        if (TextUtils.isEmpty(customHome)) {
1112            uiPath = "file://" + context.getFilesDir().getAbsolutePath() + "/dds/custom/ui/index.html";
1113        } else {
1114            uiPath = "file://" + customHome + "/ui/index.html";
1115        }
1116        File indexFile = new File(uiPath.replace("file://", ""));
1117        if (!indexFile.exists()) {
1118            AILog.userI(TAG, "getValidH5Path result = null");
1119            return null;
1120        }
1121        int port = getUiagentProt();
1122        uiPath = uiPath + "?port=" + port;
1123        AILog.userI(TAG, "getValidH5Path result =", uiPath);
1124        return uiPath;
1125    }
1126
1127//    public void push(String message) {
1128//        throw new NotImplementedException("need to implement");
1129//    }
1130
1131
1132    /**
1133     * 此方法在Agent中已弃用,参见{@link WakeupEngine#getWakeupWords()}
1134     * 获取当前的唤醒词
1135     *
1136     * @return String[] 唤醒词数组
1137     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1138     */
1139    @Deprecated
1140    public String[] getWakeupWords() throws DDSNotInitCompleteException {
1141        return getWakeupEngine().getWakeupWords();
1142    }
1143
1144
1145    /**
1146     * 此方法在Agent中已弃用,参见{@link WakeupEngine#getMinorWakeupWord()}
1147     * 获取当前的副唤醒词
1148     *
1149     * @return String 副唤醒词, 若无则返回null
1150     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1151     */
1152    @Deprecated
1153    public String getMinorWakeupWord() throws DDSNotInitCompleteException {
1154        return getWakeupEngine().getMinorWakeupWord();
1155    }
1156
1157
1158    /**
1159     * 此方法在Agent中已弃用,参见{@link WakeupEngine#enableWakeup()}
1160     * 开启唤醒
1161     *
1162     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1163     */
1164    @Deprecated
1165    public void enableWakeup() throws DDSNotInitCompleteException {
1166        getWakeupEngine().enableWakeup();
1167    }
1168
1169    /**
1170     * 此方法在Agent中已弃用,参见{@link WakeupEngine#disableWakeup()}
1171     * 关闭唤醒
1172     *
1173     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1174     */
1175    @Deprecated
1176    public void disableWakeup() throws DDSNotInitCompleteException {
1177        getWakeupEngine().disableWakeup();
1178    }
1179
1180    /**
1181     * 开启新对话
1182     * <p>
1183     * 播报提示音后进入识别。若当前正在对话中,会先结束当前对话,再开启新对话。
1184     *
1185     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1186     */
1187    public void startDialog() throws DDSNotInitCompleteException {
1188        startDialog(null);
1189    }
1190
1191    /**
1192     * 开启新对话
1193     * <p>
1194     * 播报提示音后进入识别。若当前正在对话中,会先结束当前对话,再开启新对话。
1195     *
1196     * @param jsonObject 自定义配置信息,可以配置回复语greeting等
1197     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1198     */
1199    public void startDialog(JSONObject jsonObject) throws DDSNotInitCompleteException {
1200        checkInitComplete();
1201        AILog.userI(TAG, "startDialog input =", jsonObject);
1202        if (bc != null) {
1203            ProcessorTopicUtil.publishDialogCtrl(bc, "start", jsonObject);
1204        } else {
1205            AILog.e(TAG, "startDialog failed due to null busclient");
1206        }
1207    }
1208
1209    /**
1210     * 停止当前对话,包括停止合成,取消识别等
1211     *
1212     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1213     */
1214    public void stopDialog() throws DDSNotInitCompleteException {
1215        stopDialog(null);
1216    }
1217
1218    public void breakDialog() throws DDSNotInitCompleteException {
1219        breakDialog(null);
1220    }
1221
1222    /**
1223     * 取消
1224     * @param jsonObject
1225     * @throws DDSNotInitCompleteException
1226     */
1227    public void breakDialog(JSONObject jsonObject) throws DDSNotInitCompleteException {
1228        checkInitComplete();
1229        AILog.userI(TAG, "breakDialog input =", jsonObject);
1230        if (bc != null) {
1231            ProcessorTopicUtil.publishDialogCtrl(bc, "break", jsonObject);
1232        } else {
1233            AILog.e(TAG, "startDialog failed due to null busclient");
1234        }
1235    }
1236
1237    /**
1238     * 打开tip提示音
1239     * <p>
1240     *
1241     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1242     */
1243    public void openTip() throws DDSNotInitCompleteException {
1244        checkInitComplete();
1245        AILog.userI(TAG, "openTip");
1246        if (bc != null) {
1247            ProcessorTopicUtil.publishTipCtrl(bc, "open");
1248        } else {
1249            AILog.e(TAG, "openTip failed due to null busclient");
1250        }
1251    }
1252
1253    /**
1254     * 关闭tip提示音
1255     *
1256     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1257     */
1258    public void closeTip() throws DDSNotInitCompleteException {
1259        checkInitComplete();
1260        AILog.userI(TAG, "closeTip");
1261        if (bc != null) {
1262            ProcessorTopicUtil.publishTipCtrl(bc, "close");
1263        } else {
1264            AILog.e(TAG, "closeTip failed due to null busclient");
1265        }
1266    }
1267
1268    /**
1269     * 停止当前对话,包括停止合成,取消识别等
1270     *
1271     * @param jsonObject 自定义配置信息,可以配置合成文本,内部会在合成完该文本后停止当前对话
1272     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1273     */
1274    public void stopDialog(JSONObject jsonObject) throws DDSNotInitCompleteException {
1275        checkInitComplete();
1276        AILog.userI(TAG, "stopDialog input =", jsonObject);
1277        if (bc != null) {
1278            BusManager.getInstance().clearPendingMessage(NodeName.DMS);
1279            BusManager.getInstance().clearPendingMessage(NodeName.PROCESSOR);
1280            ProcessorTopicUtil.publishDialogCtrl(bc, "close", jsonObject);
1281        } else {
1282            AILog.e(TAG, "stopDialog failed due to null busclient");
1283        }
1284    }
1285
1286    /**
1287     * @param skill  技能名称, 必填
1288     * @param task   任务名称, 必填
1289     * @param intent 意图名称, 必填
1290     * @param slots  语义槽, key-value Json, 可选
1291     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1292     * @deprecated 主动触发意图的接口
1293     * <p>
1294     * 跳过识别和语义,直接进入指定的意图对话
1295     */
1296    @Deprecated
1297    public void triggerIntent(String skill, String task, String intent, String slots)
1298            throws DDSNotInitCompleteException {
1299        SkillIntent skillIntent = new SkillIntent("", task, intent, slots);
1300        skillIntent.setSkillName(skill);
1301        triggerIntent(skillIntent);
1302    }
1303
1304    /**
1305     * 主动触发意图的接口
1306     * <p>
1307     * 跳过识别和语义,直接进入指定的意图对话
1308     *
1309     * @param skillIntent 技能意图
1310     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1311     */
1312    public void triggerIntent(SkillIntent skillIntent) throws DDSNotInitCompleteException {
1313        AILog.userI(TAG, "triggerIntent input =", skillIntent.toString());
1314        checkInitComplete();
1315        if (bc != null) {
1316            ProcessorTopicUtil.publishInputIntent(bc, skillIntent.toJson());
1317        } else {
1318            AILog.e(TAG, "triggerIntent failed due to null busclient");
1319        }
1320    }
1321
1322    /**
1323     * 点击唤醒/停止识别/打断播报 操作接口
1324     *
1325     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1326     * @see com.aispeech.dui.dds.DDSConfigBuilder.DmBuilder#setClickMode
1327     */
1328    public void avatarClick() throws DDSNotInitCompleteException {
1329        avatarClick("");
1330    }
1331
1332    /**
1333     * 点击唤醒/停止识别/打断播报 操作接口
1334     *
1335     * @param greeting 在唤醒时附带一则欢迎语
1336     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1337     */
1338    public void avatarClick(String greeting) throws DDSNotInitCompleteException {
1339        checkInitComplete();
1340        AILog.userI(TAG, "avatarClick input =", greeting);
1341        if (bc != null) {
1342            ProcessorTopicUtil.publishAvatarClick(bc, "tap", greeting);
1343        } else {
1344            AILog.e(TAG, "avatarClick failed due to null busclient");
1345        }
1346    }
1347
1348    /**
1349     * 按下按键接口
1350     *
1351     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1352     */
1353    public void avatarPress() throws DDSNotInitCompleteException {
1354        checkInitComplete();
1355        AILog.userI(TAG, "avatarPress");
1356        if (bc != null) {
1357            ProcessorTopicUtil.publishAvatarClick(bc, "press", "");
1358        } else {
1359            AILog.e(TAG, "avatarPress failed due to null busclient");
1360        }
1361    }
1362
1363    /**
1364     * 释放按键接口
1365     *
1366     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1367     */
1368    public void avatarRelease() throws DDSNotInitCompleteException {
1369        checkInitComplete();
1370        AILog.userI(TAG, "avatarRelease");
1371        if (bc != null) {
1372            ProcessorTopicUtil.publishAvatarClick(bc, "release", "");
1373        } else {
1374            AILog.e(TAG, "avatarRelease failed due to null busclient");
1375        }
1376    }
1377
1378    /**
1379     * 获取当前的产品资源版本号
1380     *
1381     * @return String 资源版本号
1382     */
1383    public String getResourceVersion() {
1384        String customHome = context.getFilesDir().getAbsolutePath() + "/dds/custom/";
1385        String localCustomHome = LocalKeysUtil.getInstance().getString(LocalKeys.Daemon.LOCAL_KEY_CUSTOM_HOME);
1386        if (!TextUtils.isEmpty(localCustomHome))
1387            customHome = localCustomHome;
1388        String content = FileUtils.readFileContent(customHome + "/product.cfg");
1389        String result = null;
1390        if (!TextUtils.isEmpty(content)) {
1391
1392            JSONObject jsonContent = null;
1393            try {
1394                jsonContent = new JSONObject(content);
1395                result = jsonContent.optString("version");
1396            } catch (JSONException e) {
1397                AIJavaException.printException(e);
1398            }
1399        }
1400        AILog.userI(TAG, "getResourceVersion result =", result);
1401        return result;
1402    }
1403
1404    private String getFromKeys(String key, String defaultValue) throws DDSNotInitCompleteException {
1405        String value = getFromKeys(key);
1406        return (value != null && "".equals(value)) ? value : defaultValue;
1407    }
1408
1409    /**
1410     * Get the string value from local_keys using the key.
1411     *
1412     * @param key
1413     * @return null for no keys stored.
1414     */
1415    private String getFromKeys(String key) throws DDSNotInitCompleteException {
1416        checkInitPartComplete();
1417        return LocalKeysUtil.getInstance().getString(key);
1418    }
1419
1420    /**
1421     * 此方法已在Agent中弃用,建议使用TTSEngine中的同名方法{@link TTSEngine#speak(String, int)}
1422     * <p>
1423     * 播报文本,支持SSML
1424     * <p>
1425     * 当DDSConfig.K_TTS_MODE设置为"external"时,该接口无效,请直接调用外部TTS引擎的对应接口。
1426     *
1427     * @param text     播报文本
1428     * @param priority 优先级
1429     *                 <ul>
1430     *                 <li>优先级0-保留,与aios语音交互同级,仅限内部使用</li>
1431     *                 <li>优先级1-正常,默认选项,同级按序播放</li>
1432     *                 <li>优先级2-重要,可以插话优先级1,同级按序播放,播报完毕后继续播报刚才被插话的优先级1</li>
1433     *                 <li>优先级3-紧急,可以打断优先级1或优先级2,同级按序播放,播报完毕后播报下一句优先级2</li>
1434     *                 </ul>
1435     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1436     */
1437    @Deprecated
1438    public void speak(String text, int priority) throws DDSNotInitCompleteException {
1439        getTTSEngine().speak(text, priority);
1440    }
1441
1442    /**
1443     * 此方法已在Agent中弃用,建议使用TTSEngine中的同名方法{@link TTSEngine#speak(String, int, String, int)}
1444     * <p>
1445     * 播报文本,支持SSML
1446     * <p>
1447     * 当DDSConfig.K_TTS_MODE设置为"external"时,该接口无效,请直接调用外部TTS引擎的对应接口。
1448     *
1449     * @param text       播报文本
1450     * @param priority   优先级
1451     *                   <ul>
1452     *                   <li>优先级0-保留,与aios语音交互同级,仅限内部使用;</li>
1453     *                   <li>优先级1-正常,默认选项,同级按序播放;</li>
1454     *                   <li>优先级2-重要,可以插话优先级1,同级按序播放,播报完毕后继续播报刚才被插话的优先级1</li>
1455     *                   <li>优先级3-紧急,可以打断优先级1或优先级2,同级按序播放,播报完毕后播报下一句优先级2</li>
1456     *                   </ul>
1457     * @param ttsId      用于追踪该次播报的id,建议使用UUID.
1458     * @param audioFocus 该次播报的音频焦
1459     *                   <ul>
1460     *                   <li>priority == 0
1461     *                   {@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}</li>
1462     *                   <li>priority != 0
1463     *                   {@link android.media.AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}</li>
1464     *                   </ul>
1465     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1466     */
1467    @Deprecated
1468    public void speak(String text, int priority, String ttsId, int audioFocus) throws DDSNotInitCompleteException {
1469        getTTSEngine().speak(text, priority, ttsId, audioFocus);
1470    }
1471
1472    /**
1473     * 此方法已在Agent中弃用,建议使用TTSEngine中的同名方法{@link TTSEngine#shutup(String)}
1474     * <p>
1475     * 停止播报接口
1476     * <p>
1477     * 当DDSConfig.K_TTS_MODE设置为"external"时,该接口无效,请直接调用外部TTS引擎的对应接口。
1478     *
1479     * @param ttsId 和 {@link #speak(String, int, String, int)} ttsId.一致
1480     *              ttsId与speak接口的ttsId一致,则停止或者移除该播报;
1481     *              ttsId为空, 停止所有播报;
1482     *              ttsId为"0",停止当前播报.
1483     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1484     */
1485    @Deprecated
1486    public void shutup(String ttsId) throws DDSNotInitCompleteException {
1487        getTTSEngine().shutup(ttsId);
1488    }
1489
1490    public boolean updateAccessToken(String accessToken) throws DDSNotInitCompleteException {
1491        checkInitPartComplete();
1492        if(!TextUtils.isEmpty(accessToken)) {
1493            LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_AUTH_TYPE, AuthType.AISPEECH_ID);
1494        } else {
1495            LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_AUTH_TYPE, AuthType.PROFILE);
1496        }
1497        AuthCallUtil.callAuthUpdateAccessToken(getBusClient(), accessToken);
1498        return true;
1499    }
1500
1501    public boolean updateAccessToken(String accessToken,String userId) throws DDSNotInitCompleteException {
1502        checkInitPartComplete();
1503        if(!TextUtils.isEmpty(userId)) {
1504            LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_USER_ID, userId);
1505        } else {
1506            LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_USER_ID, "");
1507        }
1508        updateAccessToken(accessToken);
1509        return true;
1510    }
1511
1512    public String getAccessToken() throws DDSNotInitCompleteException {
1513        return LocalKeysUtil.getInstance().getString(LocalKeys.Auth.LOCAL_KEY_AUTH_ACCESS_TOKEN, "");
1514    }
1515
1516    /**
1517     * dca授权登录, 该方法需要在SDK初始化后使用;
1518     * 此方法应该在 OathSDK 登录获取 authCode 后使用
1519     * 与之对应的方法是 {@link Agent#clearAuthCode()}
1520     *
1521     * @param tokenListener 返回成功和错误的信息
1522     */
1523    public void setAuthCode(final AuthInfo authInfo, final TokenListener tokenListener) throws DDSNotInitCompleteException {
1524        checkInitComplete();
1525        AILog.userI(TAG, "setAuthCode input =", (authInfo != null ? authInfo.toString() : "null"));
1526        if (bc != null) {
1527            if (tokenListener == null) {
1528                AILog.w(TAG, "setAuthCode : TokenListener is null ");
1529                return;
1530            }
1531            if (authInfo == null) {
1532                tokenListener.onError(-1, "authInfo is null !!! ");
1533                return;
1534            }
1535            if (TextUtils.isEmpty(authInfo.getClientId())) {
1536                tokenListener.onError(-1, "clientId is empty");
1537                return;
1538            }
1539            if (TextUtils.isEmpty(authInfo.getUserId())) {
1540                tokenListener.onError(-1, "userId is empty");
1541                return;
1542            }
1543            if (TextUtils.isEmpty(authInfo.getAuthCode())) {
1544                tokenListener.onError(-1, "authCode is empty");
1545                return;
1546            }
1547            if (TextUtils.isEmpty(authInfo.getCodeVerifier())) {
1548                tokenListener.onError(-1, "codeVerifier is empty");
1549                return;
1550            }
1551            String authInfoStr = authInfo.toString();
1552            AILog.i(TAG, "setAuthCode = " + authInfoStr);
1553            String localOauthInfoStr = LocalKeysUtil.getInstance().getString(LocalKeys.Auth.LOCAL_KEY_OAUTH_INFO);
1554            AILog.i(TAG, "localOauthInfoStr = " + localOauthInfoStr);
1555            if (!TextUtils.isEmpty(localOauthInfoStr)) {
1556                try {
1557                    JSONObject oauthObj = new JSONObject(localOauthInfoStr);
1558                    if (TextUtils.equals(oauthObj.optString("userId"), authInfo.getUserId())
1559                            && TextUtils.equals(oauthObj.optString("authCode"), authInfo.getAuthCode())) {
1560                        AILog.i(TAG, "setAuthCode 重复调用,return。。。");
1561                        return;
1562                    }
1563                } catch (Exception e) {
1564                    e.printStackTrace();
1565                }
1566            }
1567
1568            LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_OAUTH_INFO, authInfoStr);
1569            OAuthSdk.initialize(context, authInfo.getClientId(), "dds");
1570            AccessTokenManager.getInstance(context).start(authInfo.getAuthCode(), authInfo.getCodeVerifier(), authInfo.getRedirectUri(), new TokenListener() {
1571                @Override
1572                public void onSuccess(TokenResult tokenResult) {
1573                    LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_AUTH_TYPE, AuthType.AISPEECH_ID);
1574                    LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_USER_ID, authInfo.getUserId());
1575                    bc.publishSticky(Topic.Auth.MSG_AUTH_CHANGE_TYPE);
1576                    LocalKeysUtil.getInstance().put(LocalKeys.OAuth.LOCAL_AUTH_TYPE_CLIEND_ID, authInfo.getClientId());
1577                    AILog.userO(TAG, "setAuthCode success");
1578                    tokenListener.onSuccess(tokenResult);
1579                }
1580
1581                @Override
1582                public void onError(int errorId, String error) {
1583                    AILog.userO(TAG, "setAuthCode failed");
1584                    tokenListener.onError(errorId, error);
1585                    LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_OAUTH_INFO, "");
1586                }
1587            });
1588        } else {
1589            AILog.e(TAG, "setAuthCode failed due to null busclient");
1590        }
1591    }
1592
1593    /**
1594     * 清除 authcode, 该方法需要在SDK初始化后使用;
1595     * 与之对应的方法是 {@link Agent#setAuthCode(AuthInfo, TokenListener)}
1596     *
1597     * @throws DDSNotInitCompleteException
1598     */
1599    public void clearAuthCode() throws DDSNotInitCompleteException {
1600        checkInitComplete();
1601        AILog.userI(TAG, "clearAuthCode");
1602        AccessTokenManager.getInstance(context).clearToken();
1603        AccessTokenManager.getInstance(context).clearTasks();
1604        LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_OAUTH_INFO, "");
1605        LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_AUTH_TYPE, AuthType.PROFILE);
1606        LocalKeysUtil.getInstance().put(LocalKeys.Auth.LOCAL_KEY_USER_ID, "");
1607        LocalKeysUtil.getInstance().put(LocalKeys.OAuth.LOCAL_AUTH_TYPE_CLIEND_ID, "");
1608        if (bc != null) {
1609            AuthCallUtil.callAuthUpdateAccessToken(bc, "");
1610            bc.publishSticky(Topic.Auth.MSG_AUTH_CHANGE_TYPE);
1611        } else {
1612            AILog.e(TAG, "clearAuthCode failed due to null busclient");
1613        }
1614    }
1615
1616    /**
1617     * 从 sdk 保存的数据里获取对应的值
1618     * <p>
1619     * 获取 oauth 的 userId 示例:
1620     * </p>
1621     * <p>
1622     * DDS.getInstance().getAgent().getLocalData("/local_keys/aispeech_user_id")
1623     * </p>
1624     *
1625     * @param key key
1626     * @return 值
1627     */
1628    public String getLocalData(String key) throws DDSNotInitCompleteException {
1629        checkInitComplete();
1630        return LocalKeysUtil.getInstance().getString(key);
1631    }
1632
1633    /**
1634     * 设置 数据到 sdk
1635     * <p>
1636     * 设置 oauth 的 userId 示例:
1637     * </p>
1638     * <p>
1639     * DDS.getInstance().getAgent().setLocalData("/local_keys/aispeech_user_id", "XXX");
1640     * </p>
1641     *
1642     * @param key   key
1643     * @param value value
1644     */
1645    public void setLocalData(String key, String value) throws DDSNotInitCompleteException {
1646        checkInitComplete();
1647        LocalKeysUtil.getInstance().put(key, value);
1648    }
1649
1650    /**
1651     * 设置场景模式
1652     *
1653     * @param mode 场景模式, 参考{@link DDSMode}
1654     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1655     */
1656    public void setDDSMode(int mode) throws DDSNotInitCompleteException {
1657        checkInitComplete();
1658        AILog.userI(TAG, "setDDSMode input =", mode);
1659        switch (mode) {
1660            case DDSMode.TTS_NORMAL:
1661            case DDSMode.TTS_SILENCE:
1662                PlayerNode.setTtsMode(mode);
1663                PlayerTopicUtil.publishSetPlayerMode(bc, mode);
1664                break;
1665            case DDSMode.PICKUP_FAR:
1666            case DDSMode.PICKUP_NEAR:
1667                if (bc != null) {
1668                    JSONObject obj = new JSONObject();
1669                    try {
1670                        obj.put("module", "pickup");
1671                        obj.put("config", mode == DDSMode.PICKUP_NEAR ? "near" : "far");
1672                        AgentTopicUtil.publishAgentSettings(bc, obj);
1673                    } catch (JSONException e) {
1674                        AILog.e(TAG, "setDDSMode failed due to JSONException");
1675                        AIJavaException.printException(e);
1676                    }
1677
1678                } else {
1679                    AILog.e(TAG, "setDDSMode failed due to null busclient");
1680                }
1681                break;
1682            default:
1683                AILog.e(TAG, "setDDSMode: not support.");
1684                break;
1685        }
1686
1687    }
1688
1689    /**
1690     * 设置dds策略
1691     *
1692     * @param duplexMode 策略 DuplexMode.HALF_DUPLEX:一问一答策略,  DuplexMode.FULL_DUPLEX:全双工策略
1693     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1694     */
1695    public void setDuplexMode(final DuplexMode duplexMode) throws DDSNotInitCompleteException {
1696        checkInitComplete();
1697        AILog.userI(TAG, "setDuplexMode input =", duplexMode.name());
1698        if (bc != null) {
1699            String value = duplexMode.name();
1700            ProcessorCallUtil.callSetDuplexMode(bc, value);
1701
1702        } else {
1703            AILog.e(TAG, "setDuplexMode failed due to null busclient");
1704        }
1705    }
1706
1707    /**
1708     * 获取当前的运行模式
1709     *
1710     * @return DuplexMode.HALF_DUPLEX:一问一答策略,  DuplexMode.FULL_DUPLEX:全双工策略
1711     * @throws DDSNotInitCompleteException
1712     */
1713    public DuplexMode getDuplexMode() throws DDSNotInitCompleteException {
1714        checkInitComplete();
1715        DuplexMode mode = DuplexMode.HALF_DUPLEX;
1716        String result = LocalKeysUtil.getInstance().getString("/local_keys/processor/policy");
1717        if (TextUtils.equals("FULL_DUPLEX", result)) {
1718            mode = DuplexMode.FULL_DUPLEX;
1719        }
1720        AILog.userI(TAG, "getDuplexMode result =", mode);
1721        return mode;
1722    }
1723
1724    /**
1725     * 设置是否使用voip功能
1726     *
1727     * @param enableVoip true/false
1728     * @throws DDSNotInitCompleteException
1729     */
1730    public void setEnableVoip(boolean enableVoip) throws DDSNotInitCompleteException {
1731        AILog.userI(TAG, "setEnableVoip input =", enableVoip);
1732        if (bc != null) {
1733            VoipTopicUtil.publishLocalVoipEnable(bc, enableVoip);
1734        }
1735    }
1736
1737    /**
1738     * 外部录音机拾音接口
1739     * <p>
1740     * 需要将DDSConfig.K_RECORDER_MODE设置为"external",该接口才会生效
1741     *
1742     * @param pcm 音频数据
1743     */
1744    public void feedPcm(byte[] pcm) {
1745        RecorderExNode.feedPcm(pcm);
1746    }
1747
1748    /**
1749     * 外部录音机拾音接口
1750     * <p>
1751     * 需要将DDSConfig.K_RECORDER_MODE设置为"external",该接口才会生效
1752     * 并且需要提前调用: DDS.getInstance().getAgent().setRecorderMode(Agent.RecorderMode.SBC)
1753     *
1754     * @param sbc sbc格式的音频数据
1755     */
1756    public void feedSbc(byte[] sbc) {
1757        RecorderExNode.feedSbc(sbc);
1758    }
1759
1760    /**
1761     * 外部录音机拾音接口
1762     * <p>
1763     * 需要将DDSConfig.K_RECORDER_MODE设置为"external",该接口才会生效
1764     * 并且需要提前调用: DDS.getInstance().getAgent().setRecorderMode(Agent.RecorderMode.OPUS)
1765     *
1766     * @param opus sbc格式的音频数据
1767     */
1768    public void feedOpus(byte[] opus) {
1769        RecorderExNode.feedOpus(opus);
1770    }
1771
1772    /**
1773     * 设置外部录音机拾音模式
1774     * <p>
1775     * 需要将DDSConfig.K_RECORDER_MODE设置为"external",该接口才会生效
1776     *
1777     * @param recorderMode 音频格式枚举
1778     */
1779    public void setRecorderMode(RecorderMode recorderMode) throws DDSNotInitCompleteException {
1780        checkInitComplete();
1781        AILog.userI(TAG, "setRecorderMode input =", recorderMode.name());
1782        if (bc != null) {
1783            RecorderExNode.setMode(recorderMode.name());
1784            RecorderTopicUtil.publishUpdateRecorderMode(bc, recorderMode.name());
1785        } else {
1786            AILog.e(TAG, "setRecorderMode failed due to null busclient");
1787        }
1788    }
1789
1790    /**
1791     * 外部TTS引擎注册接口
1792     * <p>
1793     * 需要将DDSConfig.K_TTS_MODE设置为"external",该接口才会生效
1794     *
1795     * @param listener TTS请求监听器。如果为空,则为解除注册
1796     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1797     */
1798    public void setTTSRequestListener(TTSRequestListener listener) throws DDSNotInitCompleteException {
1799        checkInitComplete();
1800        AILog.userI(TAG, "setTTSRequestListener");
1801        TtsExNode.subscribe(listener);
1802    }
1803
1804    /**
1805     * 外部TTS播报结束通知接口
1806     * <p>
1807     * 播放完毕后,需要调用该接口才能自动进入下一轮识别
1808     * <p>
1809     * 需要将DDSConfig.K_TTS_MODE设置为"external",该接口才会生效
1810     */
1811    public void notifyTTSEnd() {
1812        notifyTTSEnd(null);
1813    }
1814
1815    /**
1816     * 外部TTS播报结束通知接口
1817     * <p>
1818     * 播放完毕后,需要调用该接口才能自动进入下一轮识别
1819     * <p>
1820     * 需要将DDSConfig.K_TTS_MODE设置为"external",该接口才会生效
1821     */
1822    public void notifyTTSEnd(String ttsId) {
1823        TtsExNode.notifyTTSEnd(ttsId);
1824    }
1825
1826    /**
1827     * 更新词库接口
1828     * <p>
1829     * 更新指定词库的词条。
1830     * 更新结果可以通过sys.upload.result消息来获取。
1831     *
1832     * @param vocabs 需要更新的词库列表
1833     * @return 请求ID,用于追踪sys.upload.result
1834     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1835     * @see VocabIntent
1836     */
1837    public String updateVocabs(final VocabIntent... vocabs) throws DDSNotInitCompleteException {
1838        checkInitComplete();
1839
1840        final String id = UUIDUtil.randomUUID();
1841        if (bc != null) {
1842            asyncExecute(new Runnable() {
1843                @Override
1844                public void run() {
1845                    JSONObject obj = new JSONObject();
1846                    try {
1847                        obj.put("reqId", id);
1848
1849                        JSONArray arr = new JSONArray();
1850                        StringBuilder sb = new StringBuilder();
1851                        long t1 = System.currentTimeMillis();
1852                        for (VocabIntent s : vocabs) {
1853                            if (s.isNumberExtension() && s.getContents() != null) {
1854                                for (int i = 0; i < s.getContents().size(); i++) {
1855                                    String c = s.getContents().get(i);
1856                                    if (c != null && !c.contains(":")) {
1857                                        boolean transform2Num = StringUtil.str2Num(c, sb);
1858                                        if (transform2Num) {
1859                                            s.getContents().set(i, c + ":" + sb.toString());
1860                                        }
1861                                    }
1862                                }
1863                            }
1864                            arr.put(new JSONObject(s.toJson()));
1865                        }
1866                        AILog.d(TAG, "trans2Number cost " + (System.currentTimeMillis() - t1));
1867                        obj.put("vocabs", arr);
1868                    } catch (JSONException e) {
1869                        AILog.e(TAG, "updateVocabs failed due to JSONException");
1870                        AIJavaException.printException(e);
1871                    }
1872                    AILog.userI(TAG, "updateVocabs input =", obj.toString());
1873                    UploadTopicUtil.publishUploadVocabs(bc, obj,VocabIntent.ALL);
1874                }
1875            });
1876        } else {
1877            AILog.e(TAG, "updateVocabs failed due to null busclient");
1878        }
1879        return id;
1880    }
1881
1882    /**
1883     * 更新词库接口
1884     * <p>
1885     * 更新指定词库的词条。
1886     * 更新结果可以通过sys.upload.result消息来获取。
1887     *
1888     * @param mode VocabIntent.LOCAL VocabIntent.CLOUD
1889     * @param vocabs 需要更新的词库列表
1890     * @return 请求ID,用于追踪sys.upload.result
1891     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1892     * @see VocabIntent
1893     */
1894    public String updateVocabs(final int mode,final VocabIntent... vocabs) throws DDSNotInitCompleteException {
1895        checkInitComplete();
1896
1897        final String id = UUIDUtil.randomUUID();
1898        if (bc != null) {
1899            asyncExecute(new Runnable() {
1900                @Override
1901                public void run() {
1902                    JSONObject obj = new JSONObject();
1903                    try {
1904                        obj.put("reqId", id);
1905
1906                        JSONArray arr = new JSONArray();
1907                        StringBuilder sb = new StringBuilder();
1908                        long t1 = System.currentTimeMillis();
1909                        for (VocabIntent s : vocabs) {
1910                            if (s.isNumberExtension() && s.getContents() != null) {
1911                                for (int i = 0; i < s.getContents().size(); i++) {
1912                                    String c = s.getContents().get(i);
1913                                    if (c != null && !c.contains(":")) {
1914                                        boolean transform2Num = StringUtil.str2Num(c, sb);
1915                                        if (transform2Num) {
1916                                            s.getContents().set(i, c + ":" + sb.toString());
1917                                        }
1918                                    }
1919                                }
1920                            }
1921                            arr.put(new JSONObject(s.toJson()));
1922                        }
1923                        AILog.d(TAG, "trans2Number cost " + (System.currentTimeMillis() - t1));
1924                        obj.put("vocabs", arr);
1925                    } catch (JSONException e) {
1926                        AILog.e(TAG, "updateVocabs failed due to JSONException");
1927                        AIJavaException.printException(e);
1928                    }
1929                    AILog.userI(TAG, "updateVocabs input =", obj.toString());
1930                    UploadTopicUtil.publishUploadVocabs(bc, obj,mode);
1931                }
1932            });
1933        } else {
1934            AILog.e(TAG, "updateVocabs failed due to null busclient");
1935        }
1936        return id;
1937    }
1938
1939    /**
1940     * 更新热词识别接口(请求级别)
1941     * <p>
1942     * 每次请求都会带上热词词库, 例如: [{"type": "vocab", "name": "行政区", "data":["黄浦区"]}]
1943     * 本接口为覆盖式接口,以最新设置为准,如需要清除请求空参数即可
1944     *
1945     * @param phraseHints 需要更新的热词识别列表
1946     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
1947     * @see PhraseHintsIntent
1948     */
1949    public void updatePhraseHints(PhraseHintsIntent... phraseHints) throws DDSNotInitCompleteException {
1950        checkInitComplete();
1951        if (bc != null) {
1952            String phrashHintsJson;
1953            if (phraseHints != null) {
1954                phrashHintsJson = AIGsonUtil.getInstance().toJson(phraseHints);
1955            } else {
1956                phrashHintsJson = "[]";
1957            }
1958            AILog.userI(TAG, "updatePhraseHints input =", phrashHintsJson);
1959            DmsTopicUtil.publishUploadPhrasehints(bc, phrashHintsJson);
1960        } else {
1961            AILog.e(TAG, "updatePhraseHints failed due to null busclient");
1962        }
1963    }
1964
1965    /**
1966     * 设置热词,用于在一路中做热词增强识别。如果不启用该功能请不要使用该方法
1967     * <p>
1968     * 每次请求时会带上热词。上传形式如: [{"name":"common", "words":["示例"], ”boost“:3}]
1969     * </p>
1970     * 使用示例:
1971     * <pre>
1972     *  PhraseList p = new PhraseList();
1973     *  p.setName("common"); // 槽位 默认是 common
1974     *  p.setBoost(1); // 表示增强强度,从小到大取值是1,2,3; 默认值是1
1975     *  p.addWord("苏州");
1976     *  p.addWord("上海");
1977     *  DDS.getInstance().getAgent().updatePhraseList(p);
1978     * </pre>
1979     *
1980     * @param phraseLists 热词
1981     */
1982    public void updatePhraseList(PhraseList... phraseLists) {
1983        String data = phraseLists == null || phraseLists.length == 0 ? ""
1984                : AIGsonUtil.getInstance().toJson(phraseLists);
1985        AILog.userI(TAG, "updatePhraseList input =", data);
1986        LocalKeysUtil.getInstance().put(LocalKeys.Dms.LOCAL_KEY_PHRASELIST, data, false);
1987    }
1988
1989    /**
1990     * 设置需要进行识别过滤的词
1991     * 使用示例:
1992     * <pre>
1993     *  String asrDropWords = "你好小驰,小苹果";
1994     *  DDS.getInstance().getAgent().updateAsrDropWords(asrDropWords);
1995     * </pre>
1996     *
1997     * @param asrDropWords 识别过滤词
1998     */
1999    public void updateAsrDropWords(String asrDropWords) {
2000        AILog.userI(TAG, "updateAsrDropWords input =", asrDropWords);
2001        LocalKeysUtil.getInstance().put(LocalKeys.Dms.LOCAL_KEY_ASR_DROP_WORDS, asrDropWords, false);
2002    }
2003
2004    /**
2005     * @deprecated Replaced by {@link #updateVocabs(VocabIntent...)}
2006     */
2007    @Deprecated
2008    public String updateVocab(String vocabName, String[] contents, boolean addOrDelete) throws
2009            DDSNotInitCompleteException {
2010        checkInitComplete();
2011
2012        String id = UUIDUtil.randomUUID();
2013        if (bc != null) {
2014            JSONObject obj = new JSONObject();
2015            try {
2016                obj.put("reqId", id);
2017                obj.put("type", "vocab");
2018                obj.put("vocabName", vocabName);
2019                if (null != contents) {
2020                    JSONArray contentsArray = new JSONArray();
2021                    for (String s : contents) {
2022                        contentsArray.put(s);
2023                    }
2024                    obj.put("vocabs", contentsArray);
2025                }
2026                obj.put("method", addOrDelete ? "POST" : "DELETE");
2027            } catch (JSONException e) {
2028                AILog.e(TAG, "updateVocab failed due to JSONException");
2029                AIJavaException.printException(e);
2030            }
2031            UploadTopicUtil.publishUploadVocab(bc, obj,VocabIntent.ALL);
2032        } else {
2033            AILog.e(TAG, "updateVocab failed due to null busclient");
2034        }
2035
2036        return id;
2037    }
2038
2039    /**
2040     * 更新设备状态,用来保存设备的一些状态,比如:蓝牙是否打开
2041     * 更新结果可以通过sys.upload.result消息来获取
2042     * <p>
2043     *
2044     * @param deviceJson 设备状态信息的Json串,例如:
2045     *                   {"bluetooth":{”state“:"disconnected"}},在技能中使用$context.bluetooth.state$将会获取到返回值"disconnected"
2046     * @return 请求ID,用于追踪sys.upload.result
2047     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2048     * @deprecated Replaced by {@link Agent#updateProductContext(ContextIntent)}}
2049     * {@link Agent#updateSkillContext(ContextIntent)}}
2050     */
2051    @Deprecated
2052    public String updateDeviceInfo(String deviceJson) throws
2053            DDSNotInitCompleteException {
2054        checkInitComplete();
2055        String id = null;
2056        try {
2057            JSONObject jsonObject = new JSONObject(deviceJson);
2058            Iterator<String> keyIterator = jsonObject.keys();
2059            if (keyIterator.hasNext()) {
2060                String key = keyIterator.next();
2061                ContextIntent contextIntent = new ContextIntent(key, jsonObject.opt(key).toString());
2062                id = updateProductContext(contextIntent);
2063            }
2064        } catch (Exception e) {
2065            AIJavaException.printException(e);
2066        }
2067        return id;
2068    }
2069
2070    /**
2071     * 更新设备状态,产品级的配置。比如:定位信息,设备硬件状态等
2072     * 更新结果可以通过sys.upload.result消息来获取
2073     *
2074     * @param intent 请求的ContextIntent对象,包括key和value
2075     *               如:ContextIntent intent = new ContextIntent("location",{"city":"苏州市"});
2076     *               updateProductContext(intent);
2077     *               技能里通过$context.location.city$即可获取到"苏州市"
2078     * @return 请求的ID,用于追踪sys.upload.result
2079     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2080     */
2081    public String updateProductContext(ContextIntent intent) throws DDSNotInitCompleteException {
2082        checkInitComplete();
2083        AILog.userI(TAG, "updateProductContext obj =", (intent != null ? intent.toString() : ""));
2084
2085        String id = UUIDUtil.randomUUID();
2086
2087        if (bc != null) {
2088            DmsTopicUtil.publishSystemSettings(bc, intent.keyValue2JSONArray(), id, intent.getOption());
2089        } else {
2090            AILog.e(TAG, "updateProductContext failed due to null busclient");
2091        }
2092
2093        return id;
2094    }
2095
2096    /**
2097     * 更新技能配置,需要调用ContextIntent.setSkillId设置技能ID
2098     * 更新结果可以通过sys.upload.result消息来获取
2099     *
2100     * @param intent 请求的ContextIntent对象,包括key、value、skillId。
2101     * @return 请求的ID,用于追踪sys.upload.result
2102     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2103     */
2104    public String updateSkillContext(ContextIntent intent) throws DDSNotInitCompleteException {
2105        checkInitComplete();
2106        AILog.userI(TAG, "updateSkillContext obj =", (intent != null ? intent.toString() : ""));
2107        if (TextUtils.isEmpty(intent.getSkillId())) {
2108            throw new IllegalArgumentException("updateSkillContext, ContextIntent must set skillId!");
2109        }
2110
2111        String id = UUIDUtil.randomUUID();
2112
2113        if (bc != null) {
2114            DmsTopicUtil.publishSkillSettings(bc, intent.keyValue2JSONArray(), id, intent.getSkillId(), intent.getOption());
2115        } else {
2116            AILog.e(TAG, "updateSkillContext failed due to null busclient");
2117        }
2118
2119        return id;
2120    }
2121
2122    /**
2123     * 在多轮对话中强制设置为首轮,进入延迟聆听状态。(全双工模式下支持此接口)
2124     * 如果当前在对话中,则进入延迟聆听状态并发送消息:sys.dialog.endSkillDm
2125     * 如果当前在播报中,则打断当前播报进入延迟聆听状态并发送消息:sys.dialog.endSkillDm
2126     * 如果当前不在对话中,此接口无效
2127     *
2128     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2129     */
2130    public String endSkillInDialog() throws DDSNotInitCompleteException {
2131        checkInitComplete();
2132        AILog.userI(TAG, "endSkillInDialog...");
2133
2134        if (bc != null) {
2135            ProcessorTopicUtil.publishEndSkillInDialog(bc);
2136        } else {
2137            AILog.e(TAG, "endSkillInDialog failed due to null busclient");
2138        }
2139        return null;
2140    }
2141
2142    /**
2143     * 自定义多模态事件同步,该接口用于客户端给对话中控发送一个事件
2144     *
2145     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2146     */
2147    public void updateCustomSyncInfo(JSONObject obj) throws DDSNotInitCompleteException {
2148        checkInitComplete();
2149        AILog.userI(TAG, "updateCustomSyncInfo input =", (obj != null ? obj.toString() : ""));
2150        if (bc != null && obj != null) {
2151            DmsTopicUtil.publishCustomContextSync(bc, obj);
2152        } else {
2153            AILog.e(TAG, "updateCustomSyncInfo failed due to null busclient");
2154        }
2155    }
2156
2157    /**
2158     * 多模态事件同步,该接口用于客户端给对话中控发送一个事件,例如:
2159     * <li>
2160     * <ul>不再支持: <s>在多轮对话中强制设置为首轮,传入数据为: new JSONObject().put("endSkill", "true")</s></ul>
2161     * </li>
2162     *
2163     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2164     */
2165    public String updateDispatchEvent(JSONObject obj) throws DDSNotInitCompleteException {
2166        checkInitComplete();
2167        AILog.userI(TAG, "updateDispatchEvent input =", (obj != null ? obj.toString() : ""));
2168        if (bc != null && obj != null) {
2169            String requestId = UUIDUtil.randomUUID();
2170            try {
2171                obj.put("requestId", requestId);
2172            } catch (JSONException e) {
2173                AIJavaException.printException(e);
2174            }
2175            ProcessorTopicUtil.publishUpdateDispatchEvent(bc, obj);
2176            return requestId;
2177        } else {
2178            AILog.e(TAG, "updateDispatchEvent failed due to null busclient");
2179        }
2180        return null;
2181    }
2182
2183    /**
2184     * 设置对话离线与在线模式
2185     *
2186     * @param policy: 0: 在线优先模式,如果网络离线自动切换到离线
2187     *                1: 强制离线模式
2188     *                2: 强制在线模式
2189     *                3: 混合对话模式
2190     */
2191    public void updateDmsPolicy(int policy) throws DDSNotInitCompleteException {
2192        checkInitComplete();
2193        AILog.userI(TAG, "updateDmsPolicy input =", policy);
2194        if (bc != null) {
2195            DmsTopicUtil.publishUpdateDmsPolicy(bc, policy);
2196        } else {
2197            AILog.e(TAG, "updateDmsPolicy failed due to null busclient");
2198        }
2199    }
2200
2201    /**
2202     * 对话同步接口
2203     *
2204     * @param obj
2205     * @throws DDSNotInitCompleteException
2206     */
2207    public void updateDmSync(JSONObject obj) throws DDSNotInitCompleteException {
2208        checkInitComplete();
2209        AILog.userI(TAG, "updateDmSync input =", (obj != null ? obj.toString() : ""));
2210
2211        if (bc != null && obj != null) {
2212            ProcessorTopicUtil.publishInputDmSync(bc, ProcessorJsonBuilder.getDmSyncObj(obj));
2213        } else {
2214            AILog.e(TAG, "updateDmSync failed due to null busclient");
2215        }
2216    }
2217
2218    private Object getContext(String inputKey, String type, String skillId) {
2219        String data = LocalKeysUtil.getInstance().getString(skillId == null ? type : (type + skillId));
2220        try {
2221            JSONObject obj = new JSONObject(data);
2222            return obj.opt(inputKey);
2223        } catch (Exception e) {
2224        }
2225        return null;
2226    }
2227
2228    /**
2229     * 获取产品的配置信息
2230     *
2231     * @param inputKey 配置项名称
2232     * @return 配置项值
2233     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2234     */
2235    public Object getProductContext(String inputKey) throws DDSNotInitCompleteException {
2236        checkInitComplete();
2237        Object result = getContext(inputKey, LocalKeys.Dms.LOCAL_KEY_SYSTEM_SETTINGS, null);
2238        AILog.userI(TAG, "getProductContext result =", result);
2239        return result;
2240    }
2241
2242    /**
2243     * 获取技能的配置信息
2244     *
2245     * @param skillId  技能ID
2246     * @param inputKey 配置项名称
2247     * @return 配置项值
2248     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2249     */
2250    public Object getSkillContext(String skillId, String inputKey) throws DDSNotInitCompleteException {
2251        checkInitComplete();
2252        Object result = getContext(inputKey, "/local_keys/", skillId);
2253        AILog.userI(TAG, "getProductContext result =", result);
2254        return result;
2255    }
2256
2257
2258    /**
2259     * 清空客户端上传的设备信息
2260     *
2261     * @param key 不为null时,清空指定key的设备信息,如"state"
2262     * @return 请求ID,用于追踪sys.upload.result
2263     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2264     */
2265    public String clearDeviceInfo(String key) throws DDSNotInitCompleteException {
2266        // v2接口不再支持删除全部cinfo信息, 所以key不可以为null
2267        AILog.userI(TAG, "clearDeviceInfo input =", key);
2268        if (TextUtils.isEmpty(key)) {
2269            return "";
2270        }
2271        checkInitComplete();
2272
2273        ContextIntent contextIntent = new ContextIntent(key, "");
2274        contextIntent.setOption("delete");
2275        return updateProductContext(contextIntent);
2276    }
2277
2278    /**
2279     * 获取客户端保存的设备信息
2280     *
2281     * @return 设备信息的jsonString,例如:{"bluetooth":"disconnected"}
2282     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2283     */
2284    public String getDeviceInfo() throws DDSNotInitCompleteException {
2285        checkInitComplete();
2286        String result = LocalKeysUtil.getInstance().getString(LocalKeys.Dms.LOCAL_KEY_SYSTEM_SETTINGS);
2287        if (TextUtils.isEmpty(result)) {
2288            result = LocalKeysUtil.getInstance().getString(LocalKeys.Upload.LOCAL_KEYS_DEVICE_INFO);
2289        }
2290        AILog.userI(TAG, "getDeviceInfo result =", result);
2291        return result;
2292    }
2293
2294    /**
2295     * 此接口在Agent中已弃用,参见{@link WakeupEngine#updateMinorWakeupWord(String, String, String, String[])}
2296     * 更新副唤醒词的接口
2297     * <p>
2298     * 支持设置一个副唤醒词,重复调用会以最新的副唤醒词为准
2299     *
2300     * @param word      副唤醒词, 若设置null,则清空当前的副唤醒词
2301     * @param pinyin    副唤醒词的拼音
2302     * @param threshold 副唤醒词的阈值, 若设置null,则自动估算
2303     * @param greetings 副唤醒词的欢迎语, 若设置null,则与主唤醒词保持一致
2304     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2305     */
2306    @Deprecated
2307    public void updateMinorWakeupWord(String word, String pinyin, String threshold, String[] greetings) throws
2308            DDSNotInitCompleteException {
2309        getWakeupEngine().updateMinorWakeupWord(word, pinyin, threshold, greetings);
2310    }
2311
2312    /**
2313     * 获取ASR引擎
2314     *
2315     * @return ASREngine实例
2316     */
2317    public ASREngine getASREngine() {
2318        return ASREngine.getInstance();
2319    }
2320
2321    /**
2322     * 获取TTS引擎
2323     *
2324     * @return TTSEngine实例
2325     */
2326    public TTSEngine getTTSEngine() {
2327        return TTSEngine.getInstance();
2328    }
2329
2330    /**
2331     * 获取Wakeup引擎
2332     *
2333     * @return Wakeup实例
2334     */
2335    public WakeupEngine getWakeupEngine() {
2336        return WakeupEngine.getInstance();
2337    }
2338
2339    /**
2340     * 获取Vprint引擎
2341     *
2342     * @return Vprint实例
2343     */
2344//    public VprintEngine getVprintEngine() {
2345//        return VprintEngine.getInstance();
2346//    }
2347
2348    /**
2349     * 此接口在Agent中已弃用,参见{@link WakeupEngine#updateCommandWakeupWord(String[], String[], String[], String[], String[][])}
2350     * 更新命令唤醒词的接口,这类唤醒词会在唤醒之后执行一条指令,不能打断正在播报的语音
2351     * <p>
2352     * 支持设置多个命令唤醒词,所以参数为数组,重复调用会以最新的命令唤醒词数组为准。
2353     *
2354     * @param actions   命令唤醒词对应的command命令(必须)
2355     * @param words     命令唤醒词的汉字(必须)
2356     * @param pinyin    命令唤醒词的拼音(必须)
2357     * @param threshold 命令唤醒词的阈值(必须
2358     * @param greetings 命令唤醒词对应的唤醒语,一个唤醒词可以设置多条欢迎语,所以参数为二维数组,如果想要某个唤醒词不要欢迎语,那么该第二维数组的string可以设置为空字符串""
2359     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2360     */
2361    @Deprecated
2362    public void updateCommandWakeupWord(String[] actions, String[] words, String[] pinyin, String[] threshold, String[][] greetings) throws DDSNotInitCompleteException {
2363        getWakeupEngine().updateCommandWakeupWord(actions, words, pinyin, threshold, greetings);
2364    }
2365
2366    /**
2367     * 此接口在Agent中已弃用,参见{@link WakeupEngine#clearCommandWakeupWord()}
2368     * 清空命令唤醒词的接口
2369     *
2370     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2371     */
2372    @Deprecated
2373    public void clearCommandWakeupWord() throws DDSNotInitCompleteException {
2374        checkInitComplete();
2375        WakeupTopicUtil.publishStickyRenameMinorWord(bc, null);
2376    }
2377
2378
2379    /**
2380     * 此接口在Agent中已弃用,参见{@link WakeupEngine#clearShortCutWakeupWord()}
2381     * 清空打断唤醒词的接口
2382     *
2383     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2384     */
2385    @Deprecated
2386    public void clearShortCutWakeupWord() throws DDSNotInitCompleteException {
2387        getWakeupEngine().clearShortCutWakeupWord();
2388    }
2389
2390    /**
2391     * 此接口在Agent中已弃用,参见{@link WakeupEngine#updateShortcutWakeupWord(String[], String[], String[])}
2392     * 更新打断唤醒词的接口,这类唤醒词能打断正在播报的语音并且将唤醒词送入识别
2393     * <p>
2394     * 支持设置多个打断唤醒词,所以参数为数组,重复调用会以最新的打断唤醒词数组为准。
2395     *
2396     * @param words     打断唤醒词的汉字(必须)
2397     * @param pinyin    打断唤醒词的拼音(必须)
2398     * @param threshold 打断唤醒词的阈值(必须
2399     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2400     */
2401    @Deprecated
2402    public void updateShortcutWakeupWord(String[] words, String[] pinyin, String[] threshold) throws DDSNotInitCompleteException {
2403        getWakeupEngine().updateShortcutWakeupWord(words, pinyin, threshold);
2404    }
2405
2406    /**
2407     * 设置VAD后端停顿时间的接口
2408     * <p>
2409     * 若VAD在用户说话时停顿超过一定的时间,则认为用户已经说完,发出sys.vad.end消息,结束录音。
2410     *
2411     * @param millis 后端停顿时间,单位为毫秒。默认值为500毫秒。
2412     * @return true-设置成功;false-设置失败
2413     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2414     */
2415    @Deprecated
2416    public boolean setVadPauseTime(long millis) throws DDSNotInitCompleteException {
2417        return getASREngine().setVadPauseTime(millis);
2418    }
2419
2420    /**
2421     * 获取VAD后端停顿时间的接口
2422     *
2423     * @return millis 后端停顿时间,单位为毫秒。
2424     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2425     */
2426    @Deprecated
2427    public long getVadPauseTime() throws DDSNotInitCompleteException {
2428        return getASREngine().getVadPauseTime();
2429    }
2430
2431    /**
2432     * 输入文本的接口
2433     * <p>
2434     * 跳过对话中的识别过程,直接开始对话中的语义理解
2435     * <p>
2436     * 若当前没有在对话中,则以文本作为首轮说法,新发起一轮对话请求
2437     * 若当前正在对话中,则跳过识别,直接发送文本
2438     *
2439     * @param text 输入的文本内容
2440     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2441     */
2442    public void sendText(String text) throws DDSNotInitCompleteException {
2443        checkInitComplete();
2444        AILog.userI(TAG, "sendText input =", text);
2445        if (bc != null) {
2446            ProcessorTopicUtil.publishInputText(bc, text, DDSConstant.DmsInputInnerType.USER_INPUT_TEXT);
2447        } else {
2448            AILog.e(TAG, "feedText failed due to null busclient");
2449        }
2450    }
2451
2452    public List<SkillConfig> getSkillList() throws DDSNotInitCompleteException {
2453        List<SkillConfig> list = new ArrayList<>();
2454
2455        String path = getFromKeys(LocalKeys.Daemon.LOCAL_KEY_CUSTOM_HOME);
2456        if (TextUtils.isEmpty(path)) {
2457            AILog.e(TAG, "Never Happened! invalid custom_home");
2458            return list;
2459        }
2460
2461        String productCfg = null;
2462        try {
2463            productCfg = FileUtils.readFile(path + "/product.cfg");
2464        } catch (IOException e) {
2465            AIJavaException.printException(e);
2466        }
2467        if (TextUtils.isEmpty(productCfg)) {
2468            AILog.e(TAG, "Never Happened! invalid product.cfg");
2469            return list;
2470        }
2471
2472        try {
2473            JSONObject obj = new JSONObject(productCfg);
2474            JSONArray array = obj.getJSONArray("skill_list");
2475            for (int i = 0; i < array.length(); i++) {
2476                JSONObject skillObj = array.getJSONObject(i);
2477                SkillConfig skillConfig = new SkillConfig();
2478                skillConfig.setId(skillObj.getString("id"));
2479                skillConfig.setName(skillObj.getString("name"));
2480                AILog.i(TAG, skillConfig.toString());
2481                list.add(skillConfig);
2482            }
2483        } catch (JSONException e) {
2484            AIJavaException.printException(e);
2485        }
2486        AILog.userI(TAG, "getSkillList result =", list.toString());
2487        return list;
2488    }
2489
2490    /**
2491     * 暂停当前对话
2492     * <p>
2493     *
2494     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2495     */
2496    public void pauseDialog() throws DDSNotInitCompleteException {
2497        checkInitComplete();
2498        AILog.userI(TAG, "pauseDialog");
2499        if (bc != null) {
2500            ProcessorTopicUtil.publishDialogCtrl(bc, "freeze");
2501        } else {
2502            AILog.e(TAG, "pauseDialog failed due to null busclient");
2503        }
2504    }
2505
2506    /**
2507     * 恢复当前对话
2508     * <p>
2509     *
2510     * @throws DDSNotInitCompleteException 如果DDS没有初始化完成,会抛出exception
2511     */
2512    public void resumeDialog() throws DDSNotInitCompleteException {
2513        checkInitComplete();
2514        AILog.userI(TAG, "resumeDialog");
2515        if (bc != null) {
2516            ProcessorTopicUtil.publishDialogCtrl(bc, "resume");
2517        } else {
2518            AILog.e(TAG, "resumeDialog failed due to null busclient");
2519        }
2520    }
2521
2522    /**
2523     * 此接口支持修改对话中的语音播报,修改文本展示等功能
2524     *
2525     * @param callback 对话监听器
2526     * @deprecated 该接口极端情况会导致rpc死锁问题,已舍弃。建议使用支持{@code topic}的{{@link #setDMTaskCallback(DMTaskCallback)}}接口
2527     */
2528    @Deprecated
2529    public void setDMCallback(DMCallback callback) {
2530        this.mDMCallback = callback;
2531        if (bc != null) {
2532            ProcessorTopicUtil.publishDmsWaitOperation(bc, mDMCallback != null);
2533        } else {
2534            AILog.e(TAG, "setDMCallback failed due to null busclient");
2535        }
2536    }
2537
2538    /**
2539     * 此接口支持修改唤醒逻辑,是否需要忽略唤醒词类型强行唤醒/忽略唤醒
2540     *
2541     * @param dmWkupRetCallback 对话监听器
2542     */
2543    public void setDMWkupRetCallback(DMWkupRetCallback dmWkupRetCallback) {
2544        this.mDmWkupRetCallback = dmWkupRetCallback;
2545        AILog.userI(TAG, "setDMWkupRetCallback input =", (dmWkupRetCallback != null));
2546        if (mDmWkupRetCallback != null) {
2547            if (bc != null) {
2548                JSONObject jo = new JSONObject();
2549                try {
2550                    jo.put(Topic.Processor.WkupOperationEnable.STATE, "enable");
2551                    ProcessorTopicUtil.publishWkupOperationEnable(bc, jo);
2552                } catch (Exception e) {
2553                    e.printStackTrace();
2554                }
2555            } else {
2556                AILog.e(TAG, "setDMWkupRetCallback failed due to null busclient");
2557            }
2558        } else {
2559            if (bc != null) {
2560                JSONObject jo = new JSONObject();
2561                try {
2562                    jo.put(Topic.Processor.WkupOperationEnable.STATE, "disable");
2563                    ProcessorTopicUtil.publishWkupOperationEnable(bc, jo);
2564                } catch (Exception e) {
2565                    e.printStackTrace();
2566                }
2567            } else {
2568                AILog.e(TAG, "setDMWkupRetCallback null failed due to null busclient");
2569            }
2570        }
2571    }
2572
2573    /**
2574     * 配置对话的额外参数,由客户和服务器约定键值。设置后会在下次对话开始时带上
2575     * <p>
2576     * 对话发送 recorder.stream.start 消息的报文时,都会在 context.attributes 字段加上这里配置 JSON
2577     * </p>
2578     *
2579     * @param extInfo 额外参数, 为空时标识额外参数置空
2580     */
2581    public void setDialogExtInfo(JSONObject extInfo) {
2582        AILog.userI(TAG, "setDialogExtInfo input =", extInfo);
2583        LocalKeysUtil.getInstance().put(LocalKeys.BA.LOCAL_KEY_BA_EXT_INFO, extInfo == null ? "" : extInfo.toString());
2584    }
2585
2586    public void setAudioFocusCallback(AudioFocusCallback audioFocusCallback) {
2587        this.mAudioFocusCallback = audioFocusCallback;
2588        AILog.userI(TAG, "setAudioFocusCallback");
2589        if (mAudioFocusCallback != null) {
2590            if (bc != null) {
2591                bc.publishSticky(TOPIC_PLAYER_AUDIOFOCUS_CALLBACK);
2592            } else {
2593                AILog.e(TAG, "setAudioFocusCallback failed due to null busclient");
2594            }
2595        }
2596    }
2597
2598
2599    /**
2600     * 新增技能锁定接口,让用户说的话只落在锁定的技能里
2601     *
2602     * @param jsonArray skillId array,
2603     *                  [
2604     *                  {"skillId": "123123"},
2605     *                  {"skillId": "123123"},
2606     *                  {"skillId": "123123"},
2607     *                  ]
2608     */
2609    public void updatePermanentSkill(JSONArray jsonArray) throws DDSNotInitCompleteException {
2610        checkInitComplete();
2611        AILog.userI(TAG, "updatePermanentSkill input =", (jsonArray != null ? jsonArray.toString() : "null"));
2612        if (bc != null) {
2613            if (jsonArray != null) {
2614                ProcessorTopicUtil.publishUpdatePermanentSkill(bc, jsonArray);
2615            } else {
2616                AILog.e(TAG, "updatePermanentSkill failed due to null jsonArray");
2617            }
2618        } else {
2619            AILog.e(TAG, "updatePermanentSkill failed due to null busclient");
2620        }
2621    }
2622
2623    /**
2624     * 此接口支持修改对话中的语音播报,修改文本展示等功能
2625     *
2626     * @param dmTaskCallback 对话监听器
2627     */
2628    public void setDMTaskCallback(DMTaskCallback dmTaskCallback) {
2629        AILog.userI(TAG, "setDMTaskCallback");
2630        if(CallbackManager.getInstance().getOnDmTaskCallback() != null) {
2631            throw new IllegalStateException("you have setDMTaskCallbackSync");
2632        }
2633        this.mDMTaskCallback = dmTaskCallback;
2634        if (bc != null) {
2635            ProcessorTopicUtil.publishTaskOperationEnable(bc, mDMTaskCallback != null);
2636        } else {
2637            AILog.e(TAG, "setDMTaskCallback failed due to null busclient");
2638        }
2639    }
2640
2641    /**
2642     * 此接口支持修改对话中的语音播报,修改文本展示等功能,同步方法
2643     *
2644     * @param dmTaskCallbackSync 对话监听器
2645     */
2646    public void setDMTaskCallbackSync(final DMTaskCallbackSync dmTaskCallbackSync) {
2647        AILog.userI(TAG, "setDMTaskCallback");
2648        if(mDMTaskCallback != null) {
2649            throw new IllegalStateException("you have setDMTaskCallback");
2650        }
2651        if(dmTaskCallbackSync != null) {
2652            CallbackManager.getInstance().setOnDmTaskCallback(new OnDmTaskCallback() {
2653                @Override
2654                public void onDMTaskResult(JSONObject dmTaskResult) {
2655                    DMTaskCallbackSync.Type type = DMTaskCallbackSync.Type.getTypeByVal(dmTaskResult.optString("from"));
2656                    dmTaskCallbackSync.onDMTaskResultSync(dmTaskResult,type);
2657                }
2658            });
2659        } else {
2660            CallbackManager.getInstance().setOnDmTaskCallback(null);
2661        }
2662    }
2663
2664
2665    /**
2666     * 此接口支持动态修改 custom tips
2667     *
2668     * @param obj <p>
2669     *            取值:JSON字符串,如:<br>
2670     *            {
2671     *            "71304":"这是识别结果为空的自定义播报",
2672     *            "71305":"这是语义结果为空的自定义播报",
2673     *            "71308":"这是进入闲聊技能的自定义播报",
2674     *            "713**":"*****"
2675     *            }<br>
2676     * @throws DDSNotInitCompleteException
2677     */
2678    public void setCustomTips(JSONObject obj) throws DDSNotInitCompleteException {
2679        checkInitComplete();
2680        AILog.userI(TAG, "setCustomTips input =", (obj != null ? obj.toString() : "null"));
2681        if (bc != null) {
2682            ProcessorTopicUtil.publishTipCustomTips(bc, obj);
2683        } else {
2684            AILog.e(TAG, "setCustomTips failed due to null busclient");
2685        }
2686    }
2687
2688    @Override
2689    public void onNoObserverAttached(String topic) {
2690        AILog.d(TAG, "onNoObserverAttached: " + topic);
2691//        synchronized (topicsLock) {
2692        subscribedTopics.remove(topic);
2693        if (bc != null) {
2694            bc.unsubscribe(topic);
2695        }
2696//        }
2697
2698    }
2699
2700    @Override
2701    public void onFirstObserverAttached(String topic) {
2702        AILog.d(TAG, "onFirstObserverAttached: " + topic);
2703        subscribedTopics.add(topic);
2704        if (bc != null) {
2705            bc.subscribe(topic);
2706        }
2707    }
2708
2709    private void uploadData() {
2710        SysTopicUtil.publishUploadHardwareInfo(bc, DeviceUtil.queryHardwareInfo(context));
2711        try {
2712            JSONObject jsonObject = new JSONObject();
2713            jsonObject.put("type",NetworkUtils.getCurrentNetType(context));
2714            SysTopicUtil.publishSysNetworkType(bc, jsonObject);
2715        } catch (JSONException e) {
2716            e.printStackTrace();
2717        }
2718    }
2719
2720    private void checkInitComplete() throws DDSNotInitCompleteException {
2721        if (DDS.getInstance().getInitStatus() != DDS.INIT_COMPLETE_FULL) {
2722            throw new DDSNotInitCompleteException();
2723        }
2724    }
2725
2726    private void checkInitPartComplete() throws DDSNotInitCompleteException {
2727        if (DDS.getInstance().getInitStatus() == DDS.INIT_COMPLETE_NONE) {
2728            throw new DDSNotInitCompleteException();
2729        }
2730    }
2731
2732    public Set<String> getSubscribedTopics() {
2733        return subscribedTopics;
2734    }
2735
2736
2737    private class ConnectionChangeReceiver extends BroadcastReceiver {
2738
2739        @Override
2740        public void onReceive(Context context, Intent intent) {
2741            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
2742            if (connectivityManager != null) {
2743                NetworkInfo info = connectivityManager.getActiveNetworkInfo();
2744                JSONObject jsonObject = new JSONObject();
2745                String delayStr = PlayerConfig.getInstance().getStringConfig(DDSConfig.K_NETWORKMANAGER_DELAY);
2746                if (!TextUtils.isEmpty(delayStr)) {
2747                    int delay = Integer.parseInt(delayStr);
2748                    SystemClock.sleep(delay);
2749                }
2750                if (info != null) {
2751                    switch (info.getType()) {
2752                        case ConnectivityManager.TYPE_ETHERNET:
2753                            // 联网之后检测token是否可用
2754                            AccessTokenManager.getInstance(Agent.this.context).checkAccessToken();
2755                            if (bc != null) {
2756                                try {
2757                                    jsonObject.put("type", "ETHERNET");
2758                                    SysTopicUtil.publishSysNetworkType(bc, jsonObject);
2759                                } catch (Exception e) {
2760                                    AIJavaException.printException(e);
2761                                }
2762                            }
2763                            break;
2764                        case ConnectivityManager.TYPE_WIFI:
2765                            // 联网之后检测token是否可用
2766                            AccessTokenManager.getInstance(Agent.this.context).checkAccessToken();
2767                            if (bc != null) {
2768                                try {
2769                                    jsonObject.put("type", "WIFI");
2770                                    AILog.e(TAG, "net = " + jsonObject.toString());
2771                                    SysTopicUtil.publishSysNetworkType(bc, jsonObject);
2772                                } catch (Exception e) {
2773                                    AIJavaException.printException(e);
2774                                }
2775                            }
2776                            break;
2777                        case ConnectivityManager.TYPE_MOBILE:
2778                            // 联网之后检测token是否可用
2779                            AccessTokenManager.getInstance(Agent.this.context).checkAccessToken();
2780                            String type = getNetworkTypeName(info.getSubtype());
2781                            String generation = "TYPE_MOBILE";
2782                            if (type.equals("GPRS") || type.equals("EDGE") || type.equals("CDMA")) {
2783                                generation = "2G";
2784                            } else if (type.equals("UMTS") || type.equals("HSDPA") || type.equals("TD_SCDMA")
2785                                    || type.contains("EVDO") || type.contains("HS")) {
2786                                generation = "3G";
2787                            } else if (type.equals("LTE")) {
2788                                generation = "4G";
2789                            }
2790                            if (bc != null) {
2791                                try {
2792                                    jsonObject.put("type", generation);
2793                                    SysTopicUtil.publishSysNetworkType(bc, jsonObject);
2794                                } catch (Exception e) {
2795                                    AIJavaException.printException(e);
2796                                }
2797                            }
2798                            break;
2799                        default:
2800                            break;
2801                    }
2802                } else {
2803                    ArrayList<MessageObserver> observers = messageObservers.get("sys.fatal.exception");
2804                    if (observers != null) {
2805                        for (MessageObserver observer : observers) {
2806                            try {
2807                                AILog.e(TAG, "MESSAGE_FATAL_EXCEPTION for TVUI!");
2808                                observer.onMessage("sys.fatal.exception", null);
2809                            } catch (Exception e) {
2810                                throw new BusClient.BusClientImplementException(e);
2811                            }
2812                        }
2813                    }
2814
2815                    if (bc != null) {
2816                        try {
2817                            jsonObject.put("type", "none");
2818                            SysTopicUtil.publishSysNetworkType(bc, jsonObject);
2819                        } catch (Exception e) {
2820                            AIJavaException.printException(e);
2821                        }
2822                    }
2823                }
2824            }
2825        }
2826    }
2827
2828    private String getNetworkTypeName(int type) {
2829        switch (type) {
2830            case TelephonyManager.NETWORK_TYPE_GPRS:
2831                return "GPRS";
2832            case TelephonyManager.NETWORK_TYPE_EDGE:
2833                return "EDGE";
2834            case TelephonyManager.NETWORK_TYPE_UMTS:
2835                return "UMTS";
2836            case TelephonyManager.NETWORK_TYPE_CDMA:
2837                return "CDMA";
2838            case TelephonyManager.NETWORK_TYPE_EVDO_0:
2839                return "EVDO revision 0";
2840            case TelephonyManager.NETWORK_TYPE_EVDO_A:
2841                return "EVDO revision A";
2842            case TelephonyManager.NETWORK_TYPE_1xRTT:
2843                return "1xRTT";
2844            case TelephonyManager.NETWORK_TYPE_HSDPA:
2845                return "HSDPA";
2846            case TelephonyManager.NETWORK_TYPE_HSUPA:
2847                return "HSUPA";
2848            case TelephonyManager.NETWORK_TYPE_HSPA:
2849                return "HSPA";
2850            case TelephonyManager.NETWORK_TYPE_IDEN:
2851                return "iDen";
2852            case TelephonyManager.NETWORK_TYPE_EVDO_B:
2853                return "EVDO revision B";
2854            case TelephonyManager.NETWORK_TYPE_LTE:
2855                return "LTE";
2856            case TelephonyManager.NETWORK_TYPE_EHRPD:
2857                return "eHRPD";
2858            case TelephonyManager.NETWORK_TYPE_HSPAP:
2859                return "HSPA+";
2860            case TelephonyManager.NETWORK_TYPE_GSM:
2861                return "GSM";
2862            case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
2863                return "TD_SCDMA";
2864            case TelephonyManager.NETWORK_TYPE_IWLAN:
2865                return "IWLAN";
2866            default:
2867                return "UNKNOWN";
2868        }
2869    }
2870
2871}