panlili2024
2025-03-05 134209ad70f82051da3ce63471df0cc8f778e57d
增加source屏扫码绑定住宅接口
29个文件已添加
19个文件已修改
1 文件已重命名
3762 ■■■■■ 已修改文件
HDLSDK/app/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/App.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/MainActivity.java 146 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/SourceBindActivity.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/SourceTestActivity.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/layout/activity_source_bind.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/build.gradle 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/HDLLink.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/response/BindInfoBean.java 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/CloudCode.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/HdlCloudApi.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/HdlCloudController.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/bean/AiLoginInfo.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/bean/GatewayInfo.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/CloudBroadcast.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/CloudBroadcastAction.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/GlobalBroadcastManager.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/HDLCloudBroadcast.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/interceptor/EncryptInterceptor.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/interceptor/HdlLoginInterceptor.java 281 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/GatewayListListener.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/GatewayListener.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/SibichiListener.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/config/HDLCloudConfig.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/config/HDLLinkConfig.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageDecoder.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/socket/HDLAuthSocket.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/socket/HDLSocket.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/socket/SocketBoot.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BaseEvent.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BindAuthEvent.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BindHomeService.java 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/EventType.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QRCode.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QRCodeUtils.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QrCodeView.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/DeviceUtils.java 377 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/SPKey.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/MainThreadExecutor.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/RenameThreadFactory.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/ThreadUtils.java 319 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/build.gradle 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/libs/com.hdl.sdk-v1.2.1.aar 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/App.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/MainActivity.java 146 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/SourceBindActivity.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/SourceTestActivity.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK_DEMO/app/src/main/res/layout/activity_source_bind.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/AndroidManifest.xml
@@ -36,6 +36,11 @@
            android:exported="false"
            android:windowSoftInputMode="adjustPan|stateHidden" />
        <activity
            android:name=".SourceBindActivity"
            android:exported="false"
            android:windowSoftInputMode="adjustPan|stateHidden" />
    </application>
</manifest>
HDLSDK/app/src/main/java/com/hdl/hdlsdk/App.java
@@ -1,13 +1,11 @@
package com.hdl.hdlsdk;
import android.app.Application;
import android.util.Log;
import com.hdl.sdk.common.HDLSdk;
import com.hdl.sdk.common.event.EventListener;
import com.hdl.sdk.common.utils.LogUtils;
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.bean.LinkResponse;
import com.hdl.sdk.connect.cloud.broadcast.GlobalBroadcastManager;
import com.hdl.sdk.sourceos.OsManager;
/**
 * Created by Tong on 2021/10/8.
@@ -15,6 +13,7 @@
public class App extends Application {
    private String deviceStatusUpdateTopic;
    @Override
    public void onCreate() {
        super.onCreate();
@@ -23,6 +22,15 @@
        //控制SDK日志打印
        HDLSdk.getInstance().setLogEnabled(true);
        //source系统接口初始化
        OsManager.init(this);
        //appkey:ryfElI3tVOT
        //appsecret:AKIn7s1A2YnNvAZRtL8FQxzp0R2KUpIY
        HDLLink.getInstance().initCloud(this, "ryfElI3tVOT", "AKIn7s1A2YnNvAZRtL8FQxzp0R2KUpIY");
        //注册全局广播,刷新token
        GlobalBroadcastManager.registerGlobalBroadcast(this);
    }
    @Override
HDLSDK/app/src/main/java/com/hdl/hdlsdk/MainActivity.java
@@ -35,6 +35,7 @@
import com.hdl.sdk.common.exception.HDLLinkException;
import com.hdl.sdk.common.utils.IdUtils;
import com.hdl.sdk.common.utils.LogUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.common.utils.gson.GsonConvert;
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.bean.LinkResponse;
@@ -44,6 +45,7 @@
import com.hdl.sdk.connect.bean.request.ListSidRequest;
import com.hdl.sdk.connect.bean.request.ListUploadRequest;
import com.hdl.sdk.connect.bean.response.BaseLocalResponse;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.bean.response.GatewaySearchBean;
import com.hdl.sdk.connect.bean.response.UpdateInfo;
import com.hdl.sdk.connect.callback.HDLLinkCallBack;
@@ -51,8 +53,13 @@
import com.hdl.sdk.connect.cloud.CallBackListener;
import com.hdl.sdk.connect.cloud.CheckAppVersionListener;
import com.hdl.sdk.connect.cloud.HDLException;
import com.hdl.sdk.connect.cloud.bean.GatewayInfo;
import com.hdl.sdk.connect.cloud.listener.GatewayListener;
import com.hdl.sdk.connect.cloud.listener.SibichiListener;
import com.hdl.sdk.connect.cloud.bean.AiLoginInfo;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.connect.socket.HDLAuthSocket;
import com.hdl.sdk.sourceos.utils.SPKey;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
@@ -79,21 +86,12 @@
    private List<SceneBean> sceneList = new ArrayList<>();
    private List<SceneDetailBean> sceneDetailList = new ArrayList<>();
    private List<SceneDetailBean> roomSceneList = new ArrayList<>();
    private GatewayInfo gatewayInfo;
    void applyDeviceSecret() {
        tv.setText("开始申请设备密钥...");
        responseTv.setText("");
//        //正式服务器
//        String appKey = "i8hR07jzrIS";//appkey
//        String appSecret = "BmnJ8RWTtaVEBk24zPPF4UMwfYu0lAWU";//appsecret
        //测试服务器
        String appKey = "FcRyUJlLJFF";
        String appSecret = "wz8wn75ABidx8vXcFGUotqhwFkTaYvvJ";
//        String appKey = "L2OZliZRxHc";
//        String appSecret = "aCIWSvJDOukXfx3kivsKW11x9xdR3IbV";
        String supplier = "JINMAOYUN";//厂商
//        String mac = "AA00000000000100";//设备唯一MAC地址
        String mac = editText.getText().toString();
@@ -103,8 +101,8 @@
            Toast.makeText(this, "mac不能为空!", Toast.LENGTH_SHORT).show();
            return;
        }
        HDLLink.getInstance().applyDeviceSecret(this, appKey, appSecret, supplier, mac, spk, new CallBackListener() {
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().applyDeviceSecret(supplier, mac, spk, new CallBackListener() {
            @Override
            public void onError(HDLException e) {
                tv.setText("申请失败");
@@ -126,13 +124,10 @@
        tv.setText("开始检测更新...");
        responseTv.setText("");
//        //正式服务器
        String appKey = "i8hR07jzrIS";//appkey
        String appSecret = "BmnJ8RWTtaVEBk24zPPF4UMwfYu0lAWU";//appsecret
        String appCode = "1697150870315999233";//appCode
        HDLLink.getInstance().checkAppVersion(this, appKey, appSecret, getAppVersionName(this), appCode, new CheckAppVersionListener() {
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().checkAppVersion(getAppVersionName(this), appCode, new CheckAppVersionListener() {
            @Override
            public void onSuccess(UpdateInfo info) {
                tv.setText("有新更新");
@@ -142,6 +137,60 @@
            @Override
            public void onError(HDLException e) {
                tv.setText("检测更新失败");
                responseTv.setText(e.getMsg());
            }
        });
    }
    void getSibichiToken() {
        tv.setText("获取思必驰token...");
        responseTv.setText("");
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        String clientId = "4ED634B5A7AD97A770A52AC00FF43805";//思必驰clientId
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().getSibichiToken(bindInfoBean.getHomeId(), clientId, new SibichiListener() {
            @Override
            public void onSuccess(AiLoginInfo info) {
                tv.setText("思必驰token");
                responseTv.setText(info.toString());
            }
            @Override
            public void onError(HDLException e) {
                tv.setText("获取思必驰token失败");
                responseTv.setText(e.getMsg());
            }
        });
    }
    void syncMainGateway() {
        tv.setText("获取主网关信息...");
        responseTv.setText("");
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        if (bindInfoBean == null) {
            Toast.makeText(this, "请先扫码绑定住宅!", Toast.LENGTH_SHORT).show();
            return;
        }
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().syncMainGateway(bindInfoBean.getHomeId(), new GatewayListener() {
            @Override
            public void onSuccess(GatewayInfo info) {
                tv.setText("获取主网关信息");
                responseTv.setText(info.toString());
                if (info != null) {
                    HDLLinkConfig.getInstance().setGatewayId(info.getGatewayId());
                    //HDLLinkConfig.getInstance().setIpAddress(info.ip);
                }
            }
            @Override
            public void onError(HDLException e) {
                tv.setText("获取主网关信息失败");
                responseTv.setText(e.getMsg());
            }
        });
@@ -180,7 +229,7 @@
        selectnetwork();
        checkIfCertified();
        initDeviceInfo();//初始化
        //initDeviceInfo();//不走从机入网的方式不需要初始化
        registerAllTopicsListener();
        HDLLink.getInstance().setDeleteNetworkListener(new DeleteNetworkListener() {
            @Override
@@ -226,6 +275,9 @@
        beans.add(new DemoBean("获取房间场景列表"));
        beans.add(new DemoBean("获取自动化列表"));
        beans.add(new DemoBean("⾃动化启⽤禁⽤"));
        beans.add(new DemoBean("生成二维码绑定住宅"));
        beans.add(new DemoBean("获取思必驰token"));
        beans.add(new DemoBean("获取网关信息"));
        demoAdapter = new DemoAdapter(beans);
        rv.setAdapter(demoAdapter);
@@ -313,6 +365,18 @@
                    case 19:
                        //⾃动化启⽤禁⽤
                        editEnableLogic();
                        break;
                    case 20:
                        //生成二维码绑定住宅
                        startSourceBindActivity();
                        break;
                    case 21:
                        //获取思必驰token
                        getSibichiToken();
                        break;
                    case 22:
                        //获取网关信息
                        syncMainGateway();
                        break;
                }
            }
@@ -418,6 +482,20 @@
    }
    void initLink() {
        //step1:先生成二维码,用onpro扫码绑定住宅获取住宅信息
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        if (bindInfoBean != null) {
            HDLLinkConfig.getInstance().setHomeId(bindInfoBean.getHomeId());
            HDLLinkConfig.getInstance().setLocalSecret(bindInfoBean.getLocalSecret());
        }
        //step2:再获取网关信息
        if (gatewayInfo != null) {
            HDLLinkConfig.getInstance().setGatewayId(gatewayInfo.getGatewayId());//当前主网关id
        }
    }
    /**
     * 入网认证
     */
@@ -425,15 +503,9 @@
        tv.setText("开始入网认证...");
        //认证提交参数准备
//        测试服务
//        String spkStr = "ir.module";//产品spk
//        String macStr = "AA000000000000AF";//设备唯一MAC地址
//        String secret = "44b360eb74b7ba64";//通过spk和mac提交云端认证后分配的secret
//        正式服务器
        //正式服务器
        String spkStr = "screen.mirror";//产品spk
        String macStr = "f2c5d8bad48f";//设备唯一MAC地址
//        String secret = "e186beeb7974998e";//通过spk和mac提交云端认证后分配的secret
        String mac_key = stringToMD5(stringToMD5(macStr + secret));
        String versionString = "HDL_V1.0.1";//
@@ -560,7 +632,12 @@
        tv.setText("设备功能属性读取");
        responseTv.setText("");
        List<String> sids = new ArrayList<>();
        sids.add(testLightSid);
        if (devicesList.size() != 0) {
            sids.add(devicesList.get(0).getSid());
        } else {
            sids.add(testLightSid);
        }
        HDLLink.getInstance().getFunctionAttribute(sids, new HDLLinkCallBack() {
            @Override
            public void onSuccess(String msg) {
@@ -582,7 +659,11 @@
        tv.setText("读取状态中...");
        responseTv.setText("");
        List<String> list = new ArrayList<>();
        list.add(testLightSid);//要读取设备的sid
        if (devicesList.size() != 0) {
            list.add(devicesList.get(0).getSid());
        } else {
            list.add(testLightSid);//要读取设备的sid
        }
        HDLLink.getInstance().propertyRead(list, new HDLLinkCallBack() {
            @Override
            public void onSuccess(String data) {
@@ -608,7 +689,11 @@
        isOn = !isOn;
        List<DeviceControlRequest> requestList = new ArrayList<>();
        DeviceControlRequest request = new DeviceControlRequest();
        request.setSid(testLightSid);
        if (devicesList.size() != 0) {
            request.setSid(devicesList.get(0).getSid());
        } else {
            request.setSid(testLightSid);//要读取设备的sid
        }
        List<DeviceControlRequest.StatusBean> statusBeanList = new ArrayList<>();
        DeviceControlRequest.StatusBean bean = new DeviceControlRequest.StatusBean();
        bean.setKey("on_off");
@@ -905,6 +990,11 @@
        startActivity(intent);
    }
    void startSourceBindActivity() {
        Intent intent = new Intent(this, SourceBindActivity.class);
        startActivity(intent);
    }
    /**
     * TCP发送 只发一次,不监听回复,不重发
     */
HDLSDK/app/src/main/java/com/hdl/hdlsdk/SourceBindActivity.java
New file
@@ -0,0 +1,106 @@
package com.hdl.hdlsdk;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.sourceos.bind.BaseEvent;
import com.hdl.sdk.sourceos.bind.BindAuthEvent;
import com.hdl.sdk.sourceos.bind.BindHomeService;
import com.hdl.sdk.sourceos.bind.EventType;
import com.hdl.sdk.sourceos.qrcode.QRCode;
import com.hdl.sdk.sourceos.qrcode.QrCodeView;
import com.hdl.sdk.sourceos.utils.SPKey;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class SourceBindActivity extends AppCompatActivity {
    private static final String TAG = "SourceBindActivity";
    private QrCodeView qrcodeView;
    private TextView responseTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_source_bind);
        registerEventBus();
        initView();
    }
    @SuppressLint("WrongConstant")
    private void initView() {
        qrcodeView = findViewById(R.id.qrcode_view);
        responseTv = findViewById(R.id.response_tv);
        createBindQRCodeInfo();
    }
    private void createBindQRCodeInfo() {
        final String time = String.valueOf(System.currentTimeMillis());
        String info = QRCode.createBindQRCodeInfo(time);
        qrcodeView.setContent(info);
        //开始轮询
        BindHomeService.getInstance().startQuery(time);
    }
    /**
     * 绑定成功通知事件
     *
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMessage(BaseEvent event) {
        switch (event.getEventType()) {
            case EventType.ON_PLUS_BINDING_TYPE:
                if (event instanceof BindAuthEvent) {
                    int action = ((BindAuthEvent) event).getAction();
                    if (action == BindAuthEvent.ON_PLUS_BINDING_SUCCEED_ACTION) {
                        //on+绑定成功
                        Toast.makeText(this, "绑定成功", Toast.LENGTH_SHORT).show();
                        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
                        responseTv.setText(bindInfoBean.toString());
                        if (bindInfoBean != null) {
                            HDLLinkConfig.getInstance().setHomeId(bindInfoBean.getHomeId());
                            HDLLinkConfig.getInstance().setLocalSecret(bindInfoBean.getLocalSecret());
                        }
                    } else if (action == BindAuthEvent.ON_PLUS_BINDING_TIMEOUT_ACTION) {
                        //on+绑定超时
                        Toast.makeText(this, "绑定超时", Toast.LENGTH_SHORT).show();
                    } else if (action == BindAuthEvent.ON_PLUS_BINDING_ERROR_ACTION) {
                        //网络错误
                        //Toast.makeText(this, "网络异常", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
        }
    }
    protected void unregisterEventBus() {
        if (EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
    }
    protected void registerEventBus() {
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterEventBus();
        BindHomeService.getInstance().stopQuery();
    }
}
HDLSDK/app/src/main/java/com/hdl/hdlsdk/SourceTestActivity.java
@@ -78,8 +78,6 @@
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REBOOT, Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
        }
        //系统接口初始化
        OsManager.init(SourceTestActivity.this);
        OsManager.addEventListener(eventListener);
HDLSDK/app/src/main/res/layout/activity_source_bind.xml
New file
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SourceBindActivity">
    <com.hdl.sdk.sourceos.qrcode.QrCodeView
        android:id="@+id/qrcode_view"
        android:layout_width="125dp"
        android:layout_height="125dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginStart="28dp"
        android:layout_marginTop="28dp"
        android:padding="3dp" />
    <TextView
        android:id="@+id/response_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="3dp"
        android:layout_marginTop="28dp" />
</LinearLayout>
HDLSDK/hdl-connect/build.gradle
@@ -8,8 +8,8 @@
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 8
        versionName "1.2.0"
        versionCode 9
        versionName "1.2.1"
        consumerProguardFiles "consumer-rules.pro"
    }
@@ -34,4 +34,8 @@
    api files('libs/hdlSDK_V1.0.7.jar')
    api 'com.google.code.gson:gson:2.8.8'
    //二维码
    api 'com.google.zxing:core:3.4.1'
    //事件总线
    api 'org.greenrobot:eventbus:3.2.0'
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/HDLLink.java
@@ -17,8 +17,9 @@
import com.hdl.sdk.connect.cloud.CheckAppVersionListener;
import com.hdl.sdk.connect.cloud.HdlCloudApi;
import com.hdl.sdk.connect.cloud.HdlCloudController;
import com.hdl.sdk.connect.cloud.interceptor.EncryptInterceptor;
import com.hdl.sdk.connect.cloud.interceptor.SmartHeaderInterceptor;
import com.hdl.sdk.connect.cloud.listener.GatewayListener;
import com.hdl.sdk.connect.cloud.listener.SibichiListener;
import com.hdl.sdk.connect.config.HDLCloudConfig;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.connect.socket.HDLAuthSocket;
import com.hdl.sdk.connect.socket.HDLSocket;
@@ -164,51 +165,59 @@
     * @param spk      设备spk
     * @param callBack 结果回调
     */
    public void applyDeviceSecret(Context context, String appKey, String appSecret, String supplier, String mac, String spk, CallBackListener callBack) {
    public void applyDeviceSecret(String supplier, String mac, String spk, CallBackListener callBack) {
        LogUtils.i("申请设备密钥");
        HxHttpConfig.getInstance().init(context, HdlCloudApi.BASE_CHINA_URL)
                .addInterceptor(
                        new EncryptInterceptor(),
                        new SmartHeaderInterceptor());
        this.appKey = appKey;
        this.appSecret = appSecret;
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        httpLoggingInterceptor.level(HttpLoggingInterceptor.Level.BODY);
        HxHttpConfig.getInstance()
                //.addInterceptor(new HttpCacheInterceptor())
                .addInterceptor(httpLoggingInterceptor).ignoreSSL();
        HdlCloudController.applyDeviceSecret(supplier, mac, spk, callBack);
    }
    /**
     * 检测更新
     *
     * @param appKey      appKey
     * @param appSecret   appSecret
     * @param versionCode 当前版本
     * @param appCode     appCode
     * @param listener    结果回调
     */
    public void checkAppVersion(Context context, String appKey, String appSecret, String versionCode, String appCode, CheckAppVersionListener listener) {
    public void checkAppVersion(String versionCode, String appCode, CheckAppVersionListener listener) {
        LogUtils.i("检测更新");
        HxHttpConfig.getInstance().init(context, HdlCloudApi.BASE_CHINA_URL)
                .addInterceptor(
                        new EncryptInterceptor(),
                        new SmartHeaderInterceptor());
        this.appKey = appKey;
        this.appSecret = appSecret;
        HdlCloudController.checkAppVersion(versionCode, appCode, listener);
    }
    /**
     * 初始化云端
     */
    public void initCloud(Context context, String appKey, String appSecret) {
        HDLCloudConfig.getInstance().init(context, appKey, appSecret, HdlCloudApi.BASE_CHINA_URL);
        HxHttpConfig.getInstance().ignoreSSL();
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
        httpLoggingInterceptor.level(HttpLoggingInterceptor.Level.BODY);
        HxHttpConfig.getInstance().addInterceptor(httpLoggingInterceptor);
        HxHttpConfig.getInstance()
                //.addInterceptor(new HttpCacheInterceptor())
                .addInterceptor(httpLoggingInterceptor).ignoreSSL();
    }
        HdlCloudController.checkAppVersion(versionCode, appCode, listener);
    /**
     * 获取思必驰token
     *
     * @param homeId   homeId
     * @param clientId clientId
     * @param listener 结果回调
     */
    public void getSibichiToken(String homeId, String clientId, SibichiListener listener) {
        LogUtils.i("获取思必驰token");
        HdlCloudController.getSibichiToken(homeId, clientId, listener);
    }
    /**
     * 获取主网关信息
     *
     * @param homeId   homeId
     * @param listener 结果回调
     */
    public void syncMainGateway(String homeId, GatewayListener listener) {
        LogUtils.i("获取主网关信息");
        HdlCloudController.syncMainGateway(homeId, listener);
    }
    /**
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/response/BindInfoBean.java
New file
@@ -0,0 +1,338 @@
package com.hdl.sdk.connect.bean.response;
import android.text.TextUtils;
import java.io.Serializable;
/**
 * Created by Tong on 2021/11/14.
 * on+扫码绑定
 */
public class BindInfoBean implements Serializable {
    private String homeId;
    private String homeName;
    private String token;
    private String expiresIn;
    private Long expiration;
    private String refreshToken;
    private String refreshExpiresIn;
    private Long refreshExpiration;
    //社区编号
    private String communityCode;
    private String communityId;
    private String houseCode;
    private String groupName;
    //期/区 号
    private String groupNum;
    private String buildName;
    //楼栋号
    private String buildNum;
    //楼层号
    private String floorNum;
    private String floorName;
    private String houseNo;
    //单元名称
    private String unitName;
    //单元号
    private String unitNum;
    private String localSecret;
    //小区维度 本地密钥
    private String communityLocalSecret;
    //设备分机号
    private String deviceNumber;
    //项目类型
    private String projectType;
    private String userId;
    //空间编号 用于跟门口机通讯,不包含分机号
    public String getHomeId() {
        return homeId;
    }
    public void setHomeId(String homeId) {
        this.homeId = homeId;
    }
    public String getHomeName() {
        return homeName;
    }
    public void setHomeName(String homeName) {
        this.homeName = homeName;
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }
    public String getExpiresIn() {
        return expiresIn;
    }
    public void setExpiresIn(String expiresIn) {
        this.expiresIn = expiresIn;
    }
    public Long getExpiration() {
        return expiration;
    }
    public void setExpiration(Long expiration) {
        this.expiration = expiration;
    }
    public String getRefreshToken() {
        return refreshToken;
    }
    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }
    public String getRefreshExpiresIn() {
        return refreshExpiresIn;
    }
    public void setRefreshExpiresIn(String refreshExpiresIn) {
        this.refreshExpiresIn = refreshExpiresIn;
    }
    public Long getRefreshExpiration() {
        return refreshExpiration;
    }
    public void setRefreshExpiration(Long refreshExpiration) {
        this.refreshExpiration = refreshExpiration;
    }
    public String getCommunityCode() {
        return communityCode;
    }
    public void setCommunityCode(String communityCode) {
        this.communityCode = communityCode;
    }
    public String getCommunityId() {
        return communityId;
    }
    public void setCommunityId(String communityId) {
        this.communityId = communityId;
    }
    public String getHouseCode() {
        return houseCode;
    }
    public void setHouseCode(String houseCode) {
        this.houseCode = houseCode;
    }
    public String getGroupName() {
        return groupName;
    }
    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
    public String getGroupNum() {
        return groupNum;
    }
    public void setGroupNum(String groupNum) {
        this.groupNum = groupNum;
    }
    public String getBuildName() {
        return buildName;
    }
    public void setBuildName(String buildName) {
        this.buildName = buildName;
    }
    public String getBuildNum() {
        return buildNum;
    }
    public void setBuildNum(String buildNum) {
        this.buildNum = buildNum;
    }
    public String getHouseNo() {
        return houseNo;
    }
    public void setHouseNo(String houseNo) {
        this.houseNo = houseNo;
    }
    public String getUnitName() {
        return unitName;
    }
    public void setUnitName(String unitName) {
        this.unitName = unitName;
    }
    public String getUnitNum() {
        return unitNum;
    }
    public void setUnitNum(String unitNum) {
        this.unitNum = unitNum;
    }
    public String getLocalSecret() {
        return localSecret;
    }
    public void setLocalSecret(String localSecret) {
        this.localSecret = localSecret;
    }
    public String getCommunityLocalSecret() {
        return communityLocalSecret;
    }
    public void setCommunityLocalSecret(String communityLocalSecret) {
        this.communityLocalSecret = communityLocalSecret;
    }
    public String getDeviceNumber() {
        return deviceNumber;
    }
    public void setDeviceNumber(String deviceNumber) {
        this.deviceNumber = deviceNumber;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getFloorNum() {
        return floorNum;
    }
    public void setFloorNum(String floorNum) {
        this.floorNum = floorNum;
    }
    public String getFloorName() {
        return floorName;
    }
    public void setFloorName(String floorName) {
        this.floorName = floorName;
    }
    public String getProjectType() {
        return projectType;
    }
    public void setProjectType(String projectType) {
        this.projectType = projectType;
    }
    /**
     * @return 空间编号,没有分机号
     */
    public String getSpaceCode() {
        return generateSpaceCode();
    }
    /**
     * 生成空间编号
     */
    public String generateSpaceCode() {
        StringBuilder builder = new StringBuilder();
        //期/区 号
        if (!TextUtils.isEmpty(groupNum)) {
            builder.append("-").append(groupNum);
        }
        //楼栋号
        if (!TextUtils.isEmpty(buildNum)) {
            builder.append("-").append(buildNum);
        }
        //单元号
        if (!TextUtils.isEmpty(unitNum)) {
            builder.append("-").append(unitNum);
        }
        //楼层号
        if (!TextUtils.isEmpty(floorNum)) {
            builder.append("-").append(floorNum);
        }
        //门牌号
        if (!TextUtils.isEmpty(houseNo)) {
            builder.append("-").append(houseNo);
        }
        /*//分机号 肯定得有
        if (TextUtils.isEmpty(deviceNumber)) {
            //空的话,用唯一码吧
            Log.i("info", "分机号为空:用唯一码");
            deviceNumber = DeviceUtils.getUniqueCode();
        }
        builder.append("~").append(deviceNumber);*/
        String code = builder.toString();
        int index = code.indexOf("-");
        if (index != -1) {
            code = code.substring(index + 1);
        }
        return code;
    }
    @Override
    public String toString() {
        return "BindInfoBean{" +
                "homeId='" + homeId + '\'' +
                ", homeName='" + homeName + '\'' +
                ", token='" + token + '\'' +
                ", expiresIn='" + expiresIn + '\'' +
                ", expiration=" + expiration +
                ", refreshToken='" + refreshToken + '\'' +
                ", refreshExpiresIn='" + refreshExpiresIn + '\'' +
                ", refreshExpiration=" + refreshExpiration +
                ", communityCode='" + communityCode + '\'' +
                ", communityId='" + communityId + '\'' +
                ", houseCode='" + houseCode + '\'' +
                ", groupName='" + groupName + '\'' +
                ", groupNum='" + groupNum + '\'' +
                ", buildName='" + buildName + '\'' +
                ", buildNum='" + buildNum + '\'' +
                ", floorNum='" + floorNum + '\'' +
                ", floorName='" + floorName + '\'' +
                ", houseNo='" + houseNo + '\'' +
                ", unitName='" + unitName + '\'' +
                ", unitNum='" + unitNum + '\'' +
                ", localSecret='" + localSecret + '\'' +
                ", communityLocalSecret='" + communityLocalSecret + '\'' +
                ", deviceNumber='" + deviceNumber + '\'' +
                ", projectType='" + projectType + '\'' +
                ", userId='" + userId + '\'' +
                '}';
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/CloudCode.java
New file
@@ -0,0 +1,59 @@
package com.hdl.sdk.connect.cloud;
/**
 * Created by Tong on 2021/11/2.
 */
public @interface CloudCode {
    //成功
    int SUCCEED = 0;
    //系统繁忙
    int SYSTEM_BUSY = 1;
    //系统维护 2
    //服务异常 3
    //签名错误 4
    //参数异常 5
    //token超时
    int TOKEN_TIMEOUT = 10001;
    //10002 重新登录
    int RE_LOGIN = 10002;
    //token不合法
    int TOKEN_NOT_STANDARD = 10003;
    //缺少token参数
    int NO_TOKEN = 10004;
    //用户名或者密码错误 10008
    //用户已被禁用 10009
    //10010    用户不存在
    //10012    用户已存在,不能重复注册
    //10404    住宅唯一标识不存在
    //10506    网关不存在
    //10805    设备不存在
    //12005    应用不存在
    //20000    控制失败
    //20001    网关离线
    //20002    设备离线
    //20101    spk不支持该功能属性
    //20102    spk不支持该功能属性值
    //10009 用户
    //10401 住宅交付了
    //10041 : RefreshToken 数据错误
    //10042 : RefreshToken 过期
    //10043 : RefreshToken 解析错误
    // 10003 : 不合法的token,请认真比对token的签名
    // 10004 : 缺少token参数
    // 10006 : 解析用户身份错误,请重新登录
    // 10009 :
    //125102思必驰授权
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/HdlCloudApi.java
@@ -7,10 +7,13 @@
public class HdlCloudApi {
    //正式环境
//    public static final String BASE_CHINA_URL = "https://china-gateway.hdlcontrol.com";
    public static final String BASE_CHINA_URL = "https://china-gateway.hdlcontrol.com";
    //测试环境
    public static final String BASE_CHINA_URL = "https://test-gz.hdlcontrol.com";
//    public static final String BASE_CHINA_URL = "https://test-gz.hdlcontrol.com";
    //登陆刷新token
    public static final String LOGIN = "/smart-footstone/member/oauth/login";
    //申请设备密钥(根据设备MAC)
    public static final String APPLY_DEVICE_SECRET = "/smart-open/third/device/authByMac";
@@ -18,4 +21,13 @@
    //检查app是否更新
    public static final String CHECK_APP_VERSION_URL = "/basis-footstone/app/appVersion/check";
    //获取思必驰token
    public static final String GET_SIBICHI_TOKEN = "/smart-footstone/thirdToken/aiSpeechToken";
    //验证是否on+是否绑定source
    public static final String IS_BIND_URL = "/home-wisdom/source/screen/home/isBind";
    //获取网关列表
    public static final String GET_GATEWAY_LIST = "/home-wisdom/app/gateway/getGatewayList";
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/HdlCloudController.java
@@ -1,7 +1,15 @@
package com.hdl.sdk.connect.cloud;
import com.hdl.hdlhttp.HxHttp;
import com.hdl.hdlhttp.HxHttpBuilder;
import com.hdl.sdk.connect.bean.response.UpdateInfo;
import com.hdl.sdk.connect.cloud.bean.AiLoginInfo;
import com.hdl.sdk.connect.cloud.bean.GatewayInfo;
import com.hdl.sdk.connect.cloud.listener.GatewayListListener;
import com.hdl.sdk.connect.cloud.listener.GatewayListener;
import com.hdl.sdk.connect.cloud.listener.SibichiListener;
import java.util.List;
import io.reactivex.rxjava3.disposables.Disposable;
@@ -62,4 +70,80 @@
                });
    }
    /**
     * 获取思必驰token
     *
     * @return
     */
    public static Disposable getSibichiToken(String homeId, String clientId, SibichiListener listener) {
        return HxHttp.builder()
                .url(HdlCloudApi.GET_SIBICHI_TOKEN)
                .params("homeId", homeId)
                .params("clientId", clientId)
                .build()
                .post()
                .subscribeWith(new HDLResponse<AiLoginInfo>() {
                    @Override
                    public void onResponse(AiLoginInfo response) {
                        listener.onSuccess(response);
                    }
                    @Override
                    public void onFailure(HDLException e) {
                        listener.onError(e);
                    }
                });
    }
    /**
     * 获取主网关
     */
    public static Disposable syncMainGateway(String homeId, GatewayListener listener) {
        return syncGatewayList(homeId, new GatewayListListener() {
            @Override
            public void onSuccess(List<GatewayInfo> info) {
                if (listener != null) {
                    if (info == null || info.isEmpty()) {
                        listener.onError(new HDLException(-5000, "搜索网关失败"));
                    } else {
                        listener.onSuccess(info.get(0));
                    }
                }
            }
            @Override
            public void onError(HDLException e) {
                if (listener != null) {
                    listener.onError(e);
                }
            }
        });
    }
    /**
     * 同步获取网关列表
     */
    public static Disposable syncGatewayList(String homeId, GatewayListListener listener) {
        HxHttpBuilder httpBuilder = HxHttp.builder()
                .params("homeId", homeId)
                .url(HdlCloudApi.GET_GATEWAY_LIST);
        return httpBuilder.build().post().subscribeWith(new HDLResponse<List<GatewayInfo>>() {
            @Override
            public void onResponse(List<GatewayInfo> response) {
                if (listener != null) {
                    listener.onSuccess(response);
                }
            }
            @Override
            public void onFailure(HDLException e) {
                if (listener != null) {
                    listener.onError(e);
                }
            }
        });
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/bean/AiLoginInfo.java
New file
@@ -0,0 +1,163 @@
package com.hdl.sdk.connect.cloud.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2024/12/10.
 * 思必驰
 */
public class AiLoginInfo implements Serializable {
    private String accessToken;
    private long expiration;
    private String refreshToken;
    private long refreshExpiration;
    private String headerPrefix;
    private String name;
    private String account;
    private String userPhone;
    private String tenantId;
    private String userId;
    private String role;
    private Integer expiresIn;
    /**
     * 接口验证token
     */
    public String getAccessToken() {
        return accessToken == null ? "" : accessToken;
    }
    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }
    /**
     * token过期时间戳
     */
    public long getExpiration() {
        return expiration;
    }
    public void setExpiration(long expiration) {
        this.expiration = expiration;
    }
    /**
     * refreshToken过期时间戳
     */
    public long getRefreshExpiration() {
        return refreshExpiration;
    }
    public void setRefreshExpiration(long refreshExpiration) {
        this.refreshExpiration = refreshExpiration;
    }
    /**
     * 刷新token需要使用
     */
    public String getRefreshToken() {
        return refreshToken == null ? "" : refreshToken;
    }
    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }
    /**
     * token类型
     */
    public String getHeaderPrefix() {
        return headerPrefix == null ? "" : headerPrefix;
    }
    public void setHeaderPrefix(String headerPrefix) {
        this.headerPrefix = headerPrefix;
    }
    /**
     * 用户真实姓名(没有认证房屋情况下建议使用account)
     */
    public String getName() {
        return name == null ? "" : name;
    }
    public void setName(String name) {
        this.name = name;
    }
    /**
     * 登录账号
     */
    public String getAccount() {
        return account == null ? "" : account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    /**
     * 用户手机号码 老版本使用邮箱注册会没有
     */
    public String getUserPhone() {
        return userPhone == null ? "" : userPhone;
    }
    public void setUserPhone(String userPhone) {
        this.userPhone = userPhone;
    }
    public String getTenantId() {
        return tenantId == null ? "" : tenantId;
    }
    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }
    /**
     * 用户ID
     */
    public String getUserId() {
        return userId == null ? "" : userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getRole() {
        return role == null ? "" : role;
    }
    public void setRole(String role) {
        this.role = role;
    }
    public Integer getExpiresIn() {
        return expiresIn;
    }
    public void setExpiresIn(Integer expiresIn) {
        this.expiresIn = expiresIn;
    }
    @Override
    public String toString() {
        return "AiLoginInfo{" +
                "accessToken='" + accessToken + '\'' +
                ", expiration=" + expiration +
                ", refreshToken='" + refreshToken + '\'' +
                ", refreshExpiration=" + refreshExpiration +
                ", headerPrefix='" + headerPrefix + '\'' +
                ", name='" + name + '\'' +
                ", account='" + account + '\'' +
                ", userPhone='" + userPhone + '\'' +
                ", tenantId='" + tenantId + '\'' +
                ", userId='" + userId + '\'' +
                ", role='" + role + '\'' +
                ", expiresIn=" + expiresIn +
                '}';
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/bean/GatewayInfo.java
New file
@@ -0,0 +1,177 @@
package com.hdl.sdk.connect.cloud.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2021/12/7.
 * 网关信息
 */
public class GatewayInfo implements Serializable {
    private String gatewayId;
    private String aesKey;
    private Integer deviceId;
    private Integer encryptionType;
    private String gatewayType;
    private String groupName;
    private String homeId;
    private String mac;
    private String primaryKey;
    private String projectName;
    private Integer subnetId;
    private String userName;
    private String gatewayStatus;
    private String modifyTime;
    private String localSecret;
    private String lastHeartbeatTime;
    public String getGatewayId() {
        return gatewayId;
    }
    public void setGatewayId(String gatewayId) {
        this.gatewayId = gatewayId;
    }
    public String getAesKey() {
        return aesKey;
    }
    public void setAesKey(String aesKey) {
        this.aesKey = aesKey;
    }
    public Integer getDeviceId() {
        return deviceId;
    }
    public void setDeviceId(Integer deviceId) {
        this.deviceId = deviceId;
    }
    public Integer getEncryptionType() {
        return encryptionType;
    }
    public void setEncryptionType(Integer encryptionType) {
        this.encryptionType = encryptionType;
    }
    public String getGatewayType() {
        return gatewayType;
    }
    public void setGatewayType(String gatewayType) {
        this.gatewayType = gatewayType;
    }
    public String getGroupName() {
        return groupName;
    }
    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
    public String getHomeId() {
        return homeId;
    }
    public void setHomeId(String homeId) {
        this.homeId = homeId;
    }
    public String getMac() {
        return mac;
    }
    public void setMac(String mac) {
        this.mac = mac;
    }
    public String getPrimaryKey() {
        return primaryKey;
    }
    public void setPrimaryKey(String primaryKey) {
        this.primaryKey = primaryKey;
    }
    public String getProjectName() {
        return projectName;
    }
    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }
    public Integer getSubnetId() {
        return subnetId;
    }
    public void setSubnetId(Integer subnetId) {
        this.subnetId = subnetId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getGatewayStatus() {
        return gatewayStatus;
    }
    public void setGatewayStatus(String gatewayStatus) {
        this.gatewayStatus = gatewayStatus;
    }
    public String getModifyTime() {
        return modifyTime;
    }
    public void setModifyTime(String modifyTime) {
        this.modifyTime = modifyTime;
    }
    public String getLocalSecret() {
        return localSecret;
    }
    public void setLocalSecret(String localSecret) {
        this.localSecret = localSecret;
    }
    public String getLastHeartbeatTime() {
        return lastHeartbeatTime;
    }
    public void setLastHeartbeatTime(String lastHeartbeatTime) {
        this.lastHeartbeatTime = lastHeartbeatTime;
    }
    @Override
    public String toString() {
        return "GatewayInfo{" +
                "gatewayId='" + gatewayId + '\'' +
                ", aesKey='" + aesKey + '\'' +
                ", deviceId=" + deviceId +
                ", encryptionType=" + encryptionType +
                ", gatewayType='" + gatewayType + '\'' +
                ", groupName='" + groupName + '\'' +
                ", homeId='" + homeId + '\'' +
                ", mac='" + mac + '\'' +
                ", primaryKey='" + primaryKey + '\'' +
                ", projectName='" + projectName + '\'' +
                ", subnetId=" + subnetId +
                ", userName='" + userName + '\'' +
                ", gatewayStatus='" + gatewayStatus + '\'' +
                ", modifyTime='" + modifyTime + '\'' +
                ", localSecret='" + localSecret + '\'' +
                ", lastHeartbeatTime='" + lastHeartbeatTime + '\'' +
                '}';
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/CloudBroadcast.java
New file
@@ -0,0 +1,58 @@
package com.hdl.sdk.connect.cloud.broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
/**
 * Created by Tong on 2021/11/9.
 * 网络请求广播
 * 需要应用层注册
 */
public abstract class CloudBroadcast extends BroadcastReceiver {
    public static final String KEY_API_CODE = "key_api_code";
    public static final String KEY_API_MESSAGE = "key_api_message";
    @Override
    public final void onReceive(Context context, Intent intent) {
        if (intent == null) return;
        String action = intent.getAction();
        if (action != null && !TextUtils.isEmpty(action)) {
            switch (action) {
                case CloudBroadcastAction.REFRESH_TOKEN_INVALID_ACTION:
                    onLogOut();
                    break;
                case CloudBroadcastAction.API_ERROR_ACTION:
                    try {
                        final String message = intent.getStringExtra(KEY_API_MESSAGE);
                        final int code = intent.getIntExtra(KEY_API_CODE, -1);
                        onOtherError(code, message);
                    } catch (Exception e) {
                        onOtherError(-1, e.getMessage());
                    }
                    break;
            }
        }
    }
    /**
     * 登录失效
     */
    public void onLogOut() {
        //会频繁收到
    }
    /**
     * 消息
     *
     * @param code    code
     * @param message 消息
     */
    public void onOtherError(int code, String message) {
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/CloudBroadcastAction.java
New file
@@ -0,0 +1,17 @@
package com.hdl.sdk.connect.cloud.broadcast;
/**
 * Created by Tong on 2021/11/8.
 */
public class CloudBroadcastAction {
    //token刷新了
    public static final String REFRESH_TOKEN_ACTION = "hdl.cloud.broadcast.action.REFRESH_TOKEN";
    //刷新token失效了
    public static final String REFRESH_TOKEN_INVALID_ACTION = "hdl.cloud.broadcast.action.REFRESH_TOKEN_INVALID";
    //非0且不是刷新token,error
    public static final String API_ERROR_ACTION = "hdl.cloud.broadcast.action.Api_Error";
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/GlobalBroadcastManager.java
New file
@@ -0,0 +1,35 @@
package com.hdl.sdk.connect.cloud.broadcast;
import android.app.Application;
import android.content.IntentFilter;
/**
 * Created by Tong on 2023/05/06.
 * 需要注册全局广播代码放这里
 */
public class GlobalBroadcastManager {
    /**
     * 注册全局广播
     */
    public static void registerGlobalBroadcast(Application app) {
        registerCloudBroadcast(app);
    }
    /**
     * 注册接口返回的广播
     */
    private static void registerCloudBroadcast(Application application) {
        HDLCloudBroadcast hdlCloudBroadcast = new HDLCloudBroadcast();
        IntentFilter filter = new IntentFilter();
        filter.addAction(CloudBroadcastAction.REFRESH_TOKEN_INVALID_ACTION);
        filter.addAction(CloudBroadcastAction.REFRESH_TOKEN_ACTION);
        application.registerReceiver(hdlCloudBroadcast, filter);
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/broadcast/HDLCloudBroadcast.java
New file
@@ -0,0 +1,119 @@
package com.hdl.sdk.connect.cloud.broadcast;
import android.text.TextUtils;
import android.util.Log;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.cloud.HDLException;
import com.hdl.sdk.connect.cloud.HDLResponse;
import com.hdl.sdk.connect.config.HDLCloudConfig;
import com.hdl.sdk.sourceos.bind.BindHomeService;
import com.hdl.sdk.sourceos.qrcode.QRCode;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * Created by Tong on 2022/2/12.
 * token失效处理
 */
public class HDLCloudBroadcast extends CloudBroadcast {
    private final ExecutorService service = Executors.newSingleThreadExecutor();
    private volatile Future<?> mBindTokenTask;
    private static final String TAG = HDLCloudBroadcast.class.getSimpleName();
    /**
     * 登录失效了
     */
    @Override
    public void onLogOut() {
        super.onLogOut();
        refreshBindingToken();
    }
    /**
     * 刷新绑定Token
     */
    public void refreshBindingToken() {
        Log.e(TAG, "refreshBindingToken");
        if (mBindTokenTask != null) {
            try {
                mBindTokenTask.cancel(true);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        final String code = QRCode.getBindQRCode();
        if (!TextUtils.isEmpty(code)) {
            String[] split = code.split("_");
            if (split.length == 3) {
                try {
                    mBindTokenTask = service.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                try {
                                    //防止频繁休眠10秒
                                    Thread.sleep(10 * 1000);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                                BindHomeService.isBind(split[0], split[1], split[2], new HDLResponse<BindInfoBean>() {
                                    @Override
                                    public void onResponse(BindInfoBean response) {
                                        if (response != null) {
                                            HDLCloudConfig.getInstance().setToken(response.getToken());
                                            HDLCloudConfig.getInstance().setRefreshToken(response.getRefreshToken());
                                        }
                                        Log.e(TAG, "refreshBindingToken---> succeed");
                                    }
                                    @Override
                                    public void onFailure(HDLException e) {
                                        Log.e(TAG, "refreshBindingToken error---> " + e);
                                        /*if (e instanceof ApiException) {
                                            if (e.getCode() != CloudCode.NO_TOKEN) {
                                            }
                                        }*/
                                    }
                                });
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @Override
    public void onOtherError(int code, String message) {
        super.onOtherError(code, message);
        Log.d(TAG, "onOtherError: code" + code + "," + message);
        if (code == 125102 || code == 10401) {
            //思必驰失效了, 住宅和当前用户不匹配
            Log.d(TAG, "onOtherError: 思必驰失效了");
            refreshBindingToken();
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/interceptor/EncryptInterceptor.java
@@ -12,6 +12,7 @@
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.cloud.GsonUtils;
import com.hdl.sdk.connect.cloud.MD5Utils;
import com.hdl.sdk.connect.config.HDLCloudConfig;
import java.io.IOException;
import java.net.URLDecoder;
@@ -57,8 +58,8 @@
     */
    private Request encrypt(Request request) {
        final String timestamp = String.valueOf(System.currentTimeMillis());
        final String appKey = HDLLink.getInstance().getAppKey();
        final String appSecret = HDLLink.getInstance().getAppSecret();
        final String appKey = HDLCloudConfig.getInstance().getAppKey();
        final String appSecret = HDLCloudConfig.getInstance().getAppSecret();
        final JsonObject json = getBodyJson(request);
        if (json != null) {
            json.addProperty("appKey", appKey);
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/interceptor/HdlLoginInterceptor.java
New file
@@ -0,0 +1,281 @@
package com.hdl.sdk.connect.cloud.interceptor;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.hdl.hdlhttp.HxHttpConfig;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.connect.cloud.CloudCode;
import com.hdl.sdk.connect.cloud.HdlCloudApi;
import com.hdl.sdk.connect.cloud.broadcast.CloudBroadcast;
import com.hdl.sdk.connect.cloud.broadcast.CloudBroadcastAction;
import com.hdl.sdk.connect.config.HDLCloudConfig;
import com.hdl.sdk.sourceos.utils.SPKey;
import java.io.IOException;
import java.nio.charset.Charset;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
/**
 * Created by Tong on 2021/11/2.
 */
public class HdlLoginInterceptor implements Interceptor {
    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request = chain.request();
        if (!isRefreshTokenHeader(request.headers())) {
            String token = HDLCloudConfig.getInstance().getToken();
            if (!TextUtils.isEmpty(token)) {
                Request processRequest = addToken(request, token);
                Response processResponse = chain.proceed(processRequest);
                return disposeToken(chain, processRequest, processResponse);
            } else {
                //没有缓存token处理
                Response response = chain.proceed(request);
                //是否需要token,需要则广播
                ResponseBody responseBody = response.body();
                if (401 == response.code() || 402 == response.code() || 403 == response.code()) {
                    //网络校验异常,需要token
                    final Intent intent = new Intent();
                    intent.setAction(CloudBroadcastAction.REFRESH_TOKEN_INVALID_ACTION);
                    sendBroadcast(intent);
                } else if (responseBody != null && response.isSuccessful()) {
                    try {
                        MediaType mediaType = responseBody.contentType();
                        if (mediaType != null) {
                            String type = mediaType.toString();
                            //json类型才处理
                            if (!TextUtils.isEmpty(type) && type.contains("json")) {
                                BufferedSource source = responseBody.source();
                                source.request(Long.MAX_VALUE);
                                Buffer buffer = source.getBuffer().clone();
                                String respString = buffer.readString(Charset.defaultCharset());
                                JsonObject object = JsonParser.parseString(respString).getAsJsonObject();
                                int code = object.get("code").getAsInt();
                                if (isTokenLoseByCode(code)) {
                                    //token失效了,需要token
                                    final Intent intent = new Intent();
                                    intent.setAction(CloudBroadcastAction.REFRESH_TOKEN_INVALID_ACTION);
                                    sendBroadcast(intent);
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return response;
            }
        }
        return chain.proceed(request);
    }
    @NonNull
    private Response disposeToken(Chain chain, Request request, Response response) {
        try {
            if (401 == response.code() || 402 == response.code() || 403 == response.code()) {
                final String token = refreshToken();
                if (!TextUtils.isEmpty(token)) {
                    return chain.proceed(addToken(request, token));
                }
                //网络异常不需要重置token
            } else if (response.isSuccessful()) {
                ResponseBody responseBody = response.body();
                if (responseBody != null) {
                    MediaType mediaType = responseBody.contentType();
                    if (mediaType != null) {
                        String type = mediaType.toString();
                        //json类型才处理
                        if (!TextUtils.isEmpty(type) && type.contains("json")) {
                            BufferedSource source = responseBody.source();
                            source.request(Long.MAX_VALUE);
                            Buffer buffer = source.getBuffer().clone();
                            String respString = buffer.readString(Charset.defaultCharset());
                            JsonObject object = JsonParser.parseString(respString).getAsJsonObject();
                            int code = -1;
                            try {
                                if (object != null) {
                                    code = object.get("code").getAsInt();
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            String message = "";
                            try {
                                if (object != null && object.has("message")) {
                                    message = object.get("message").getAsString();
                                    if (TextUtils.isEmpty(message)) {
                                        message = "";
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            if (isTokenLoseByCode(code)) {
                                //刷新token
                                final String token = refreshToken();
                                if (!TextUtils.isEmpty(token)) {
                                    Response newResponse = chain.proceed(addToken(request, token));
                                    //token更新了
                                    final Intent intent = new Intent();
                                    intent.setAction(CloudBroadcastAction.REFRESH_TOKEN_ACTION);
                                    sendBroadcast(intent);
                                    return newResponse;
                                } else {
                                    //清空token
                                    HDLCloudConfig.getInstance().setToken("");
                                    HDLCloudConfig.getInstance().setRefreshToken("");
                                    //token刷新失败,通知token失效了
                                    final Intent intent = new Intent();
                                    intent.setAction(CloudBroadcastAction.REFRESH_TOKEN_INVALID_ACTION);
                                    sendBroadcast(intent);
                                }
                            } else {
                                try {
                                    //其他异常情况
                                    final Intent intent = new Intent();
                                    intent.setAction(CloudBroadcastAction.API_ERROR_ACTION);
                                    intent.putExtra(CloudBroadcast.KEY_API_CODE, code);
                                    intent.putExtra(CloudBroadcast.KEY_API_MESSAGE, message);
                                    sendBroadcast(intent);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                            buffer.clear();
                        }
                    }
                }
            }
        } catch (Exception e) {
            Log.d("HDL===>", "disposeToken: 失败," + e);
        }
        return response;
    }
    /**
     * 根据code判断token是否失效
     *
     * @param code 业务code
     * @return ture 失效
     */
    private boolean isTokenLoseByCode(int code) {
        return CloudCode.TOKEN_TIMEOUT == code ||
                CloudCode.TOKEN_NOT_STANDARD == code ||
                CloudCode.NO_TOKEN == code;
    }
    /**
     * 是否存在刷新头
     */
    private boolean isRefreshTokenHeader(Headers headers) {
        String signHeader = headers.get(SmartHeader.REFRESH_TOKEN_HEADER);
        return !TextUtils.isEmpty(signHeader);
    }
    private Request addToken(Request request, String token) {
        return request.newBuilder()
                .removeHeader("Authorization")
                .build()
                .newBuilder()
                .method(request.method(), request.body())
                .addHeader("Authorization", HDLCloudConfig.getInstance().getTokenHeaderPrefix() + token)
                .build();
    }
    /**
     * 拦截器EncryptInterceptor会处理加密
     *
     * @return token
     */
    private String refreshToken() {
        final String cacheRefreshToken = SPUtils.getString(SPKey.REFRESH_TOKEN);
        if (TextUtils.isEmpty(cacheRefreshToken)) {
            return null;
        }
        final OkHttpClient client = HxHttpConfig.getInstance().getClient();
        Request.Builder builder = new Request.Builder();
        final JsonObject json = new JsonObject();
        json.addProperty("refreshToken", cacheRefreshToken);
        json.addProperty("grantType", "refresh_token");
        final RequestBody requestBody = RequestBody.create(json.toString(), MediaType.parse("application/json;charset=UTF-8"));
        builder.post(requestBody);
        builder.url(HDLCloudConfig.getInstance().getBaseUrl() + HdlCloudApi.LOGIN);
        builder.addHeader(SmartHeader.REFRESH_TOKEN_HEADER, "0");
        try {
            Response response = client.newCall(builder.build()).execute();
            if (response.isSuccessful()) {
                ResponseBody responseBody = response.body();
                if (responseBody != null) {
                    BufferedSource source = responseBody.source();
                    source.request(Long.MAX_VALUE);
                    Buffer buffer = source.getBuffer().clone();
                    String respString = buffer.readString(Charset.defaultCharset());
                    JsonObject object = JsonParser.parseString(respString).getAsJsonObject();
                    JsonObject dataJson = object.get("data").getAsJsonObject();
                    String accessToken = dataJson.get("accessToken").getAsString();
                    String refreshToken = dataJson.get("refreshToken").getAsString();
                    if (!TextUtils.isEmpty(accessToken)) {
                        HDLCloudConfig.getInstance().setToken(accessToken);
                    }
                    if (!TextUtils.isEmpty(refreshToken)) {
                        HDLCloudConfig.getInstance().setRefreshToken(refreshToken);
                    }
                    return accessToken;
                }
            } else {
                Log.d("OKHTTP===>", "refreshToken: 失败了");
            }
        } catch (Exception ignored) {
            Log.d("OKHTTP===>", "refreshToken: 失败,异常");
        }
        return "";
    }
    private void sendBroadcast(Intent intent) {
        HDLCloudConfig.getInstance().getContext().sendBroadcast(intent);
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/GatewayListListener.java
New file
@@ -0,0 +1,15 @@
package com.hdl.sdk.connect.cloud.listener;
import com.hdl.sdk.connect.cloud.BaseCallBack;
import com.hdl.sdk.connect.cloud.bean.GatewayInfo;
import java.util.List;
/**
 * Created by Tong on 2021/12/7.
 * 网关列表
 */
public interface GatewayListListener extends BaseCallBack {
    void onSuccess(List<GatewayInfo> info);
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/GatewayListener.java
New file
@@ -0,0 +1,11 @@
package com.hdl.sdk.connect.cloud.listener;
import com.hdl.sdk.connect.cloud.BaseCallBack;
import com.hdl.sdk.connect.cloud.bean.GatewayInfo;
/**
 * Created by Tong on 2021/12/7.
 */
public interface GatewayListener extends BaseCallBack {
    void onSuccess(GatewayInfo info);
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/cloud/listener/SibichiListener.java
New file
@@ -0,0 +1,14 @@
package com.hdl.sdk.connect.cloud.listener;
import com.hdl.sdk.connect.cloud.BaseCallBack;
import com.hdl.sdk.connect.cloud.bean.AiLoginInfo;
/**
 * Created by Zoro
 * Created on 2021/11/26
 * description:
 */
public interface SibichiListener extends BaseCallBack {
    void onSuccess(AiLoginInfo info);
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/config/HDLCloudConfig.java
New file
@@ -0,0 +1,150 @@
package com.hdl.sdk.connect.config;
import android.content.Context;
import android.text.TextUtils;
import com.hdl.hdlhttp.HxHttpConfig;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.connect.cloud.interceptor.EncryptInterceptor;
import com.hdl.sdk.connect.cloud.interceptor.HdlLoginInterceptor;
import com.hdl.sdk.connect.cloud.interceptor.SmartHeaderInterceptor;
import com.hdl.sdk.sourceos.utils.SPKey;
import okhttp3.Interceptor;
import okhttp3.logging.HttpLoggingInterceptor;
/**
 * Created by panlili on 2025/3/4
 * description:
 */
public class HDLCloudConfig {
    private String mBaseUrl;
    private String mDefBaseUrl;
    private String mLanguage;
    private static final String DEF_TOKEN_HEADER_PREFIX = "Bearer ";
    private HDLCloudConfig() {
    }
    private static class SingletonInstance {
        private static final HDLCloudConfig INSTANCE = new HDLCloudConfig();
    }
    public static HDLCloudConfig getInstance() {
        return SingletonInstance.INSTANCE;
    }
    private Context context;
    private String appKey, appSecret;
    private String token, refreshToken;
    private String tokenHeaderPrefix = DEF_TOKEN_HEADER_PREFIX;
    public Context getContext() {
        return context;
    }
    public void init(Context context, String appKey, String appSecret, String url) {
        this.context = context;
        HxHttpConfig.getInstance().init(context, url)
                .addInterceptor(
                        new HdlLoginInterceptor(),
                        new EncryptInterceptor(),
                        new SmartHeaderInterceptor());
        this.mBaseUrl = url;
        this.mDefBaseUrl = url;
        this.appKey = appKey;
        this.appSecret = appSecret;
        setBaseUrl(url);
    }
    public void addInterceptor(Interceptor... interceptors) {
        HxHttpConfig.getInstance().addInterceptor(interceptors);
    }
    public String getBaseUrl() {
        if (TextUtils.isEmpty(mBaseUrl)) {
            mBaseUrl = SPUtils.getString(SPKey.BASE_URL, "");
        }
        return mBaseUrl;
    }
    public void setBaseUrl(String mBaseUrl) {
        this.mBaseUrl = mBaseUrl;
        HxHttpConfig.getInstance().init(context,mBaseUrl);
        SPUtils.put(SPKey.BASE_URL, mBaseUrl);
    }
    public String getDefBaseUrl() {
        return mDefBaseUrl;
    }
    public void setDefBaseUrl(String mDefBaseUrl) {
        this.mDefBaseUrl = mDefBaseUrl;
    }
    public String getAppKey() {
        return appKey;
    }
    public void setAppKey(String appKey) {
        this.appKey = appKey;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
    public synchronized String getToken() {
        if (TextUtils.isEmpty(token)) {
            token = SPUtils.getString(SPKey.TOKEN);
        }
        return token;
    }
    public synchronized void setToken(String token) {
        this.token = token;
        SPUtils.put(SPKey.TOKEN, token);
    }
    public synchronized String getRefreshToken() {
        if (TextUtils.isEmpty(refreshToken)) {
            refreshToken = SPUtils.getString(SPKey.REFRESH_TOKEN);
        }
        return refreshToken;
    }
    public synchronized void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
        SPUtils.put(SPKey.REFRESH_TOKEN, refreshToken);
    }
    public String getTokenHeaderPrefix() {
        return tokenHeaderPrefix;
    }
    public void setTokenHeaderPrefix(String tokenHeaderPrefix) {
        this.tokenHeaderPrefix = tokenHeaderPrefix;
    }
    public String getLanguage() {
        return mLanguage;
    }
    public void setLanguage(String mLanguage) {
        this.mLanguage = mLanguage;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/config/HDLLinkConfig.java
@@ -21,7 +21,7 @@
    private static final String AUTHENTICATE_IS_LS_KEY = "auth_isls_key";
    public static final String AUTHENTICATE_IS_DEVICEINFO_KEY = "auth_deviceinfo_key";
    public static final String GATEWAY_REMOTEINFO_KEY="gateway_remoteinfo_key";
    public static final String GATEWAY_REMOTEINFO_KEY = "gateway_remoteinfo_key";
    private static final String GATEWAY_PARENTOID_KEY = "gateway_parentoiid_key";
    private String localSecret;//本地加密密钥
@@ -39,9 +39,9 @@
    /**
     * instance
     */
    private  static final HDLLinkConfig instance=new HDLLinkConfig();
    private HDLLinkConfig()
    {
    private static final HDLLinkConfig instance = new HDLLinkConfig();
    private HDLLinkConfig() {
        loadConfig();
    }
@@ -50,7 +50,7 @@
     *
     * @return AuthenticateConfig
     */
    public static  HDLLinkConfig getInstance() {
    public static HDLLinkConfig getInstance() {
//        if (instance == null) {
//            synchronized (HDLLinkConfig.class) {
//                if (instance == null) {
@@ -69,7 +69,7 @@
        this.gatewayId = "";
        this.ipAddress = "";
        this.localSecret = "";
        this.homeId="";
        this.homeId = "";
        SPUtils.remove(AUTHENTICATE_LS_KEY);
        SPUtils.remove(AUTHENTICATE_GATEWAYID_KEY);
        SPUtils.remove(AUTHENTICATE_IPADDRESS_KEY);
@@ -138,14 +138,16 @@
    public String getGatewayId() {
        return gatewayId;
    }
    public void setGatewayId(String gatewayId) {
        this.gatewayId=gatewayId;
        this.gatewayId = gatewayId;
    }
    public String getIpAddress() {
        return ipAddress;
    }
    public void setIpAddress(String ipAddress){
    public void setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
    }
@@ -209,8 +211,8 @@
    public AuthenticateRequest.AuthenticateDeviceInfoBean getDeviceInfoBean() {
        AuthenticateRequest.AuthenticateDeviceInfoBean infoBean = (AuthenticateRequest.AuthenticateDeviceInfoBean) SPUtils.getSerializableEntity(AUTHENTICATE_IS_DEVICEINFO_KEY);
        if(infoBean==null){
            infoBean= new AuthenticateRequest.AuthenticateDeviceInfoBean();
        if (infoBean == null) {
            infoBean = new AuthenticateRequest.AuthenticateDeviceInfoBean();
        }
        return infoBean;
    }
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageDecoder.java
@@ -3,39 +3,19 @@
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.RequiresApi;
import com.google.gson.reflect.TypeToken;
import com.hdl.sdk.common.config.TopicConstant;
import com.hdl.sdk.common.event.EventDispatcher;
import com.hdl.sdk.common.exception.HDLLinkException;
import com.hdl.sdk.common.utils.ByteUtils;
import com.hdl.sdk.common.utils.LogUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.common.utils.gson.GsonConvert;
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.bean.LinkResponse;
import com.hdl.sdk.connect.bean.request.AuthenticateRequest;
import com.hdl.sdk.connect.bean.response.DeviceDeleteResponse;
import com.hdl.sdk.connect.bean.response.DeviceInfoResponse;
import com.hdl.sdk.connect.callback.HDLLinkCallBack;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.connect.socket.HDLAuthSocket;
import com.hdl.sdk.connect.socket.HDLSocket;
import com.hdl.sdk.connect.utils.AesUtil;
import com.hdl.sdk.connect.utils.ByteBufferUtils;
import com.hdl.sdk.socket.codec.ByteToMessageDecoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import android.util.Base64;
import kotlin.ParameterName;
/**
 * Created by Tong on 2021/9/22.
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/socket/HDLAuthSocket.java
@@ -585,7 +585,7 @@
        if (broadcast) {
            ip = IpUtils.getBroadcastAddress();
        }
        HdlSocketHelper.sendUdp(getUdpBoot(), ip, UDP_PORT, message,"", 1, new HdlSocketHelper.HdlSocketListener() {
        HdlSocketHelper.sendUdp(getUdpBoot(), ip, UDP_PORT, message, "", 1, new HdlSocketHelper.HdlSocketListener() {
                    @Override
                    public void onSucceed(Object msg) {
                        if (callBack == null) return;
@@ -703,6 +703,8 @@
                                    HDLLinkConfig.getInstance().setCurrentGateway(searchBean);//设置当前网关
                                    if (mSearchGatewayCallBack != null) {
                                        mSearchGatewayCallBack.onSuccess(searchBean);
                                        HDLSocket.getInstance().isBroadcast = true;
                                        HDLSocket.getInstance().getTcp();
                                    }
                                }
                            }
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/socket/HDLSocket.java
@@ -46,6 +46,7 @@
import com.hdl.sdk.socket.listener.ConnectStatusListener;
import com.hdl.sdk.socket.listener.SendListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
@@ -480,25 +481,30 @@
                @Override
                public void heartbeat() {
                    if (HDLLinkConfig.getInstance().getDeviceInfoBean() == null || HDLLinkConfig.getInstance().getDeviceInfoBean().getOID() == null) {
                        LogUtils.i("DeviceInfoBean为空,需要设置才能正常心跳");
                        return;
                    }
                    String time = String.valueOf(System.currentTimeMillis());
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.addProperty("id", IdUtils.getUUId());
                    jsonObject.addProperty("time_stamp", time);
                    if (HDLLinkConfig.getInstance().getDeviceInfoBean().getOID() == null) {
                        //不走从机入网,通过gatewayId发送
                        if (HDLLinkConfig.getInstance().getGatewayId() == null) {
                            return;
                        }
                        String topic = String.format(TopicConstant.HEARTBEAT, HDLLinkConfig.getInstance().getGatewayId());
                        LinkRequest message = new LinkRequest(topic,
                                jsonObject.toString());
                        sendMsg(message.getSendBytes(), null, null, null);
                    if (HDLLinkConfig.getInstance().getRequestBean() != null) {
                        jsonObject.addProperty("mac", HDLLinkConfig.getInstance().getRequestBean().getMAC());
                    } else {
                        if (HDLLinkConfig.getInstance().getRequestBean() != null) {
                            jsonObject.addProperty("mac", HDLLinkConfig.getInstance().getRequestBean().getMAC());
                        }
                        //走从机入网,通过oid发送
                        String topic = String.format(TopicConstant.HEARTBEAT, HDLLinkConfig.getInstance().getDeviceInfoBean().getOID());
                        LinkRequest message = new LinkRequest(topic,
                                jsonObject.toString());
                        sendMsg(message.getSendBytes(), null, null, null);
                    }
                    String topic = String.format(TopicConstant.HEARTBEAT, HDLLinkConfig.getInstance().getDeviceInfoBean().getOID());
                    LinkRequest message = new LinkRequest(topic,
                            jsonObject.toString());
                    sendMsg(message.getSendBytes(), null, null, null);
                }
            });
        }
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/socket/SocketBoot.java
@@ -179,7 +179,7 @@
                public void run() {
                    while (true) {
                        try {
//                            LogUtils.i("initConnectThread: connected=" + connected + " isBroadcast=" + HDLSocket.getInstance().isBroadcast);
                            LogUtils.i("initConnectThread: connected=" + connected + " isBroadcast=" + HDLSocket.getInstance().isBroadcast);
                            if (!connected && HDLSocket.getInstance().isBroadcast) {
                                reconect();
                            }
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BaseEvent.java
New file
@@ -0,0 +1,12 @@
package com.hdl.sdk.sourceos.bind;
import java.io.Serializable;
/**
 * Created by Tong on 2021/11/12.
 */
public interface BaseEvent extends Serializable {
    @EventType
    String getEventType();
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BindAuthEvent.java
New file
@@ -0,0 +1,37 @@
package com.hdl.sdk.sourceos.bind;
/**
 * Created by Tong on 2022/3/23.
 * 扫码绑定事件
 */
public class BindAuthEvent implements BaseEvent {
    //on+绑定成功
    public static final int ON_PLUS_BINDING_SUCCEED_ACTION = 0;
    //on+绑定超时
    public static final int ON_PLUS_BINDING_TIMEOUT_ACTION = 1;
    //网络异常
    public static final int ON_PLUS_BINDING_ERROR_ACTION = 2;
    private int action;
    public BindAuthEvent(int action) {
        this.action = action;
    }
    @Override
    public String getEventType() {
        return EventType.ON_PLUS_BINDING_TYPE;
    }
    public int getAction() {
        return action;
    }
    public void setAction(int action) {
        this.action = action;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/BindHomeService.java
New file
@@ -0,0 +1,184 @@
package com.hdl.sdk.sourceos.bind;
import android.text.TextUtils;
import com.hdl.hdlhttp.HxHttp;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.cloud.HDLException;
import com.hdl.sdk.connect.cloud.HDLResponse;
import com.hdl.sdk.connect.cloud.HdlCloudApi;
import com.hdl.sdk.connect.config.HDLCloudConfig;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.sourceos.Rk3566Manager;
import com.hdl.sdk.sourceos.qrcode.QRCode;
import com.hdl.sdk.sourceos.utils.DeviceUtils;
import com.hdl.sdk.sourceos.utils.SPKey;
import com.hdl.sdk.sourceos.utils.thread.ThreadUtils;
import org.greenrobot.eventbus.EventBus;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
/**
 * Created by Tong on 2021/11/11.
 * on+是否绑定
 * 1、绑定成功需要查询住宅信息
 * 2、扫码3分钟超时
 */
public class BindHomeService {
    private ScheduledExecutorService queryThread;
    private final CompositeDisposable compositeDisposable = new CompositeDisposable();
    //最大时间
    private static final long MAX_TIME = 3 * 60 * 1000;
    //上一次执行时间
    private final AtomicLong lastTime = new AtomicLong(0);
    private BindHomeService() {
    }
    private static class SingletonInstance {
        private static final BindHomeService INSTANCE = new BindHomeService();
    }
    public static BindHomeService getInstance() {
        return SingletonInstance.INSTANCE;
    }
    /**
     * 轮询是否绑定
     *
     * @param timestamp 时间戳
     */
    public void startQuery(String timestamp) {
        stopQuery();
        lastTime.set(System.currentTimeMillis());
        String packageName = Rk3566Manager.getInstance().getContext().getPackageName();
        String unique = DeviceUtils.getUniqueCode();
        queryThread = ThreadUtils.newScheduledThreadPool(1);
        queryThread.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                long timeMillis = System.currentTimeMillis();
                if (timeMillis - lastTime.get() < MAX_TIME) {
                    compositeDisposable.add(isBind(packageName, unique, timestamp));
                } else {
                    //超时了
                    EventBus.getDefault().post(new BindAuthEvent(BindAuthEvent.ON_PLUS_BINDING_TIMEOUT_ACTION));
                    stopQuery();
                }
            }
        }, 0L, 800L, TimeUnit.MILLISECONDS);
    }
    /**
     * 停止查询
     */
    public void stopQuery() {
        lastTime.set(0);
        if (queryThread != null && !queryThread.isShutdown()) {
            queryThread.shutdownNow();
        }
        if (!compositeDisposable.isDisposed()) {
            compositeDisposable.dispose();
        }
    }
    /**
     * 通过保存的code查询是否绑定
     *
     * @param bindCode 保存的code查询是否绑定
     * @return
     */
    public static Disposable isBindByCode(String bindCode) {
        if (TextUtils.isEmpty(bindCode)) return null;
        String[] code = bindCode.split("_");
        if (code.length < 3) {
            return null;
        }
        String packageName = code[0];
        String unique = code[1];
        String timestamp = code[2];
        return isBind(packageName, unique, timestamp);
    }
    public static Disposable isBind(String timestamp) {
        String packageName = Rk3566Manager.getInstance().getContext().getPackageName();
        String unique = DeviceUtils.getUniqueCode();
        return isBind(packageName, unique, timestamp);
    }
    /**
     * @param timestamp   时间戳
     * @param packageName 包名
     * @param unique      序列号
     * @return
     */
    public static Disposable isBind(String packageName, String unique, String timestamp) {
        return isBind(packageName, unique, timestamp, new HDLResponse<BindInfoBean>() {
            @Override
            public void onResponse(BindInfoBean response) {
                handleBind(response, timestamp);
            }
            @Override
            public void onFailure(HDLException e) {
                //网络异常、或者接口报错
                EventBus.getDefault().post(new BindAuthEvent(BindAuthEvent.ON_PLUS_BINDING_ERROR_ACTION));
            }
        });
    }
    /**
     * @param timestamp   时间戳
     * @param packageName 包名
     * @param unique      序列号
     * @return
     */
    public static Disposable isBind(String packageName, String unique, String timestamp, HDLResponse<BindInfoBean> response) {
        return HxHttp.builder()
                .url(HdlCloudApi.IS_BIND_URL)
                .params("packageName", packageName)
                .params("unique", unique)
                .params("qrcodeTimestamp", timestamp)
                .build()
                .executePost()
                .subscribeWith(response);
    }
    /**
     * 处理绑定
     *
     * @param response  返回
     * @param timestamp 时间戳
     */
    private static void handleBind(BindInfoBean response, String timestamp) {
        if (!TextUtils.isEmpty(response.getHomeId())) {
            SPUtils.saveSerializableEntity(SPKey.BIND_HOME_INFO, response);
            SPUtils.put(SPKey.BIND_CODE, QRCode.createBindQRCodeInfo(timestamp));
            HDLCloudConfig.getInstance().setToken(response.getToken());
            HDLCloudConfig.getInstance().setRefreshToken(response.getRefreshToken());
            EventBus.getDefault().post(new BindAuthEvent(BindAuthEvent.ON_PLUS_BINDING_SUCCEED_ACTION));
            getInstance().stopQuery();
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/bind/EventType.java
New file
@@ -0,0 +1,27 @@
package com.hdl.sdk.sourceos.bind;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import androidx.annotation.StringDef;
import java.lang.annotation.Retention;
/**
 * Created by Tong on 2021/11/8.
 */
@StringDef({
        EventType.TEST_TYPE,
        EventType.ON_PLUS_BINDING_TYPE
})
@Retention(SOURCE)
public @interface EventType {
    //测试数据
    String TEST_TYPE = "event_test_type";
    //on+绑定成功
    String ON_PLUS_BINDING_TYPE = "event_on_plus_binding_type";
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QRCode.java
New file
@@ -0,0 +1,65 @@
package com.hdl.sdk.sourceos.qrcode;
import android.net.Uri;
import android.text.TextUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.sourceos.Rk3566Manager;
import com.hdl.sdk.sourceos.utils.DeviceUtils;
import com.hdl.sdk.sourceos.utils.SPKey;
/**
 * Created by Tong on 2021/11/8.
 */
public class QRCode {
    private static final String SOURCE_SCHEME = "source";
    //绑定source
    private static final String BIND_HOST = "bind";
    /**
     * @return source://bind/包名_序列号_时间戳
     */
    public static String getBindQRCodeInfo() {
        return SPUtils.getString(SPKey.BIND_CODE);
    }
    /**
     * @return 包名_序列号_时间戳
     */
    public static String getBindQRCode(String code) {
        if (!TextUtils.isEmpty(code)) {
            Uri uri = Uri.parse(code);
            return uri.getLastPathSegment();
        }
        return null;
    }
    /**
     * @return 包名_序列号_时间戳
     */
    public static String getBindQRCode() {
        String info = QRCode.getBindQRCodeInfo();
        return getBindQRCode(info);
    }
    /**
     * 序列号android10 会获取不到
     *
     * @return 获取绑定二维码信息, source://bind/包名_序列号_时间戳
     */
    public static String createBindQRCodeInfo(String time) {
        final String packageName = Rk3566Manager.getInstance().getContext().getPackageName();
        final String deviceId = DeviceUtils.getUniqueCode();
        final String builder = packageName +
                "_" +
                deviceId +
                "_" +
                time;
        final Uri uri = new Uri.Builder().scheme(SOURCE_SCHEME).authority(BIND_HOST)
                .appendPath(builder).build();
        return uri.toString();
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QRCodeUtils.java
New file
@@ -0,0 +1,71 @@
package com.hdl.sdk.sourceos.qrcode;
import android.graphics.Bitmap;
import android.text.TextUtils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.util.HashMap;
import java.util.Map;
/**
 * Created by Tong on 2021/11/8.
 * 二维码
 */
public class QRCodeUtils {
    /**
     * 生成二维码
     */
    public static Bitmap createQRCode(String txt, int width, int height, int margin) throws WriterException {
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        createQRCode(bitmap, txt, width, height, margin);
        return bitmap;
    }
    /**
     * 生成二维码
     */
    public static void createQRCode(Bitmap bitmap, String txt, int width, int height, int margin) throws WriterException {
        if (TextUtils.isEmpty(txt)) {
            return;
        }
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        if (margin >= 0) {
            hints.put(EncodeHintType.MARGIN, margin);
        }
        // 生成二维矩阵
        BitMatrix matrix = new MultiFormatWriter().encode(txt,
                BarcodeFormat.QR_CODE, width, height, hints);
        // 二维矩阵转为一维像素数组,也就是一直横着排了
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (matrix.get(x, y)) {
                    pixels[y * width + x] = 0xff000000;
                } else {
                    pixels[y * width + x] = 0xffffffff;
                }
            }
        }
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/qrcode/QrCodeView.java
New file
@@ -0,0 +1,141 @@
package com.hdl.sdk.sourceos.qrcode;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * Created by Tong on 2021/12/2.
 * 二维码View
 */
public class QrCodeView extends View {
    private static final int DEF_MARGIN = 0;
    private int mWidth;
    private int mHeight;
    private String mContent;
    private int mMargin = DEF_MARGIN;
    private Bitmap drawBitmap;
    private Canvas drawCanvas;
    private Paint paint;
    private final AtomicBoolean isDraw = new AtomicBoolean(false);
    private DrawThread mDrawThread;
    public QrCodeView(Context context) {
        this(context, null);
    }
    public QrCodeView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public QrCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (drawBitmap != null) {
            canvas.drawBitmap(drawBitmap, getPaddingLeft(), getPaddingTop(), paint);
        }
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = h - getPaddingTop() - getPaddingBottom();
        mWidth = w - getPaddingStart() - getPaddingEnd();
        if (drawBitmap == null || (drawBitmap.isRecycled() || drawBitmap.getWidth() != mWidth || drawBitmap.getHeight() != mHeight)) {
            drawBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
            drawCanvas = new Canvas(drawBitmap);
            startDraw();
        }
    }
    public void setContent(String content) {
        setContent(content, DEF_MARGIN);
    }
    public void setContent(String content, @Px int margin) {
        this.mContent = content;
        startDraw();
        this.mMargin = margin;
    }
    public String getContent() {
        return mContent;
    }
    public void startDraw() {
        if (mContent == null || isDraw.get()) return;
        isDraw.set(true);
        mDrawThread = new DrawThread();
        mDrawThread.start();
    }
    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }
    private class DrawThread extends Thread {
        @Override
        public void run() {
            while (isDraw.get())
                try {
                    if (drawCanvas != null) {
                        drawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        int size = Math.min(mHeight, mWidth);
                        QRCodeUtils.createQRCode(drawBitmap, mContent, size, size, mMargin);
                        postInvalidate();
                        isDraw.set(false);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (isDraw.get()) {
                        try {
                            Thread.sleep(200L);
                        } catch (Exception ignore) {
                        }
                    }
                }
        }
    }
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (drawBitmap != null) {
            drawBitmap.recycle();
        }
        isDraw.set(false);
        if (mDrawThread != null) {
            mDrawThread.interrupt();
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/DeviceUtils.java
New file
@@ -0,0 +1,377 @@
package com.hdl.sdk.sourceos.utils;
import static android.content.Context.WIFI_SERVICE;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.sourceos.OsManager;
import com.hdl.sdk.sourceos.Rk3566Manager;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.UUID;
/**
 * Created by Tong on 2021/11/8.
 * ACCESS_WIFI_STATE
 */
public class DeviceUtils {
    public static final String ANDROID_DEVICE_ID_KEY = "android_device_id_key";
    /**
     * @return 获取设备ID,Android10 只能用uuid生成了
     */
    public static String getUniqueDeviceId() {
        String deviceId = SPUtils.getString(ANDROID_DEVICE_ID_KEY);
        if (!TextUtils.isEmpty(deviceId)) {
            return deviceId;
        }
        deviceId = getDeviceId();
        SPUtils.put(ANDROID_DEVICE_ID_KEY, deviceId);
        return deviceId;
    }
    private static String getDeviceId() {
        String deviceId;
        try {
            deviceId = getOsMacAddress();
            if (!TextUtils.isEmpty(deviceId)) {
                return deviceId;
            }
            deviceId = getMacAddress();
            if (!TextUtils.isEmpty(deviceId)) {
                return deviceId;
            }
            /*deviceId = getIMEI();
            if (!TextUtils.isEmpty(deviceId)) {
                return deviceId;
            }*/
            deviceId = getAndroidId();
            if (!TextUtils.isEmpty(deviceId)) {
                return deviceId;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        deviceId = getUUid("", "");
        return deviceId;
    }
    /**
     * 需要系统权限
     * READ_PRIVILEGED_PHONE_STATE
     */
    /*private static String getIMEI(Context context) {
        try {
            TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                return tm.getImei();
            } else {
                return tm.getDeviceId();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }*/
    private static String getAndroidId() {
        try {
            ContentResolver contentResolver = Rk3566Manager.getInstance().getContext().getApplicationContext().getContentResolver();
            return Settings.System.getString(contentResolver, Settings.Secure.ANDROID_ID);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String getUUid(String prefix, String id) {
        if (id.equals("")) {
            return prefix + UUID.randomUUID().toString().replace("-", "");
        }
        return prefix + UUID.nameUUIDFromBytes(id.getBytes()).toString().replace("-", "");
    }
    public static InetAddress getInetAddress() {
        try {
            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni = nis.nextElement();
                // To prevent phone of xiaomi return "10.0.2.15"
                if (!ni.isUp()) continue;
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress inetAddress = addresses.nextElement();
                    if (!inetAddress.isLoopbackAddress()) {
                        String hostAddress = inetAddress.getHostAddress();
                        if (hostAddress.indexOf(':') < 0) return inetAddress;
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String getMacAddressByWifiInfo() {
        try {
            final WifiManager wifi = (WifiManager) Rk3566Manager.getInstance().getContext().getApplicationContext()
                    .getSystemService(WIFI_SERVICE);
            if (wifi != null) {
                final WifiInfo info = wifi.getConnectionInfo();
                if (info != null) {
                    @SuppressLint({"HardwareIds", "MissingPermission"})
                    String macAddress = info.getMacAddress();
                    if (!TextUtils.isEmpty(macAddress)) {
                        return macAddress;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "02:00:00:00:00:00";
    }
    /**
     * 从有线网卡获取mac
     *
     * @return 有线网关mac
     */
    private static String getMacAddressByEthInterface() {
        try {
            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni = nis.nextElement();
                if (ni == null || !ni.getName().equalsIgnoreCase("eth0")) continue;
                byte[] macBytes = ni.getHardwareAddress();
                if (macBytes != null && macBytes.length > 0) {
                    StringBuilder sb = new StringBuilder();
                    for (byte b : macBytes) {
                        sb.append(String.format("%02x:", b));
                    }
                    return sb.substring(0, sb.length() - 1);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "02:00:00:00:00:00";
    }
    public static String getMacAddressByNetworkInterface() {
        try {
            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni = nis.nextElement();
                if (ni == null || !ni.getName().equalsIgnoreCase("wlan0")) continue;
                byte[] macBytes = ni.getHardwareAddress();
                if (macBytes != null && macBytes.length > 0) {
                    StringBuilder sb = new StringBuilder();
                    for (byte b : macBytes) {
                        sb.append(String.format("%02x:", b));
                    }
                    return sb.substring(0, sb.length() - 1);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "02:00:00:00:00:00";
    }
    private static String getMacAddressByInetAddress() {
        try {
            InetAddress inetAddress = getInetAddress();
            if (inetAddress != null) {
                NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress);
                if (ni != null) {
                    byte[] macBytes = ni.getHardwareAddress();
                    if (macBytes != null && macBytes.length > 0) {
                        StringBuilder sb = new StringBuilder();
                        for (byte b : macBytes) {
                            sb.append(String.format("%02x:", b));
                        }
                        return sb.substring(0, sb.length() - 1);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "02:00:00:00:00:00";
    }
    private static boolean isAddressNotInExcepts(final String address, final String... excepts) {
        if (TextUtils.isEmpty(address)) {
            return false;
        }
        if ("02:00:00:00:00:00".equals(address)) {
            return false;
        }
        if (excepts == null || excepts.length == 0) {
            return true;
        }
        for (String filter : excepts) {
            if (filter != null && filter.equals(address)) {
                return false;
            }
        }
        return true;
    }
    public static String getMacAddress(final String... excepts) {
        String macAddress = getMacAddressByEthInterface();
        if (isAddressNotInExcepts(macAddress, excepts)) {
            return macAddress;
        }
        macAddress = getMacAddressByNetworkInterface();
        if (isAddressNotInExcepts(macAddress, excepts)) {
            return macAddress;
        }
        macAddress = getMacAddressByInetAddress();
        if (isAddressNotInExcepts(macAddress, excepts)) {
            return macAddress;
        }
        macAddress = getMacAddressByWifiInfo();
        if (isAddressNotInExcepts(macAddress, excepts)) {
            return macAddress;
        }
        return "";
    }
    /**
     * 获取设备的固件系统版本
     *
     * @return
     */
    public static String getBuilderNumberDisplay() {
        return Build.DISPLAY;
    }
    /**
     * 设备名
     **/
    public static String getDeviceName() {
        return Build.DEVICE;
    }
    /**
     * 获取手机Android 版本
     *
     * @return
     */
    public static String getDeviceAndroidVersion() {
        return Build.VERSION.RELEASE;
    }
//    /**
//     * 获取设备序列号
//     *
//     * @return the serial of device
//     */
//    @SuppressLint("HardwareIds")
//    public static String getDeviceSerial() {
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//            try {
//                return Build.getSerial();
//            } catch (SecurityException e) {
//                e.printStackTrace();
//                return "";
//            }
//        }
//        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL;
//    }
    /**
     * 获取手机序列号
     *
     * @return 手机序列号
     */
    @SuppressLint({"NewApi", "MissingPermission"})
    public static String getSerialNumber() {
        String serial = "";
        try {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
                serial = Build.getSerial();
            } else {//8.0-
                Class<?> c = Class.forName("android.os.SystemProperties");
                Method get = c.getMethod("get", String.class);
                serial = (String) get.invoke(c, "ro.serialno");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return serial;
    }
    /**
     * 获取APP版本
     *
     * @param context
     * @return
     */
    public static String getAppVersionName(Context context) {
        String versionName = "0";
        try {
            PackageManager packageManager = context.getPackageManager();
            PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
            versionName = packageInfo.versionName;
            if (TextUtils.isEmpty(versionName)) {
                versionName = "0";
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return versionName;
    }
    /**
     * 从系统api中获取mac
     *
     * @return
     */
    private static String getOsMacAddress() {
        String macAddress = OsManager.getEthMacAddress();
        if (isAddressNotInExcepts(macAddress)) {
            return macAddress;
        }
        return null;
    }
    /**
     * 获取唯一码
     */
    public static String getUniqueCode() {
        return getUniqueDeviceId().replaceAll(":", "");
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/SPKey.java
New file
@@ -0,0 +1,19 @@
package com.hdl.sdk.sourceos.utils;
/**
 * Created by panlili on 2025/3/4
 * description:
 */
public class SPKey {
    // 绑定码 source://bind/包名_序列号_时间戳
    public static final String BIND_CODE = "bind_qr_code_info";
    public static final String BASE_URL = "base_url";
    public static final String TOKEN = "cloud_storage_token";
    public static final String REFRESH_TOKEN = "cloud_storage_refresh_token";
    public static final String BIND_HOME_INFO = "bind_home_info";
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/MainThreadExecutor.java
New file
@@ -0,0 +1,152 @@
package com.hdl.sdk.sourceos.utils.thread;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import androidx.annotation.NonNull;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.concurrent.Executor;
/**
 * Created by Tong on 2023/01/03.
 */
public class MainThreadExecutor implements Executor {
    private final LinkedList<Runnable> mQueue = new LinkedList<>();
    private final IdleHandlerImpl mHandler = new IdleHandlerImpl(Looper.getMainLooper());
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private MessageQueue mMessageQueue;
    private MainThreadExecutor() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mMessageQueue = Looper.getMainLooper().getQueue();
        } else {
            try {
                Method getQueue = Looper.class.getDeclaredMethod("getQueue");
                mMessageQueue = (MessageQueue) getQueue.invoke(Looper.getMainLooper());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static class SingletonInstance {
        private static final MainThreadExecutor INSTANCE = new MainThreadExecutor();
    }
    public static MainThreadExecutor getInstance() {
        return SingletonInstance.INSTANCE;
    }
    private class IdleHandlerImpl extends Handler implements MessageQueue.IdleHandler {
        public IdleHandlerImpl(@NonNull Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            Runnable r;
            synchronized (mQueue) {
                if (mQueue.size() == 0) {
                    return;
                }
                r = mQueue.removeFirst();
            }
            r.run();
            synchronized (mQueue) {
                scheduleNextLocked();
            }
        }
        @Override
        public boolean queueIdle() {
            handleMessage(null);
            //ture执行多次,false执行一次
            return false;
        }
    }
    private static class IdleRunnable implements Runnable {
        Runnable mRunnable;
        IdleRunnable(Runnable r) {
            mRunnable = r;
        }
        public void run() {
            mRunnable.run();
        }
    }
    @Override
    public void execute(Runnable command) {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            command.run();
        } else {
            mainHandler.post(command);
        }
    }
    public void post(Runnable runnable) {
        synchronized (mQueue) {
            mQueue.add(runnable);
            if (mQueue.size() == 1) {
                scheduleNextLocked();
            }
        }
    }
    public void postIdle(final Runnable runnable) {
        post(new IdleRunnable(runnable));
    }
    public void postIdle(final Runnable runnable, long delayMillis) {
        mHandler.postDelayed(runnable, delayMillis);
    }
    public void cancelAll() {
        synchronized (mQueue) {
            mQueue.clear();
        }
    }
    public void flush() {
        LinkedList<Runnable> queue;
        synchronized (mQueue) {
            queue = new LinkedList<>(mQueue);
            mQueue.clear();
        }
        for (Runnable r : queue) {
            r.run();
        }
    }
    private void scheduleNextLocked() {
        if (mQueue.size() > 0) {
            Runnable peek = mQueue.getFirst();
            if (peek instanceof IdleRunnable) {
                if (mMessageQueue != null) {
                    mMessageQueue.addIdleHandler(mHandler);
                }
            } else {
                mHandler.sendEmptyMessage(1);
            }
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/RenameThreadFactory.java
New file
@@ -0,0 +1,48 @@
package com.hdl.sdk.sourceos.utils.thread;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * Created by Tong on 2023/05/09.
 */
public abstract class RenameThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    public abstract String getName(int poolNumber, int threadNumber);
    private Thread.UncaughtExceptionHandler exceptionHandler;
    public RenameThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        this.group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
    }
    public RenameThreadFactory(Thread.UncaughtExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
        SecurityManager s = System.getSecurityManager();
        this.group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
    }
    public Thread newThread(Runnable r) {
        Thread t = new Thread(this.group, r, this.getName(poolNumber.getAndIncrement(), this.threadNumber.getAndIncrement()), 0L);
        if (t.isDaemon()) {
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY) {
            t.setPriority(Thread.NORM_PRIORITY);
        }
        if (exceptionHandler != null) {
            t.setUncaughtExceptionHandler(exceptionHandler);
        }
        return t;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/sourceos/utils/thread/ThreadUtils.java
New file
@@ -0,0 +1,319 @@
package com.hdl.sdk.sourceos.utils.thread;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * Created by Tong on 2021/10/21.
 */
public class ThreadUtils {
    private static final Handler uiHandler = new Handler(Looper.getMainLooper());
    private static final ArrayMap<Integer, ExecutorService> mThreadPools = new ArrayMap<>();
    //后台处理线程池
    private static final int IO_TYPE = 0;
    //分发线程池
    private static final int DISPENSE_TYPE = 1;
    private static final Timer TIMER = new Timer();
    //cpu 最大线程容纳量
    private static final int coreSize = Runtime.getRuntime().availableProcessors() + 1;
    private static final int maxCoreSize = 2 * Runtime.getRuntime().availableProcessors() + 1;
    private volatile static ThreadUtils INSTANCE = null;
    private ThreadUtils() {
    }
    public static ThreadUtils getInstance() {
        if (INSTANCE == null) {
            synchronized (ThreadUtils.class) {
                if (INSTANCE == null) {
                    INSTANCE = new ThreadUtils();
                }
            }
        }
        return INSTANCE;
    }
    /**
     * @return 单线程,无限长度队列,会在调用线程执行,频繁用到这个线程说明并发超级多
     */
    private ExecutorService getSingleThread() {
        if (!mThreadPools.isEmpty() && mThreadPools.containsKey(DISPENSE_TYPE)) {
            ExecutorService thread = mThreadPools.get(DISPENSE_TYPE);
            if (thread != null && !thread.isShutdown()) {
                return thread;
            }
        }
        final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new RenameThreadFactory() {
            @Override
            public String getName(int poolNumber, int threadNumber) {
                return "app_dispense_" + poolNumber + "_" + threadNumber;
            }
        }, new ThreadPoolExecutor.DiscardPolicy());
        poolExecutor.allowCoreThreadTimeOut(true);
        mThreadPools.put(DISPENSE_TYPE, poolExecutor);
        return poolExecutor;
    }
    /**
     * 维护 cpu 最大线程容纳量+队列1024 用完在单线程调用
     */
    private ExecutorService getIOThread() {
        if (!mThreadPools.isEmpty() && mThreadPools.containsKey(IO_TYPE)) {
            ExecutorService thread = mThreadPools.get(IO_TYPE);
            if (thread != null && !thread.isShutdown()) {
                return thread;
            }
        }
        final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(coreSize, coreSize, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), new RenameThreadFactory() {
            @Override
            public String getName(int poolNumber, int threadNumber) {
                return "app_io_" + poolNumber + "_" + threadNumber;
            }
            //满了,就使用备份线程池,会oom,使用task会捕抓异常
        }, new BackgroundRunsPolicy());
        poolExecutor.allowCoreThreadTimeOut(true);
        mThreadPools.put(IO_TYPE, poolExecutor);
        return poolExecutor;
    }
    /**
     * 线程数量固定的线程池
     */
    public static ExecutorService newFixedThreadPool(int size) {
        if (size == 0 || coreSize < size) {
            return Executors.newFixedThreadPool(coreSize);
        }
        return Executors.newFixedThreadPool(size);
    }
    /**
     * 定时任务线程池
     */
    public static ScheduledExecutorService newScheduledThreadPool(int size) {
        if (size == 0 || coreSize < size) {
            return Executors.newScheduledThreadPool(coreSize);
        }
        return Executors.newScheduledThreadPool(size);
    }
    /**
     * 切换回主线程
     */
    public static void runOnUiThread(Runnable run) {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            try {
                run.run();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            run.run();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 延时更新Ui
     */
    public static void runOnUiThreadDelay(Runnable run, long delayMillis) {
        uiHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    run.run();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, delayMillis);
    }
    /**
     * 后台耗时任务
     */
    public static void runAsyncThread(Runnable run) {
        try {
            getInstance().getIOThread().execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        run.run();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 删除后台任务
     */
    public static void removeTask(Task task) {
        if (task == null) {
            return;
        }
        try {
            task.cancel();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 有休眠
     * 后台耗时延迟任务
     */
    public static void runAsyncThread(Runnable run, long time) {
        try {
            final TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    try {
                        ExecutorService thread = getInstance().getIOThread();
                        if (!thread.isShutdown()) {
                            thread.execute(run);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            TIMER.schedule(timerTask, time);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    public abstract static class Task implements ITask {
        private Thread thread;
        public abstract void doInBackground();
        private final AtomicBoolean isCancel = new AtomicBoolean();
        public Task() {
            isCancel.set(false);
        }
        @Override
        public void run() {
            thread = Thread.currentThread();
            try {
                if (!isCancel.get()) {
                    doInBackground();
                }
            } catch (Exception e) {
                e.printStackTrace();
                onError(e);
            }
            thread = null;
        }
        public void cancel() {
            try {
                isCancel.set(true);
                if (thread != null) {
                    thread.interrupt();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onError(Throwable t) {
            t.printStackTrace();
        }
    }
    public interface ITask extends Runnable {
        void onError(Throwable t);
    }
    public static class BackgroundRunsPolicy implements RejectedExecutionHandler {
        public BackgroundRunsPolicy() {
        }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                try {
                    backgroundRun(r);
                } catch (Exception e1) {
                    if (r instanceof ITask) {
                        ((ITask) r).onError(e1);
                    } else {
                        throw e1;
                    }
                }
            }
        }
        public void backgroundRun(Runnable runnable) {
            getInstance().getSingleThread().execute(new Runnable() {
                @Override
                public void run() {
                    runnable.run();
                }
            });
        }
    }
}
HDLSDK_DEMO/app/build.gradle
@@ -39,5 +39,10 @@
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'
//    implementation 'com.google.code.gson:gson:2.8.8'
    implementation files('libs\\com.hdl.sdk-v1.2.0.aar')
    implementation files('libs\\com.hdl.sdk-v1.2.1.aar')
    //二维码
    implementation 'com.google.zxing:core:3.4.1'
    //事件总线
    implementation 'org.greenrobot:eventbus:3.2.0'
}
HDLSDK_DEMO/app/libs/com.hdl.sdk-v1.2.1.aar
Binary files differ
HDLSDK_DEMO/app/src/main/AndroidManifest.xml
@@ -36,6 +36,11 @@
            android:exported="false"
            android:windowSoftInputMode="adjustPan|stateHidden" />
        <activity
            android:name=".SourceBindActivity"
            android:exported="false"
            android:windowSoftInputMode="adjustPan|stateHidden" />
    </application>
</manifest>
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/App.java
@@ -1,13 +1,11 @@
package com.hdl.hdlsdk;
import android.app.Application;
import android.util.Log;
import com.hdl.sdk.common.HDLSdk;
import com.hdl.sdk.common.event.EventListener;
import com.hdl.sdk.common.utils.LogUtils;
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.bean.LinkResponse;
import com.hdl.sdk.connect.cloud.broadcast.GlobalBroadcastManager;
import com.hdl.sdk.sourceos.OsManager;
/**
 * Created by Tong on 2021/10/8.
@@ -15,6 +13,7 @@
public class App extends Application {
    private String deviceStatusUpdateTopic;
    @Override
    public void onCreate() {
        super.onCreate();
@@ -23,6 +22,15 @@
        //控制SDK日志打印
        HDLSdk.getInstance().setLogEnabled(true);
        //source系统接口初始化
        OsManager.init(this);
        //appkey:ryfElI3tVOT
        //appsecret:AKIn7s1A2YnNvAZRtL8FQxzp0R2KUpIY
        HDLLink.getInstance().initCloud(this, "ryfElI3tVOT", "AKIn7s1A2YnNvAZRtL8FQxzp0R2KUpIY");
        //注册全局广播,刷新token
        GlobalBroadcastManager.registerGlobalBroadcast(this);
    }
    @Override
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/MainActivity.java
@@ -35,6 +35,7 @@
import com.hdl.sdk.common.exception.HDLLinkException;
import com.hdl.sdk.common.utils.IdUtils;
import com.hdl.sdk.common.utils.LogUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.common.utils.gson.GsonConvert;
import com.hdl.sdk.connect.HDLLink;
import com.hdl.sdk.connect.bean.LinkResponse;
@@ -44,6 +45,7 @@
import com.hdl.sdk.connect.bean.request.ListSidRequest;
import com.hdl.sdk.connect.bean.request.ListUploadRequest;
import com.hdl.sdk.connect.bean.response.BaseLocalResponse;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.bean.response.GatewaySearchBean;
import com.hdl.sdk.connect.bean.response.UpdateInfo;
import com.hdl.sdk.connect.callback.HDLLinkCallBack;
@@ -51,8 +53,13 @@
import com.hdl.sdk.connect.cloud.CallBackListener;
import com.hdl.sdk.connect.cloud.CheckAppVersionListener;
import com.hdl.sdk.connect.cloud.HDLException;
import com.hdl.sdk.connect.cloud.bean.GatewayInfo;
import com.hdl.sdk.connect.cloud.listener.GatewayListener;
import com.hdl.sdk.connect.cloud.listener.SibichiListener;
import com.hdl.sdk.connect.cloud.bean.AiLoginInfo;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.connect.socket.HDLAuthSocket;
import com.hdl.sdk.sourceos.utils.SPKey;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
@@ -79,21 +86,12 @@
    private List<SceneBean> sceneList = new ArrayList<>();
    private List<SceneDetailBean> sceneDetailList = new ArrayList<>();
    private List<SceneDetailBean> roomSceneList = new ArrayList<>();
    private GatewayInfo gatewayInfo;
    void applyDeviceSecret() {
        tv.setText("开始申请设备密钥...");
        responseTv.setText("");
//        //正式服务器
//        String appKey = "i8hR07jzrIS";//appkey
//        String appSecret = "BmnJ8RWTtaVEBk24zPPF4UMwfYu0lAWU";//appsecret
        //测试服务器
        String appKey = "FcRyUJlLJFF";
        String appSecret = "wz8wn75ABidx8vXcFGUotqhwFkTaYvvJ";
//        String appKey = "L2OZliZRxHc";
//        String appSecret = "aCIWSvJDOukXfx3kivsKW11x9xdR3IbV";
        String supplier = "JINMAOYUN";//厂商
//        String mac = "AA00000000000100";//设备唯一MAC地址
        String mac = editText.getText().toString();
@@ -103,8 +101,8 @@
            Toast.makeText(this, "mac不能为空!", Toast.LENGTH_SHORT).show();
            return;
        }
        HDLLink.getInstance().applyDeviceSecret(this, appKey, appSecret, supplier, mac, spk, new CallBackListener() {
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().applyDeviceSecret(supplier, mac, spk, new CallBackListener() {
            @Override
            public void onError(HDLException e) {
                tv.setText("申请失败");
@@ -126,13 +124,10 @@
        tv.setText("开始检测更新...");
        responseTv.setText("");
//        //正式服务器
        String appKey = "i8hR07jzrIS";//appkey
        String appSecret = "BmnJ8RWTtaVEBk24zPPF4UMwfYu0lAWU";//appsecret
        String appCode = "1697150870315999233";//appCode
        HDLLink.getInstance().checkAppVersion(this, appKey, appSecret, getAppVersionName(this), appCode, new CheckAppVersionListener() {
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().checkAppVersion(getAppVersionName(this), appCode, new CheckAppVersionListener() {
            @Override
            public void onSuccess(UpdateInfo info) {
                tv.setText("有新更新");
@@ -142,6 +137,60 @@
            @Override
            public void onError(HDLException e) {
                tv.setText("检测更新失败");
                responseTv.setText(e.getMsg());
            }
        });
    }
    void getSibichiToken() {
        tv.setText("获取思必驰token...");
        responseTv.setText("");
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        String clientId = "4ED634B5A7AD97A770A52AC00FF43805";//思必驰clientId
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().getSibichiToken(bindInfoBean.getHomeId(), clientId, new SibichiListener() {
            @Override
            public void onSuccess(AiLoginInfo info) {
                tv.setText("思必驰token");
                responseTv.setText(info.toString());
            }
            @Override
            public void onError(HDLException e) {
                tv.setText("获取思必驰token失败");
                responseTv.setText(e.getMsg());
            }
        });
    }
    void syncMainGateway() {
        tv.setText("获取主网关信息...");
        responseTv.setText("");
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        if (bindInfoBean == null) {
            Toast.makeText(this, "请先扫码绑定住宅!", Toast.LENGTH_SHORT).show();
            return;
        }
        //调用云端接口需初始化HDLLink.getInstance().initCloud();
        HDLLink.getInstance().syncMainGateway(bindInfoBean.getHomeId(), new GatewayListener() {
            @Override
            public void onSuccess(GatewayInfo info) {
                tv.setText("获取主网关信息");
                responseTv.setText(info.toString());
                if (info != null) {
                    HDLLinkConfig.getInstance().setGatewayId(info.getGatewayId());
                    //HDLLinkConfig.getInstance().setIpAddress(info.ip);
                }
            }
            @Override
            public void onError(HDLException e) {
                tv.setText("获取主网关信息失败");
                responseTv.setText(e.getMsg());
            }
        });
@@ -180,7 +229,7 @@
        selectnetwork();
        checkIfCertified();
        initDeviceInfo();//初始化
        //initDeviceInfo();//不走从机入网的方式不需要初始化
        registerAllTopicsListener();
        HDLLink.getInstance().setDeleteNetworkListener(new DeleteNetworkListener() {
            @Override
@@ -226,6 +275,9 @@
        beans.add(new DemoBean("获取房间场景列表"));
        beans.add(new DemoBean("获取自动化列表"));
        beans.add(new DemoBean("⾃动化启⽤禁⽤"));
        beans.add(new DemoBean("生成二维码绑定住宅"));
        beans.add(new DemoBean("获取思必驰token"));
        beans.add(new DemoBean("获取网关信息"));
        demoAdapter = new DemoAdapter(beans);
        rv.setAdapter(demoAdapter);
@@ -313,6 +365,18 @@
                    case 19:
                        //⾃动化启⽤禁⽤
                        editEnableLogic();
                        break;
                    case 20:
                        //生成二维码绑定住宅
                        startSourceBindActivity();
                        break;
                    case 21:
                        //获取思必驰token
                        getSibichiToken();
                        break;
                    case 22:
                        //获取网关信息
                        syncMainGateway();
                        break;
                }
            }
@@ -418,6 +482,20 @@
    }
    void initLink() {
        //step1:先生成二维码,用onpro扫码绑定住宅获取住宅信息
        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
        if (bindInfoBean != null) {
            HDLLinkConfig.getInstance().setHomeId(bindInfoBean.getHomeId());
            HDLLinkConfig.getInstance().setLocalSecret(bindInfoBean.getLocalSecret());
        }
        //step2:再获取网关信息
        if (gatewayInfo != null) {
            HDLLinkConfig.getInstance().setGatewayId(gatewayInfo.getGatewayId());//当前主网关id
        }
    }
    /**
     * 入网认证
     */
@@ -425,15 +503,9 @@
        tv.setText("开始入网认证...");
        //认证提交参数准备
//        测试服务
//        String spkStr = "ir.module";//产品spk
//        String macStr = "AA000000000000AF";//设备唯一MAC地址
//        String secret = "44b360eb74b7ba64";//通过spk和mac提交云端认证后分配的secret
//        正式服务器
        //正式服务器
        String spkStr = "screen.mirror";//产品spk
        String macStr = "f2c5d8bad48f";//设备唯一MAC地址
//        String secret = "e186beeb7974998e";//通过spk和mac提交云端认证后分配的secret
        String mac_key = stringToMD5(stringToMD5(macStr + secret));
        String versionString = "HDL_V1.0.1";//
@@ -560,7 +632,12 @@
        tv.setText("设备功能属性读取");
        responseTv.setText("");
        List<String> sids = new ArrayList<>();
        sids.add(testLightSid);
        if (devicesList.size() != 0) {
            sids.add(devicesList.get(0).getSid());
        } else {
            sids.add(testLightSid);
        }
        HDLLink.getInstance().getFunctionAttribute(sids, new HDLLinkCallBack() {
            @Override
            public void onSuccess(String msg) {
@@ -582,7 +659,11 @@
        tv.setText("读取状态中...");
        responseTv.setText("");
        List<String> list = new ArrayList<>();
        list.add(testLightSid);//要读取设备的sid
        if (devicesList.size() != 0) {
            list.add(devicesList.get(0).getSid());
        } else {
            list.add(testLightSid);//要读取设备的sid
        }
        HDLLink.getInstance().propertyRead(list, new HDLLinkCallBack() {
            @Override
            public void onSuccess(String data) {
@@ -608,7 +689,11 @@
        isOn = !isOn;
        List<DeviceControlRequest> requestList = new ArrayList<>();
        DeviceControlRequest request = new DeviceControlRequest();
        request.setSid(testLightSid);
        if (devicesList.size() != 0) {
            request.setSid(devicesList.get(0).getSid());
        } else {
            request.setSid(testLightSid);//要读取设备的sid
        }
        List<DeviceControlRequest.StatusBean> statusBeanList = new ArrayList<>();
        DeviceControlRequest.StatusBean bean = new DeviceControlRequest.StatusBean();
        bean.setKey("on_off");
@@ -905,6 +990,11 @@
        startActivity(intent);
    }
    void startSourceBindActivity() {
        Intent intent = new Intent(this, SourceBindActivity.class);
        startActivity(intent);
    }
    /**
     * TCP发送 只发一次,不监听回复,不重发
     */
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/SourceBindActivity.java
New file
@@ -0,0 +1,106 @@
package com.hdl.hdlsdk;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.connect.bean.response.BindInfoBean;
import com.hdl.sdk.connect.config.HDLLinkConfig;
import com.hdl.sdk.sourceos.bind.BaseEvent;
import com.hdl.sdk.sourceos.bind.BindAuthEvent;
import com.hdl.sdk.sourceos.bind.BindHomeService;
import com.hdl.sdk.sourceos.bind.EventType;
import com.hdl.sdk.sourceos.qrcode.QRCode;
import com.hdl.sdk.sourceos.qrcode.QrCodeView;
import com.hdl.sdk.sourceos.utils.SPKey;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class SourceBindActivity extends AppCompatActivity {
    private static final String TAG = "SourceBindActivity";
    private QrCodeView qrcodeView;
    private TextView responseTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_source_bind);
        registerEventBus();
        initView();
    }
    @SuppressLint("WrongConstant")
    private void initView() {
        qrcodeView = findViewById(R.id.qrcode_view);
        responseTv = findViewById(R.id.response_tv);
        createBindQRCodeInfo();
    }
    private void createBindQRCodeInfo() {
        final String time = String.valueOf(System.currentTimeMillis());
        String info = QRCode.createBindQRCodeInfo(time);
        qrcodeView.setContent(info);
        //开始轮询
        BindHomeService.getInstance().startQuery(time);
    }
    /**
     * 绑定成功通知事件
     *
     * @param event
     */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEventMessage(BaseEvent event) {
        switch (event.getEventType()) {
            case EventType.ON_PLUS_BINDING_TYPE:
                if (event instanceof BindAuthEvent) {
                    int action = ((BindAuthEvent) event).getAction();
                    if (action == BindAuthEvent.ON_PLUS_BINDING_SUCCEED_ACTION) {
                        //on+绑定成功
                        Toast.makeText(this, "绑定成功", Toast.LENGTH_SHORT).show();
                        BindInfoBean bindInfoBean = (BindInfoBean) SPUtils.getSerializableEntity(SPKey.BIND_HOME_INFO);
                        responseTv.setText(bindInfoBean.toString());
                        if (bindInfoBean != null) {
                            HDLLinkConfig.getInstance().setHomeId(bindInfoBean.getHomeId());
                            HDLLinkConfig.getInstance().setLocalSecret(bindInfoBean.getLocalSecret());
                        }
                    } else if (action == BindAuthEvent.ON_PLUS_BINDING_TIMEOUT_ACTION) {
                        //on+绑定超时
                        Toast.makeText(this, "绑定超时", Toast.LENGTH_SHORT).show();
                    } else if (action == BindAuthEvent.ON_PLUS_BINDING_ERROR_ACTION) {
                        //网络错误
                        //Toast.makeText(this, "网络异常", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
        }
    }
    protected void unregisterEventBus() {
        if (EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
    }
    protected void registerEventBus() {
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterEventBus();
        BindHomeService.getInstance().stopQuery();
    }
}
HDLSDK_DEMO/app/src/main/java/com/hdl/hdlsdk/SourceTestActivity.java
@@ -78,8 +78,6 @@
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REBOOT, Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
        }
        //系统接口初始化
        OsManager.init(SourceTestActivity.this);
        OsManager.addEventListener(eventListener);
HDLSDK_DEMO/app/src/main/res/layout/activity_source_bind.xml
New file
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SourceBindActivity">
    <com.hdl.sdk.sourceos.qrcode.QrCodeView
        android:id="@+id/qrcode_view"
        android:layout_width="125dp"
        android:layout_height="125dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginStart="28dp"
        android:layout_marginTop="28dp"
        android:padding="3dp" />
    <TextView
        android:id="@+id/response_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="3dp"
        android:layout_marginTop="28dp" />
</LinearLayout>