JLChen
2021-11-09 c584c193d5dd4290bcbeddd434e1d642db59eb13
2021-11-09 1.更新
108个文件已添加
1个文件已修改
4889 ■■■■■ 已修改文件
.gitignore 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.gitignore 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.idea/.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.idea/compiler.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.idea/gradle.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.idea/jarRepositories.xml 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/.idea/misc.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/build.gradle 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/androidTest/java/com/hdl/hdlsdk/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/AndroidManifest.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/App.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/DemoAdapter.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/DemoBean.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/java/com/hdl/hdlsdk/MainActivity.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/drawable-v24/ic_launcher_foreground.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/drawable/ic_launcher_background.xml 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/layout/activity_main.xml 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/layout/demo_item.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-hdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-mdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/values-night/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/values/colors.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/values/strings.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/main/res/values/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/app/src/test/java/com/hdl/hdlsdk/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/build.gradle 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/config.gradle 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/gradle.properties 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/gradle/wrapper/gradle-wrapper.jar 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/gradle/wrapper/gradle-wrapper.properties 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/gradlew 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/gradlew.bat 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/build.gradle 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/consumer-rules.pro 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/androidTest/java/com/hdl/sdk/common/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/HDLSdk.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/IHDLClient.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/config/TopicConstant.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/event/EventDispatcher.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/event/EventListener.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/ByteUtils.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/IdUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/IpUtils.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/LogUtils.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/SPUtils.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/ThreadToolUtils.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/gson/GsonConvert.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/gson/ParameterizedTypeImpl.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-common/src/test/java/com/hdl/sdk/common/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/build.gradle 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/consumer-rules.pro 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/androidTest/java/com/hdl/sdk/connect/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/HDLSocket.java 650 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/BaseLocalRequest.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/BaseLocalResponse.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/DeviceControlRequest.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/FunctionAttributeRequest.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/GatewaySearchBean.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/LinkRequest.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/LinkResponse.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/PropertyReadRequest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/PropertyUpRequest.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageDecoder.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageEncoder.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/utils/ProtocolParse.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-connect/src/test/java/com/hdl/sdk/connect/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/README.md 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/build.gradle 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/consumer-rules.pro 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/androidTest/java/com/hdl/sdk/socket/ExampleInstrumentedTest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/AndroidManifest.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketBoot.java 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketOptions.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketRequest.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/annotation/ConnectStatus.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/ClientPool.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/IClient.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/TcpClient.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/UdpClient.java 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/ByteToMessageDecoder.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IHandleFlow.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IHandleMessage.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IMessagePipeLine.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/MessagePipeLine.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/MessageToByteEncoder.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/listener/ConnectStatusListener.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/listener/SendListener.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/hdl-socket/src/test/java/com/hdl/sdk/socket/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HDLSDK/settings.gradle 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -1,8 +1,15 @@
# Created by https://www.toptal.com/developers/gitignore/api/android
# Edit at https://www.toptal.com/developers/gitignore?templates=android
### Android ###
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the Dalvik VM
# Files for the ART/Dalvik VM
*.dex
# Java class files
@@ -11,6 +18,9 @@
# Generated files
bin/
gen/
out/
#  Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
@@ -22,5 +32,72 @@
# Proguard folder generated by Eclipse
proguard/
#Log Files
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/jarRepositories.xml
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
### Android Patch ###
gen-external-apklibs
output.json
# Replacement of .externalNativeBuild directories introduced
# with Android Studio 3.5.
# End of https://www.toptal.com/developers/gitignore/api/android
HDLSDK/.gitignore
New file
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
HDLSDK/.idea/.gitignore
New file
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
HDLSDK/.idea/compiler.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <bytecodeTargetLevel target="11" />
  </component>
</project>
HDLSDK/.idea/gradle.xml
New file
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleMigrationSettings" migrationVersion="1" />
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="testRunner" value="PLATFORM" />
        <option name="distributionType" value="DEFAULT_WRAPPED" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
            <option value="$PROJECT_DIR$/hdl-common" />
            <option value="$PROJECT_DIR$/hdl-connect" />
            <option value="$PROJECT_DIR$/hdl-socket" />
          </set>
        </option>
        <option name="resolveModulePerSourceSet" value="false" />
        <option name="useQualifiedModuleNames" value="true" />
      </GradleProjectSettings>
    </option>
  </component>
</project>
HDLSDK/.idea/jarRepositories.xml
New file
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RemoteRepositoriesConfiguration">
    <remote-repository>
      <option name="id" value="central" />
      <option name="name" value="Maven Central repository" />
      <option name="url" value="https://repo1.maven.org/maven2" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="jboss.community" />
      <option name="name" value="JBoss Community repository" />
      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven" />
      <option name="name" value="maven" />
      <option name="url" value="https://maven.aliyun.com/repository/central" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven2" />
      <option name="name" value="maven2" />
      <option name="url" value="https://maven.aliyun.com/repository/google" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven5" />
      <option name="name" value="maven5" />
      <option name="url" value="https://maven.aliyun.com/repository/public/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven4" />
      <option name="name" value="maven4" />
      <option name="url" value="https://maven.aliyun.com/repository/gradle-plugin" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven3" />
      <option name="name" value="maven3" />
      <option name="url" value="https://maven.aliyun.com/repository/jcenter" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven9" />
      <option name="name" value="maven9" />
      <option name="url" value="https://jitpack.io" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="maven8" />
      <option name="name" value="maven8" />
      <option name="url" value="https://maven.aliyun.com/repository/public" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="Google" />
      <option name="name" value="Google" />
      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
    </remote-repository>
  </component>
</project>
HDLSDK/.idea/misc.xml
New file
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="DesignSurface">
    <option name="filePathToZoomLevelMap">
      <map>
        <entry key="..\:/job/me/Android/HDLSDK/app/src/main/res/layout/activity_main.xml" value="0.1" />
        <entry key="..\:/job/me/Android/HDLSDK/app/src/main/res/layout/demo_item.xml" value="0.28958333333333336" />
      </map>
    </option>
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>
HDLSDK/app/.gitignore
New file
@@ -0,0 +1 @@
/build
HDLSDK/app/build.gradle
New file
@@ -0,0 +1,43 @@
plugins {
    id 'com.android.application'
}
android {
    compileSdk 31
    defaultConfig {
        applicationId "com.hdl.hdlsdk"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'
    implementation project(path: ':hdl-connect')
}
HDLSDK/app/proguard-rules.pro
New file
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
HDLSDK/app/src/androidTest/java/com/hdl/hdlsdk/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.hdl.hdlsdk;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.hdl.hdlsdk", appContext.getPackageName());
    }
}
HDLSDK/app/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.hdl.hdlsdk">
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:name=".App"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.HDLSDK">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
HDLSDK/app/src/main/java/com/hdl/hdlsdk/App.java
New file
@@ -0,0 +1,24 @@
package com.hdl.hdlsdk;
import android.app.Application;
import com.hdl.sdk.common.HDLSdk;
import com.hdl.sdk.connect.HDLSocket;
/**
 * Created by Tong on 2021/10/8.
 */
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        HDLSdk.getInstance().init(this);
    }
    @Override
    public void onTerminate() {
        super.onTerminate();
    }
}
HDLSDK/app/src/main/java/com/hdl/hdlsdk/DemoAdapter.java
New file
@@ -0,0 +1,26 @@
package com.hdl.hdlsdk;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.chad.library.adapter.base.BaseMultiItemQuickAdapter;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.viewholder.BaseViewHolder;
import java.util.List;
/**
 * Created by Tong on 2021/10/8.
 */
public class DemoAdapter extends BaseMultiItemQuickAdapter<DemoBean, BaseViewHolder> {
    public DemoAdapter(@Nullable List<DemoBean> data) {
        super(data);
        addItemType(0,R.layout.demo_item);
    }
    @Override
    protected void convert(@NonNull BaseViewHolder baseViewHolder, DemoBean demoBean) {
        baseViewHolder.setText(R.id.tv_title,demoBean.getName());
    }
}
HDLSDK/app/src/main/java/com/hdl/hdlsdk/DemoBean.java
New file
@@ -0,0 +1,28 @@
package com.hdl.hdlsdk;
import com.chad.library.adapter.base.entity.MultiItemEntity;
/**
 * Created by Tong on 2021/10/8.
 */
public class DemoBean implements MultiItemEntity {
    private String name;
    public DemoBean(String name) {
        this.name = name;
    }
    @Override
    public int getItemType() {
        return 0;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
HDLSDK/app/src/main/java/com/hdl/hdlsdk/MainActivity.java
New file
@@ -0,0 +1,144 @@
package com.hdl.hdlsdk;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.Manifest;
import android.app.Instrumentation;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.listener.OnItemClickListener;
import com.google.gson.JsonObject;
import com.hdl.sdk.common.config.TopicConstant;
import com.hdl.sdk.common.utils.IdUtils;
import com.hdl.sdk.common.utils.IpUtils;
import com.hdl.sdk.connect.HDLSocket;
import com.hdl.sdk.connect.bean.LinkRequest;
import com.hdl.sdk.connect.protocol.LinkMessageDecoder;
import com.hdl.sdk.connect.protocol.LinkMessageEncoder;
import com.hdl.sdk.socket.SocketBoot;
import com.hdl.sdk.socket.SocketOptions;
import com.hdl.sdk.socket.client.UdpClient;
import com.hdl.sdk.socket.codec.MessagePipeLine;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
    private DemoAdapter demoAdapter;
    private RecyclerView rv;
    private TextView tv;
    private TextView responseTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        responseTv = findViewById(R.id.response_tv);
        tv = findViewById(R.id.state_tv);
        rv = findViewById(R.id.rv);
        rv.setLayoutManager(new LinearLayoutManager(this));
        ActivityResultLauncher<String[]> launcher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
            @Override
            public void onActivityResult(Map<String, Boolean> result) {
            }
        });
        launcher.launch(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE});
        final List<DemoBean> beans = new ArrayList<>();
        beans.add(new DemoBean("搜索网关"));
        beans.add(new DemoBean("获取功能列表"));
        beans.add(new DemoBean("功能属性读取"));
        beans.add(new DemoBean("设备控制"));
        beans.add(new DemoBean("状态上报"));
        beans.add(new DemoBean("读取状态"));
        demoAdapter = new DemoAdapter(beans);
        rv.setAdapter(demoAdapter);
        final SocketOptions options = new SocketOptions();
        MessagePipeLine pipeLine = new MessagePipeLine();
        pipeLine.add(new LinkMessageDecoder());
        pipeLine.add(new LinkMessageEncoder());
        options.setHandleMessage(pipeLine);
        options.setEnabledHeartbeat(false);
        demoAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
                switch (position) {
                    case 0:
                        tv.setText("搜索网关中");
                        responseTv.setText("");
                        HDLSocket.getInstance().searchGateway(new HDLSocket.CallBack() {
                            @Override
                            public void onError(String error) {
                                tv.setText("网关获取失败");
                            }
                            @Override
                            public void onResponse(String data) {
                                tv.setText("获取网关成功");
                                responseTv.setText(data);
                            }
                        });
                        break;
                    case 1:
                        tv.setText("获取功能列表中");
                        responseTv.setText("");
                        HDLSocket.getInstance().getFunctionList(new HDLSocket.CallBack() {
                            @Override
                            public void onError(String error) {
                                tv.setText(error);
                            }
                            @Override
                            public void onResponse(String data) {
                                tv.setText("获取功能列表成功");
                                responseTv.setText(data);
                            }
                        });
                        break;
                    case 2:
                        //功能属性读取
                      //  HDLSocket.getInstance().getFunctionAttribute();
                        break;
                    case 3:
                        //设备控制
                         // HDLSocket.getInstance().propertyDown();
                        break;
                    case 4:
                        //状态上报
                        //HDLSocket.getInstance().propertyUp();
                        break;
                    case 5:
                        //读取状态
                        // HDLSocket.getInstance().propertyRead();
                        break;
                }
            }
        });
    }
}
HDLSDK/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
New file
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
        <aapt:attr name="android:fillColor">
            <gradient
                android:endX="85.84757"
                android:endY="92.4963"
                android:startX="42.9492"
                android:startY="49.59793"
                android:type="linear">
                <item
                    android:color="#44000000"
                    android:offset="0.0" />
                <item
                    android:color="#00000000"
                    android:offset="1.0" />
            </gradient>
        </aapt:attr>
    </path>
    <path
        android:fillColor="#FFFFFF"
        android:fillType="nonZero"
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
        android:strokeWidth="1"
        android:strokeColor="#00000000" />
</vector>
HDLSDK/app/src/main/res/drawable/ic_launcher_background.xml
New file
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path
        android:fillColor="#3DDC84"
        android:pathData="M0,0h108v108h-108z" />
    <path
        android:fillColor="#00000000"
        android:pathData="M9,0L9,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,0L19,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,0L29,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,0L39,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,0L49,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,0L59,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,0L69,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,0L79,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M89,0L89,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M99,0L99,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,9L108,9"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,19L108,19"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,29L108,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,39L108,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,49L108,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,59L108,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,69L108,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,79L108,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,89L108,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,99L108,99"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,29L89,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,39L89,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,49L89,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,59L89,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,69L89,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,79L89,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,19L29,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,19L39,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,19L49,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,19L59,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,19L69,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,19L79,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
</vector>
HDLSDK/app/src/main/res/layout/activity_main.xml
New file
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/scrollView"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <View
        android:layout_width="1dp"
        android:layout_height="0dp"
        android:background="#f4f4f4"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/scrollView"
        app:layout_constraintStart_toEndOf="@id/rv"
        app:layout_constraintTop_toTopOf="parent" />
    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintStart_toEndOf="@id/rv"
        app:layout_constraintTop_toTopOf="parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingStart="10dp"
                android:text="当前状态:" />
            <TextView
                android:id="@+id/state_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="未操作" />
            <View
                android:layout_width="match_parent"
                android:layout_height="2dp"
                android:background="#f5f5f5" />
            <TextView
                android:paddingStart="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="响应:" />
            <TextView
                android:id="@+id/response_tv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp" />
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
HDLSDK/app/src/main/res/layout/demo_item.xml
New file
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="40dp">
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <View
        android:layout_width="0dp"
        android:layout_height="1dp"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        android:background="#f5f5f5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
HDLSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
HDLSDK/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
HDLSDK/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
HDLSDK/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
HDLSDK/app/src/main/res/values-night/themes.xml
New file
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.HDLSDK" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_200</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/black</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_200</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>
HDLSDK/app/src/main/res/values/colors.xml
New file
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple_200">#FFBB86FC</color>
    <color name="purple_500">#FF6200EE</color>
    <color name="purple_700">#FF3700B3</color>
    <color name="teal_200">#FF03DAC5</color>
    <color name="teal_700">#FF018786</color>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
</resources>
HDLSDK/app/src/main/res/values/strings.xml
New file
@@ -0,0 +1,3 @@
<resources>
    <string name="app_name">HDLSDK</string>
</resources>
HDLSDK/app/src/main/res/values/themes.xml
New file
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.HDLSDK" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>
HDLSDK/app/src/test/java/com/hdl/hdlsdk/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.hdl.hdlsdk;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
HDLSDK/build.gradle
New file
@@ -0,0 +1,30 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter' }
        maven { url 'https://maven.aliyun.com/repository/public' }
        google()
        maven { url "https://jitpack.io" }
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
HDLSDK/config.gradle
New file
@@ -0,0 +1,7 @@
ext {
    minSdkVersion = 16
    targetSdkVersion = 31
    compileSdkVersion = 31
    versionName = "1.0.0"
}
HDLSDK/gradle.properties
New file
@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
HDLSDK/gradle/wrapper/gradle-wrapper.jar
Binary files differ
HDLSDK/gradle/wrapper/gradle-wrapper.properties
New file
@@ -0,0 +1,6 @@
#Wed Sep 15 00:34:41 CST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
HDLSDK/gradlew
New file
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
    echo "$*"
}
die () {
    echo
    echo "$*"
    echo
    exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`
    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=`expr $i + 1`
    done
    case $i in
        0) set -- ;;
        1) set -- "$args0" ;;
        2) set -- "$args0" "$args1" ;;
        3) set -- "$args0" "$args1" "$args2" ;;
        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi
# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
HDLSDK/gradlew.bat
New file
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
HDLSDK/hdl-common/.gitignore
New file
@@ -0,0 +1 @@
/build
HDLSDK/hdl-common/build.gradle
New file
@@ -0,0 +1,34 @@
plugins {
    id 'com.android.library'
}
android {
    compileSdkVersion rootProject.compileSdkVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    api 'com.google.code.gson:gson:2.8.8'
    api 'androidx.annotation:annotation:1.2.0'
    api 'androidx.collection:collection:1.1.0'
}
HDLSDK/hdl-common/consumer-rules.pro
HDLSDK/hdl-common/proguard-rules.pro
New file
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
HDLSDK/hdl-common/src/androidTest/java/com/hdl/sdk/common/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.hdl.sdk.common;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.hdl.sdk.common.test", appContext.getPackageName());
    }
}
HDLSDK/hdl-common/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hdl.sdk.common">
</manifest>
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/HDLSdk.java
New file
@@ -0,0 +1,30 @@
package com.hdl.sdk.common;
import android.content.Context;
/**
 * Created by Tong on 2021/9/28.
 */
public class HDLSdk {
    private Context context;
    private HDLSdk() {
    }
    private static class SingletonInstance {
        private static final HDLSdk INSTANCE = new HDLSdk();
    }
    public static HDLSdk getInstance() {
        return SingletonInstance.INSTANCE;
    }
    public void init(Context context) {
        this.context = context.getApplicationContext();
    }
    public Context getContext() {
        return context;
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/IHDLClient.java
New file
@@ -0,0 +1,52 @@
package com.hdl.sdk.common;
/**
 * Created by Tong on 2021/9/28.
 * 包含所有操作
 */
public interface IHDLClient {
    /**
     * 搜索网关
     */
    void searchGateway();
    /**
     * 获取网关详细信息
     */
    void getGatewayInfo();
    /**
     * 获取设备列表
     */
    void getDeviceList();
    /**
     * 获取功能列表
     */
    void getFunctionList();
    /**
     * 获取功能属性
     */
    void getFunctionAttribute();
    /**
     * 设备控制
     */
    void deviceControl();
    /**
     * 获取设备状态
     */
    void getDeviceStatus();
    /**
     * 改变设备状态
     */
    void changeDeviceStatus();
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/config/TopicConstant.java
New file
@@ -0,0 +1,52 @@
package com.hdl.sdk.common.config;
/**
 * Created by Tong on 2021/9/22.
 */
public class TopicConstant {
    //搜索网关
    public static final String GATEWAY_SEARCH = "/user/all/custom/gateway/search";
    //搜索网关响应
    public static final String GATEWAY_SEARCH_REPLY = "/user/all/custom/gateway/search_reply";
    //网关详细信息获取s=gw_id
    public static final String GATEWAY_INFO = "/user/%s/custom/gateway/get";
    //获取物理设备列表
    public static final String GET_DEVICE_LIST = " /user/%s/custom/device/list/get";
    //获取物理设备列表响应
    public static final String GET_DEVICE_LIST_REPLY = " /user/%s/custom/device/list/get_reply";
    //获取功能列表
    public static final String GET_FUNCTION_LIST = "/user/%s/custom/function/list/get";
    //获取功能响应
    public static final String GET_FUNCTION_LIST_REPLY = "/user/%s/custom/function/list/get_reply";
    //功能属性读取
    public static final String GET_FUNCTION_ATTRIBUTE = "/user/%s/custom/function/attribute/get";
    //功能属性响应
    public static final String GET_FUNCTION_ATTRIBUTE_REPLY = "/user/%s/custom/function/attribute/get_reply";
    //控制
    public static final String PROPERTY_DOWN = "/base/%s/thing/property/down";
    //控制响应
    public static final String PROPERTY_DOWN_REPLY = "/base/%s/thing/property/down_reply";
    //状态上报
    public static final String PROPERTY_UP = "/base/%s/thing/property/up";
    //状态上报响应
    public static final String PROPERTY_UP_REPLY = "/base/%s/thing/property/up_reply";
    //读取状态
    public static final String PROPERTY_READ = "/base/%s/thing/property/read";
    //读取状态响应
    public static final String PROPERTY_READ_REPLY = "/base/%s/thing/property/read_reply";
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/event/EventDispatcher.java
New file
@@ -0,0 +1,132 @@
package com.hdl.sdk.common.event;
import androidx.annotation.NonNull;
import androidx.collection.ArrayMap;
import com.hdl.sdk.common.utils.ThreadToolUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
/**
 * Created by Tong on 2021/9/22.
 * 事件分发
 */
public class EventDispatcher {
    private static final ArrayMap<Object, List<EventListener>> EVENT = new ArrayMap<>();
    private static final ArrayMap<EventListener, Integer> TYPE = new ArrayMap<>();
    private static final int MAIN_TYPE = 0;
    private static final int IO_TYPE = 1;
    private static final ExecutorService ioThread = ThreadToolUtils.getInstance().newFixedThreadPool(2);
    private EventDispatcher() {
    }
    private static class SingletonInstance {
        private static final EventDispatcher INSTANCE = new EventDispatcher();
    }
    public static EventDispatcher getInstance() {
        return SingletonInstance.INSTANCE;
    }
    public synchronized void register(Object tag, EventListener listener) {
        if (!EVENT.containsKey(tag)) {
            EVENT.put(tag, new ArrayList<>());
        }
        List<EventListener> events = EVENT.get(tag);
        if (events != null && !events.contains(listener)) {
            events.add(listener);
        }
        TYPE.put(listener, MAIN_TYPE);
    }
    public synchronized void registerIo(Object tag, EventListener listener) {
        if (!EVENT.containsKey(tag)) {
            EVENT.put(tag, new ArrayList<>());
        }
        List<EventListener> events = EVENT.get(tag);
        if (events != null && !events.contains(listener)) {
            events.add(listener);
        }
        TYPE.put(listener, IO_TYPE);
    }
    public synchronized void remove(Object tag) {
        ioThread.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (EVENT.containsKey(tag)) {
                        List<EventListener> list = EVENT.get(tag);
                        for (EventListener eventListener : list) {
                            TYPE.remove(eventListener);
                        }
                        EVENT.remove(tag);
                    }
                } catch (Exception ignored) {
                }
            }
        });
    }
    public synchronized void remove(Object tag, EventListener listener) {
        ioThread.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (EVENT.containsKey(tag)) {
                        List<EventListener> ev = EVENT.get(tag);
                        if (ev != null && !ev.isEmpty()) {
                            TYPE.remove(listener);
                            ev.remove(listener);
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        });
    }
    public synchronized void post(Object tag, @NonNull Object o) {
        if (EVENT.containsKey(tag)) {
            List<EventListener> list = EVENT.get(tag);
            if (list != null && !list.isEmpty()) {
                for (EventListener listener : list) {
                    ThreadToolUtils.getInstance().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (listener != null) {
                                listener.onMessage(o);
                            }
                        }
                    });
                }
            }
        }
    }
    public synchronized void clear() {
        EVENT.clear();
        TYPE.clear();
    }
    public synchronized void release() {
        clear();
        ioThread.shutdownNow();
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/event/EventListener.java
New file
@@ -0,0 +1,10 @@
package com.hdl.sdk.common.event;
/**
 * Created by Tong on 2021/9/22.
 */
public interface EventListener {
    void onMessage(Object msg);
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/ByteUtils.java
New file
@@ -0,0 +1,134 @@
package com.hdl.sdk.common.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * Created by Tong on 2021/9/23.
 */
public class ByteUtils {
    public static byte[] toByteArray(List<Byte> list) {
        Byte[] temps = list.toArray(new Byte[0]);
        byte[] result = new byte[temps.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = temps[i];
        }
        return result;
    }
    public static List<Byte> toByteList(byte[] bytes) {
        final List<Byte> list = new ArrayList<>();
        for (byte aByte : bytes) {
            list.add(aByte);
        }
        return list;
    }
    public static byte[] getRangeBytes(List<Byte> list, int start, int end) {
        Byte[] temps = Arrays.copyOfRange(list.toArray(new Byte[0]), start, end);
        byte[] result = new byte[temps.length];
        for (int i = 0; i < temps.length; i++) {
            result[i] = temps[i];
        }
        return result;
    }
    /**
     * 拼接byte
     */
    public static byte[] concatBytes(byte[] bt1, byte[] bt2) {
        if (bt1 == null) {
            return bt2;
        }
        if (bt2 == null) {
            return bt1;
        }
        byte[] bt3 = new byte[bt1.length + bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }
    public boolean endWith(Byte[] src, byte[] target) {
        if (src.length < target.length) {
            return false;
        }
        for (int i = 0; i < target.length; i++) {
            if (target[target.length - i - 1] != src[src.length - i - 1]) {
                return false;
            }
        }
        return true;
    }
    public static int byteIndexOf(byte[] searched, byte[] find, int start) {
        boolean matched;
        int end = find.length - 1;
        int skip = 0;
        for (int index = start; index <= searched.length - find.length; ++index) {
            matched = true;
            if (find[0] != searched[index] || find[end] != searched[index + end]) continue;
            else skip++;
            if (end > 10)
                if (find[skip] != searched[index + skip] || find[end - skip] != searched[index + end - skip])
                    continue;
                else skip++;
            for (int subIndex = skip; subIndex < find.length - skip; ++subIndex) {
                if (find[subIndex] != searched[index + subIndex]) {
                    matched = false;
                    break;
                }
            }
            if (matched) {
                return index;
            }
        }
        return -1;
    }
    public static int getByteIndexOf(byte[] sources, byte[] src) {
        return getByteIndexOf(sources, src, 0, sources.length);
    }
    //判断一个byte数值在另外一个byte数组中对应的游标值
    public static int getByteIndexOf(byte[] sources, byte[] src, int startIndex) {
        return getByteIndexOf(sources, src, startIndex, sources.length);
    }
    //判断一个byte数值在另外一个byte数组中对应的游标值,指定开始的游标和结束的游标位置
    public static int getByteIndexOf(byte[] sources, byte[] src, int startIndex, int endIndex) {
        if (sources == null || src == null || sources.length == 0 || src.length == 0) {
            return -1;
        }
        if (endIndex > sources.length) {
            endIndex = sources.length;
        }
        int i, j;
        for (i = startIndex; i < endIndex; i++) {
            if (sources[i] == src[0] && i + src.length < endIndex) {
                for (j = 1; j < src.length; j++) {
                    if (sources[i + j] != src[j]) {
                        break;
                    }
                }
                if (j == src.length) {
                    return i;
                }
            }
        }
        return -1;
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/IdUtils.java
New file
@@ -0,0 +1,12 @@
package com.hdl.sdk.common.utils;
import java.util.UUID;
/**
 * Created by Tong on 2021/10/8.
 */
public class IdUtils {
    public static String getUUId() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/IpUtils.java
New file
@@ -0,0 +1,72 @@
package com.hdl.sdk.common.utils;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
 * Created by Tong on 2021/9/27.
 */
public class IpUtils {
    /**
     * @return 广播地址
     */
    public static String getBroadcastAddress() {
        try {
            for (Enumeration<NetworkInterface> niEnum = NetworkInterface.getNetworkInterfaces();
                 niEnum.hasMoreElements(); ) {
                NetworkInterface ni = niEnum.nextElement();
                if (!ni.isLoopback()) {
                    for (InterfaceAddress interfaceAddress : ni.getInterfaceAddresses()) {
                        if (interfaceAddress.getBroadcast() != null) {
                            return interfaceAddress.getBroadcast().toString().substring(1);
                        }
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return "255.255.255.255";
    }
    public static String getIP(Context application) {
        WifiManager wifiManager = (WifiManager) application.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        if (!wifiManager.isWifiEnabled()) {
            try {
                for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
                    NetworkInterface intf = en.nextElement();
                    for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                        InetAddress inetAddress = enumIpAddr.nextElement();
                        if (!inetAddress.isLoopbackAddress()) {
                            return inetAddress.getHostAddress();
                        }
                    }
                }
            } catch (SocketException e) {
                e.printStackTrace();
            }
        } else {
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            int ipAddress = wifiInfo.getIpAddress();
            return intToIp(ipAddress);
        }
        return null;
    }
    private static String intToIp(int i) {
        return (i & 0xFF) + "." +
                ((i >> 8) & 0xFF) + "." +
                ((i >> 16) & 0xFF) + "." +
                (i >> 24 & 0xFF);
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/LogUtils.java
New file
@@ -0,0 +1,51 @@
package com.hdl.sdk.common.utils;
/**
 * Created by Tong on 2021/9/23.
 */
public class LogUtils {
    private static final String TAG = "HDLSocket";
    private boolean isEnabled = true;
    private LogUtils() {
    }
    private static class SingletonInstance {
        private static final LogUtils INSTANCE = new LogUtils();
    }
    public static LogUtils getInstance() {
        return SingletonInstance.INSTANCE;
    }
    public boolean isEnabled() {
        return isEnabled;
    }
    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
    }
    public static void d(String tag, String msg) {
    }
    public static void e(String tag, String msg) {
    }
    public static void w(String tag, String msg) {
    }
    public static void v(String tag, String msg) {
    }
    public static void i(String tag, String msg) {
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/SPUtils.java
New file
@@ -0,0 +1,151 @@
package com.hdl.sdk.common.utils;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import com.hdl.sdk.common.HDLSdk;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
 * Created by Tong on 2021/9/28.
 */
public class SPUtils {
    private static final String APP_PREFERENCES_KEY = "profile";
    private static final SharedPreferences PREFERENCES =
            HDLSdk.getInstance().getContext().getApplicationContext().getSharedPreferences(APP_PREFERENCES_KEY, Context.MODE_PRIVATE);
    private static SharedPreferences getAppPreference() {
        return PREFERENCES;
    }
    //======通用存储========
    public static void put(@NonNull final String key, final String value) {
        getAppPreference().edit().putString(key, value).apply();
    }
    public static String getString(@NonNull final String key) {
        return getString(key, "");
    }
    public static String getString(@NonNull final String key, final String defaultValue) {
        return getAppPreference().getString(key, defaultValue);
    }
    public static void put(@NonNull final String key, final int value) {
        put(key, value, false);
    }
    public static void put(@NonNull final String key, final int value, final boolean isCommit) {
        getAppPreference().edit().putInt(key, value).apply();
    }
    public static int getInt(@NonNull final String key) {
        return getInt(key, -1);
    }
    public static int getInt(@NonNull final String key, final int defaultValue) {
        return getAppPreference().getInt(key, defaultValue);
    }
    public static void put(@NonNull final String key, final long value) {
        getAppPreference().edit().putLong(key, value).apply();
    }
    public static long getLong(@NonNull final String key) {
        return getLong(key, -1L);
    }
    public static long getLong(@NonNull final String key, final long defaultValue) {
        return getAppPreference().getLong(key, defaultValue);
    }
    public static void put(@NonNull final String key, final float value) {
        getAppPreference().edit().putFloat(key, value).apply();
    }
    public static float getFloat(@NonNull final String key) {
        return getFloat(key, -1f);
    }
    public static float getFloat(@NonNull final String key, final float defaultValue) {
        return getAppPreference().getFloat(key, defaultValue);
    }
    public static void put(@NonNull final String key, final boolean value) {
        getAppPreference().edit().putBoolean(key, value).apply();
    }
    public static boolean getBoolean(@NonNull final String key) {
        return getBoolean(key, false);
    }
    public static boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
        return getAppPreference().getBoolean(key, defaultValue);
    }
    public static void put(@NonNull final String key,
                           final Set<String> value
    ) {
        getAppPreference().edit().putStringSet(key, value).apply();
    }
    public static Set<String> getStringSet(@NonNull final String key) {
        return getStringSet(key, Collections.<String>emptySet());
    }
    public static Set<String> getStringSet(@NonNull final String key,
                                           final Set<String> defaultValue) {
        return getAppPreference().getStringSet(key, defaultValue);
    }
    public static Map<String, ?> getAll() {
        return getAppPreference().getAll();
    }
    public static boolean contains(@NonNull final String key) {
        return getAppPreference().contains(key);
    }
    public static void remove(@NonNull final String key) {
        getAppPreference().edit().remove(key).apply();
    }
    public static void clear() {
        getAppPreference()
                .edit()
                .clear()
                .apply();
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/ThreadToolUtils.java
New file
@@ -0,0 +1,72 @@
package com.hdl.sdk.common.utils;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
/**
 * Created by Tong on 2021/9/15.
 */
public class ThreadToolUtils {
    private final Handler uiHandler = new Handler(Looper.getMainLooper());
    //cpu 最大线程容纳量
    private final int coreSize = Runtime.getRuntime().availableProcessors() + 1;
    private ThreadToolUtils() {
    }
    private static class SingletonInstance {
        private static final ThreadToolUtils INSTANCE = new ThreadToolUtils();
    }
    public static ThreadToolUtils getInstance() {
        return SingletonInstance.INSTANCE;
    }
    /**
     * 线程数量固定的线程池
     */
    public ExecutorService newFixedThreadPool(int size) {
        if (size == 0 || coreSize < size) {
            return Executors.newFixedThreadPool(coreSize);
        }
        return Executors.newFixedThreadPool(size);
    }
    /**
     * 定时任务线程池
     */
    public ScheduledExecutorService newScheduledThreadPool(int size) {
        if (size == 0 || coreSize < size) {
            return Executors.newScheduledThreadPool(coreSize);
        }
        return Executors.newScheduledThreadPool(size);
    }
    /**
     * 单一线程
     */
    public ExecutorService newSingleThreadPool() {
        return Executors.newSingleThreadExecutor();
    }
    public ExecutorService newCachedThreadPool() {
        return Executors.newCachedThreadPool();
    }
    /**
     * 切换回主线程
     */
    public void runOnUiThread(Runnable run) {
        uiHandler.post(run);
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/gson/GsonConvert.java
New file
@@ -0,0 +1,55 @@
package com.hdl.sdk.common.utils.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
/**
 * Created by Tong on 2021/9/8.
 */
public class GsonConvert {
    private static Gson gson = null;
    public static Gson getGson() {
        if (gson == null) {
            synchronized (GsonConvert.class) {
                if (gson == null) {
                    gson = new GsonBuilder()
                            .setPrettyPrinting()
                            .disableHtmlEscaping()
                            .registerTypeAdapter(String.class, new StringTypeAdapter())
                            .create();
                }
            }
        }
        return gson;
    }
    private static class StringTypeAdapter implements JsonSerializer<String>, JsonDeserializer<String> {
        @Override
        public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            if (json instanceof JsonPrimitive) {
                return json.getAsString();
            } else {
                return json.toString();
            }
        }
        @Override
        public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
}
HDLSDK/hdl-common/src/main/java/com/hdl/sdk/common/utils/gson/ParameterizedTypeImpl.java
New file
@@ -0,0 +1,46 @@
package com.hdl.sdk.common.utils.gson;
import androidx.annotation.NonNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
 * Created by Tong on 2021/9/17.
 */
public class ParameterizedTypeImpl implements ParameterizedType {
    private final Type[] actualTypeArguments;
    private final Type rawType;
    private final Type ownerType;
    public ParameterizedTypeImpl(Type rawType, Type[] actualTypeArguments, Type ownerType) {
        this.ownerType = ownerType;
        this.rawType = rawType;
        this.actualTypeArguments = actualTypeArguments;
    }
    public static Type getType(Type rawType, Type[] actualTypeArguments) {
        return new ParameterizedTypeImpl(rawType, actualTypeArguments, null);
    }
    @NonNull
    @Override
    public Type[] getActualTypeArguments() {
        return actualTypeArguments;
    }
    @NonNull
    @Override
    public Type getRawType() {
        return rawType;
    }
    @Override
    public Type getOwnerType() {
        return ownerType;
    }
}
HDLSDK/hdl-common/src/test/java/com/hdl/sdk/common/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.hdl.sdk.common;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
HDLSDK/hdl-connect/.gitignore
New file
@@ -0,0 +1 @@
/build
HDLSDK/hdl-connect/build.gradle
New file
@@ -0,0 +1,34 @@
plugins {
    id 'com.android.library'
}
android {
    compileSdkVersion rootProject.compileSdkVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    api project(path: ':hdl-socket')
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
}
HDLSDK/hdl-connect/consumer-rules.pro
HDLSDK/hdl-connect/proguard-rules.pro
New file
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
HDLSDK/hdl-connect/src/androidTest/java/com/hdl/sdk/connect/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.hdl.sdk.connect;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.hdl.sdk.connect.test", appContext.getPackageName());
    }
}
HDLSDK/hdl-connect/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hdl.sdk.connect">
</manifest>
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/HDLSocket.java
New file
@@ -0,0 +1,650 @@
package com.hdl.sdk.connect;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
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.event.EventListener;
import com.hdl.sdk.common.utils.IdUtils;
import com.hdl.sdk.common.utils.IpUtils;
import com.hdl.sdk.common.utils.SPUtils;
import com.hdl.sdk.common.utils.ThreadToolUtils;
import com.hdl.sdk.common.utils.gson.GsonConvert;
import com.hdl.sdk.connect.bean.BaseLocalRequest;
import com.hdl.sdk.connect.bean.BaseLocalResponse;
import com.hdl.sdk.connect.bean.DeviceControlRequest;
import com.hdl.sdk.connect.bean.FunctionAttributeRequest;
import com.hdl.sdk.connect.bean.GatewaySearchBean;
import com.hdl.sdk.connect.bean.LinkRequest;
import com.hdl.sdk.connect.bean.LinkResponse;
import com.hdl.sdk.connect.bean.PropertyReadRequest;
import com.hdl.sdk.connect.bean.PropertyUpRequest;
import com.hdl.sdk.connect.protocol.LinkMessageDecoder;
import com.hdl.sdk.connect.protocol.LinkMessageEncoder;
import com.hdl.sdk.socket.SocketBoot;
import com.hdl.sdk.socket.SocketOptions;
import com.hdl.sdk.socket.client.TcpClient;
import com.hdl.sdk.socket.client.UdpClient;
import com.hdl.sdk.socket.codec.MessagePipeLine;
import com.hdl.sdk.socket.listener.ConnectStatusListener;
import com.hdl.sdk.socket.listener.SendListener;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * Created by Tong on 2021/9/26.
 * 1、通过Udp 组播或者广播搜索网关
 * 2、通过Udp 获取Tcp ip 端口统一8586
 */
public class HDLSocket {
    private static final String GATEWAY_KEY = "gateway_key";
    private static final String TCP_IP_KEY = "tcp_ip_key";
    private String gatewayId;
    public interface CallBack {
        void onError(String error);
        void onResponse(String data);
    }
    /**
     * udp默认组播ip
     */
    private static final String UDP_GROUP_IP = "239.0.168.188";
    /**
     * udp默认端口
     */
    private static final int UDP_PORT = 8585;
    /**
     * tcp默认端口
     */
    private static final int TCP_PORT = 8586;
    private String tcpIp;
    private int tcpPort;
    private int udpPort;
    private String udpIp;
    private static SocketBoot updBoot;
    private SocketBoot tcpBoot;
    private ConnectStatusListener statusListener;
    private EventListener searchEvent;
    private CallBack searchCallBack;
    private ScheduledExecutorService searchGatewayThread;
    private final AtomicInteger searchCount = new AtomicInteger(0);
    private HDLSocket() {
        statusListener = new ConnectStatusListener() {
            @Override
            public void onConnecting() {
            }
            @Override
            public void onConnected() {
            }
            @Override
            public void onConnectFailed() {
            }
        };
        searchEvent = new EventListener() {
            @Override
            public void onMessage(Object msg) {
                try {
                    if (msg instanceof LinkResponse) {
                        LinkResponse linkResponse = (LinkResponse) msg;
                        String data = linkResponse.getData();
                        if (!TextUtils.isEmpty(data)) {
                            final BaseLocalResponse<GatewaySearchBean> response = GsonConvert.getGson().fromJson(data, new TypeToken<BaseLocalResponse<GatewaySearchBean>>() {
                            }.getType());
                            GatewaySearchBean searchBean = response.getObjects();
                            if (searchBean != null) {
                                gatewayId = searchBean.getGatewayId();
                                if (!TextUtils.isEmpty(gatewayId)) {
                                    SPUtils.put(GATEWAY_KEY, gatewayId);
                                }
                                tcpIp = searchBean.getIp_address();
                                if (!TextUtils.isEmpty(tcpIp)) {
                                    SPUtils.put(TCP_IP_KEY, tcpIp);
                                }
                            }
                            if (searchCallBack != null) {
                                searchCallBack.onResponse(linkResponse.toString());
                            }
                        }
                    }
                } catch (Exception e) {
                    if (searchCallBack != null) {
                        searchCallBack.onError("解析失败");
                    }
                }
            }
        };
    }
    private static class SingletonInstance {
        private static final HDLSocket INSTANCE = new HDLSocket();
    }
    public static HDLSocket getInstance() {
        return SingletonInstance.INSTANCE;
    }
    private SocketOptions getUdpOptions() {
        final SocketOptions options = new SocketOptions();
        final MessagePipeLine pipeLine = new MessagePipeLine();
        pipeLine.add(new LinkMessageDecoder());
        pipeLine.add(new LinkMessageEncoder());
        options.setHandleMessage(pipeLine);
        options.setEnabledHeartbeat(false);
        return options;
    }
    private SocketOptions getTcpOptions() {
        final SocketOptions options = new SocketOptions();
        final MessagePipeLine pipeLine = new MessagePipeLine();
        pipeLine.add(new LinkMessageDecoder());
        pipeLine.add(new LinkMessageEncoder());
        options.setHandleMessage(pipeLine);
        options.setEnabledHeartbeat(false);
        return options;
    }
    private int getUdpPort() {
        return UDP_PORT;
    }
    public int getTcpPort() {
        return TCP_PORT;
    }
    public String getTcpIp() {
        if (!TextUtils.isEmpty(tcpIp)) {
            return tcpIp;
        }
        return SPUtils.getString(TCP_IP_KEY, "");
    }
    public String getGatewayId() {
        if (!TextUtils.isEmpty(gatewayId)) {
            return gatewayId;
        }
        return SPUtils.getString(GATEWAY_KEY, "");
    }
    private String getUdpIp() {
        if (TextUtils.isEmpty(udpIp)) {
            udpIp = UDP_GROUP_IP;
        }
        return udpIp;
    }
    public void searchGateway() {
        searchGateway(null);
    }
    /**
     * 组播搜索
     */
    public void searchGateway(CallBack callBack) {
        this.searchCallBack = callBack;
        if (searchGatewayThread != null) {
            searchGatewayThread.shutdownNow();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (TextUtils.isEmpty(gatewayId)) {
                    //搜索网关
                    searchGateway(IdUtils.getUUId(), searchEvent);
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
    /**
     * 通过组播搜索网关
     */
    public void searchGateway(String msgId, EventListener eventListener) {
        searchGateway(getUdpIp(), getUdpPort(), msgId, eventListener);
    }
    /**
     * 通过广播搜索网关
     */
    public void searchGatewayByBroadcast(String msgId, EventListener eventListener) {
        searchGateway(IpUtils.getBroadcastAddress(), getUdpPort(), msgId, eventListener);
    }
    /**
     * 默认是组播搜索网关
     */
    public void searchGateway(String ip, int port, String msgId, EventListener eventListener) {
        if (updBoot == null) {
            updBoot = UdpClient.init(ip, port, getUdpOptions());
            updBoot.connect();
        }
        String time = String.valueOf(System.currentTimeMillis());
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", msgId);
        jsonObject.addProperty("time_stamp", time);
        EventDispatcher.getInstance().registerIo(TopicConstant.GATEWAY_SEARCH_REPLY, eventListener);
        LinkRequest message = new LinkRequest(TopicConstant.GATEWAY_SEARCH,
                jsonObject.toString());
        try {
            updBoot.sendMsg(message.toString().getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取设备列表
     */
    public void getDeviceList(CallBack callBack) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("id", IdUtils.getUUId());
            jsonObject.addProperty("time_stamp", time);
            String topic = String.format(TopicConstant.GET_DEVICE_LIST, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    jsonObject.toString());
            String replyTopic = String.format(TopicConstant.GET_DEVICE_LIST_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("获取设备列表失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("获取设备列表失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("ip地址丢失");
            }
        }
    }
    /**
     * 获取功能列表
     */
    public void getFunctionList(CallBack callBack) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("id", IdUtils.getUUId());
            jsonObject.addProperty("time_stamp", time);
            String topic = String.format(TopicConstant.GET_FUNCTION_LIST, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    jsonObject.toString());
            String replyTopic = String.format(TopicConstant.GET_FUNCTION_LIST_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("获取功能列表失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("获取功能列表失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("ip地址丢失");
            }
        }
    }
    /**
     * 获取功能属性
     *
     * @param callBack
     * @param sid
     */
    public void getFunctionAttribute(CallBack callBack, String... sid) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            final BaseLocalResponse<List<FunctionAttributeRequest>> data = new BaseLocalResponse<>();
            data.setId(IdUtils.getUUId());
            data.setTime_stamp(time);
            List<FunctionAttributeRequest> list = new ArrayList<>();
            for (String s : sid) {
                list.add(new FunctionAttributeRequest(s));
            }
            data.setObjects(list);
            String topic = String.format(TopicConstant.GET_FUNCTION_ATTRIBUTE, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    GsonConvert.getGson().toJson(data));
            String replyTopic = String.format(TopicConstant.GET_FUNCTION_ATTRIBUTE_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("获取功能属性失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("获取功能属性失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("ip地址丢失");
            }
        }
    }
    /**
     * 设备控制
     */
    public void propertyDown(List<DeviceControlRequest> request, CallBack callBack) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            final BaseLocalResponse<List<DeviceControlRequest>> data = new BaseLocalResponse<>();
            data.setId(IdUtils.getUUId());
            data.setTime_stamp(time);
            data.setObjects(request);
            String topic = String.format(TopicConstant.PROPERTY_DOWN, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    GsonConvert.getGson().toJson(request));
            String replyTopic = String.format(TopicConstant.PROPERTY_DOWN_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("控制指令发送失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("控制指令发送失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("控制指令发送失败");
            }
        }
    }
    /**
     * 状态上报
     */
    public void propertyUp(List<PropertyUpRequest> request, CallBack callBack) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            final BaseLocalResponse<List<PropertyUpRequest>> data = new BaseLocalResponse<>();
            data.setId(IdUtils.getUUId());
            data.setTime_stamp(time);
            data.setObjects(request);
            String topic = String.format(TopicConstant.PROPERTY_UP, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    GsonConvert.getGson().toJson(request));
            String replyTopic = String.format(TopicConstant.PROPERTY_UP_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("指令发送失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("指令发送失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("指令发送失败");
            }
        }
    }
    /**
     * 读取状态
     */
    public void propertyRead(List<PropertyReadRequest> request, CallBack callBack) {
        if (!TextUtils.isEmpty(getGatewayId()) && !TextUtils.isEmpty(getTcpIp())) {
            String time = String.valueOf(System.currentTimeMillis());
            final BaseLocalResponse<List<PropertyReadRequest>> data = new BaseLocalResponse<>();
            data.setId(IdUtils.getUUId());
            data.setTime_stamp(time);
            data.setObjects(request);
            String topic = String.format(TopicConstant.PROPERTY_READ, getGatewayId());
            LinkRequest message = new LinkRequest(topic,
                    GsonConvert.getGson().toJson(request));
            String replyTopic = String.format(TopicConstant.PROPERTY_READ_REPLY, getGatewayId());
            try {
                sendMsg(message.toString().getBytes("utf-8"), replyTopic, callBack, new SendListener() {
                    @Override
                    public void onSucceed() {
                    }
                    @Override
                    public void onError() {
                        if (callBack != null) {
                            callBack.onError("指令发送失败");
                        }
                    }
                });
            } catch (UnsupportedEncodingException e) {
                if (callBack != null) {
                    callBack.onError("指令发送失败");
                }
            }
        } else {
            if (callBack != null) {
                callBack.onError("指令发送失败");
            }
        }
    }
    public SocketBoot getTcp() throws RuntimeException {
        if (TextUtils.isEmpty(getTcpIp())) {
            throw new RuntimeException("请搜索网关");
        }
        if (tcpBoot == null) {
            tcpBoot = TcpClient.init(getTcpIp(), getTcpPort(), getTcpOptions());
        }
        return tcpBoot;
    }
    /**
     * 清空缓存
     */
    public void clearCache() {
        SPUtils.remove(TCP_IP_KEY);
        SPUtils.remove(GATEWAY_KEY);
    }
    /**
     * 发送指令
     * 1秒没响应就让他重新发送,重试3次
     */
    public void sendMsg(byte[] data, String eventTag, CallBack callBack, SendListener sendListener) {
        try {
            final AtomicInteger sendCount = new AtomicInteger(0);
            final ScheduledExecutorService threadPool = ThreadToolUtils.getInstance().newScheduledThreadPool(1);
            final EventListener eventListener = new EventListener() {
                @Override
                public void onMessage(Object msg) {
                    if (msg instanceof LinkResponse) {
                        if (callBack != null) {
                            callBack.onResponse(msg.toString());
                        }
                        threadPool.shutdownNow();
                    }
                }
            };
            threadPool.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    if (sendCount.get() < 3) {
                        sendCount.set(sendCount.get() + 1);
                        getTcp().sendMsg(data);
                    } else {
                        threadPool.shutdownNow();
                        EventDispatcher.getInstance().remove(eventTag, eventListener);
                        ThreadToolUtils.getInstance().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (callBack != null) {
                                    callBack.onError("发送失败");
                                }
                            }
                        });
                    }
                }
            }, 1000, 500, TimeUnit.MILLISECONDS);
            EventDispatcher.getInstance().register(eventTag, eventListener);
            getTcp().sendMsg(data, new SendListener() {
                @Override
                public void onSucceed() {
                    if (sendListener != null) {
                        sendListener.onSucceed();
                    }
                }
                @Override
                public void onError() {
                    if (sendListener != null) {
                        sendListener.onError();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            ThreadToolUtils.getInstance().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (callBack != null) {
                        callBack.onError("发送失败");
                    }
                }
            });
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/BaseLocalRequest.java
New file
@@ -0,0 +1,47 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2021/9/29.
 */
public class BaseLocalRequest<T> implements Serializable {
    private String id;
    private String code;
    private String time_stamp;
    private T objects;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getTime_stamp() {
        return time_stamp;
    }
    public void setTime_stamp(String time_stamp) {
        this.time_stamp = time_stamp;
    }
    public T getObjects() {
        return objects;
    }
    public void setObjects(T objects) {
        this.objects = objects;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/BaseLocalResponse.java
New file
@@ -0,0 +1,38 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2021/9/29.
 */
public class BaseLocalResponse<T> implements Serializable {
    private String id;
    private String time_stamp;
    private T objects;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getTime_stamp() {
        return time_stamp;
    }
    public void setTime_stamp(String time_stamp) {
        this.time_stamp = time_stamp;
    }
    public T getObjects() {
        return objects;
    }
    public void setObjects(T objects) {
        this.objects = objects;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/DeviceControlRequest.java
New file
@@ -0,0 +1,51 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
import java.util.List;
/**
 * Created by Tong on 2021/10/8.
 */
public class DeviceControlRequest implements Serializable {
    private String sid;
    private List<StatusBean> status;
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
    public List<StatusBean> getStatus() {
        return status;
    }
    public void setStatus(List<StatusBean> status) {
        this.status = status;
    }
    public static class StatusBean implements Serializable {
        private String key;
        private String value;
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/FunctionAttributeRequest.java
New file
@@ -0,0 +1,24 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2021/10/8.
 */
public class FunctionAttributeRequest implements Serializable {
    private String sid;
    public FunctionAttributeRequest(String sid) {
        this.sid = sid;
    }
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/GatewaySearchBean.java
New file
@@ -0,0 +1,102 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
/**
 * Created by Tong on 2021/9/29.
 * 网关搜索
 */
public class GatewaySearchBean implements Serializable {
    private String device_model;
    private String device_name;
    private String device_mac;
    private String gatewayId;
    private String gatewayType;
    private String gateway_type;
    private String oid;
    private String ip_address;
    private String access_mode;
    private String master;
    public String getDevice_model() {
        return device_model;
    }
    public void setDevice_model(String device_model) {
        this.device_model = device_model;
    }
    public String getDevice_name() {
        return device_name;
    }
    public void setDevice_name(String device_name) {
        this.device_name = device_name;
    }
    public String getDevice_mac() {
        return device_mac;
    }
    public void setDevice_mac(String device_mac) {
        this.device_mac = device_mac;
    }
    public String getGatewayId() {
        return gatewayId;
    }
    public void setGatewayId(String gatewayId) {
        this.gatewayId = gatewayId;
    }
    public String getGatewayType() {
        return gatewayType;
    }
    public void setGatewayType(String gatewayType) {
        this.gatewayType = gatewayType;
    }
    public String getGateway_type() {
        return gateway_type;
    }
    public void setGateway_type(String gateway_type) {
        this.gateway_type = gateway_type;
    }
    public String getOid() {
        return oid;
    }
    public void setOid(String oid) {
        this.oid = oid;
    }
    public String getIp_address() {
        return ip_address;
    }
    public void setIp_address(String ip_address) {
        this.ip_address = ip_address;
    }
    public String getAccess_mode() {
        return access_mode;
    }
    public void setAccess_mode(String access_mode) {
        this.access_mode = access_mode;
    }
    public String getMaster() {
        return master;
    }
    public void setMaster(String master) {
        this.master = master;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/LinkRequest.java
New file
@@ -0,0 +1,64 @@
package com.hdl.sdk.connect.bean;
import android.text.TextUtils;
import androidx.annotation.NonNull;
/**
 * Created by Tong on 2021/9/29.
 */
public class LinkRequest {
    private String topic;
    private String data;
    private int length;
    public LinkRequest() {
    }
    public LinkRequest(String topic, String data) {
        this.topic = topic;
        setData(data);
    }
    public String getTopic() {
        return topic;
    }
    public void setTopic(String topic) {
        this.topic = topic;
    }
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
        if (!TextUtils.isEmpty(data)) {
            setLength(data.length());
        } else {
            setLength(0);
        }
    }
    public int getLength() {
        return length;
    }
    private void setLength(int length) {
        this.length = length;
    }
    @NonNull
    @Override
    public String toString() {
        return "Topic:" +
                getTopic() +
                "\r\n" +
                "Length:" +
                getLength() +
                "\r\n\r\n" +
                getData();
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/LinkResponse.java
New file
@@ -0,0 +1,55 @@
package com.hdl.sdk.connect.bean;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.hdl.sdk.common.utils.gson.GsonConvert;
import java.io.Serializable;
/**
 * Created by Tong on 2021/9/27.
 */
public class LinkResponse implements Serializable {
    private String topic;
    private String data;
    private int length;
    public String getTopic() {
        return topic;
    }
    public void setTopic(String topic) {
        this.topic = topic;
    }
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
        if (!TextUtils.isEmpty(data)) {
            setLength(data.length());
        } else {
            setLength(0);
        }
    }
    public int getLength() {
        return length;
    }
    private void setLength(int length) {
        this.length = length;
    }
    @NonNull
    @Override
    public String toString() {
        return GsonConvert.getGson().toJson(this);
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/PropertyReadRequest.java
New file
@@ -0,0 +1,17 @@
package com.hdl.sdk.connect.bean;
/**
 * Created by Tong on 2021/10/8.
 */
public class PropertyReadRequest {
    private String sid;
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/bean/PropertyUpRequest.java
New file
@@ -0,0 +1,50 @@
package com.hdl.sdk.connect.bean;
import java.io.Serializable;
import java.util.List;
/**
 * Created by Tong on 2021/10/8.
 */
public class PropertyUpRequest implements Serializable {
    private String sid;
    private List<StatusBean> status;
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
    public List<StatusBean> getStatus() {
        return status;
    }
    public void setStatus(List<StatusBean> status) {
        this.status = status;
    }
    public static class StatusBean implements Serializable{
        private String key;
        private String value;
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageDecoder.java
New file
@@ -0,0 +1,81 @@
package com.hdl.sdk.connect.protocol;
import com.hdl.sdk.common.event.EventDispatcher;
import com.hdl.sdk.common.utils.ByteUtils;
import com.hdl.sdk.connect.bean.LinkResponse;
import com.hdl.sdk.connect.utils.ProtocolParse;
import com.hdl.sdk.socket.codec.ByteToMessageDecoder;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by Tong on 2021/9/22.
 * link协议粘包拆包
 */
public class LinkMessageDecoder extends ByteToMessageDecoder<LinkResponse> {
    private final List<Byte> bytes;
    private final byte[] head = "Topic:".getBytes();
    private final byte[] body = "\r\n\r\n".getBytes();
    public LinkMessageDecoder() {
        this.bytes = new ArrayList<>();
    }
    @Override
    protected LinkResponse decoder(Object msg) throws Exception {
        LinkResponse response = new LinkResponse();
        if (msg instanceof byte[]) {
            //解析流
            byte[] data = (byte[]) msg;
            bytes.addAll(ByteUtils.toByteList(data));
            byte[] byteArray = ByteUtils.toByteArray(bytes);
            int headIndex = ByteUtils.getByteIndexOf(byteArray, head);
            if (headIndex > 0) {
                //移动到head 开始位置
                bytes.subList(0, headIndex).clear();
                byteArray = ByteUtils.toByteArray(bytes);
            }
            int bodyIndex = ByteUtils.getByteIndexOf(byteArray, body);
            if (bodyIndex < 0) {
                //头部未获取完成
                return null;
            }
            int bodyStartIndex = bodyIndex + body.length;
            //解析头部
            ProtocolParse parse = new ProtocolParse(byteArray);
            response.setTopic(parse.getTopic());
            int bodyLength = parse.getLength();
            if (bodyLength > 0) {
                if (byteArray.length >= bodyLength + bodyStartIndex) {
                    byte[] body = ByteUtils.getRangeBytes(bytes, bodyStartIndex, bodyStartIndex + bodyLength);
                    response.setData(new String(body, "utf-8"));
                    if (byteArray.length >= bodyLength + bodyStartIndex) {
                        //保存余留
                        byte[] remaining = ByteUtils.getRangeBytes(bytes, bodyStartIndex + bodyLength, byteArray.length);
                        bytes.clear();
                        for (byte b : remaining) {
                            bytes.add(b);
                        }
                    }
                    //解析完成,topic发送一次
                    EventDispatcher.getInstance().post(response.getTopic(), response);
                    return response;
                }
            } else if (bodyLength == 0) {
                //body为空
                return response;
            }
        }
        return null;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/protocol/LinkMessageEncoder.java
New file
@@ -0,0 +1,16 @@
package com.hdl.sdk.connect.protocol;
import com.hdl.sdk.socket.codec.MessageToByteEncoder;
/**
 * Created by Tong on 2021/9/22.
 * link协议合包
 */
public class LinkMessageEncoder extends MessageToByteEncoder {
    @Override
    protected byte[] encode(byte[] data) throws Exception {
        return data;
    }
}
HDLSDK/hdl-connect/src/main/java/com/hdl/sdk/connect/utils/ProtocolParse.java
New file
@@ -0,0 +1,74 @@
package com.hdl.sdk.connect.utils;
import android.text.TextUtils;
/**
 * Created by Tong on 2021/9/22.
 * 解析Link协议
 */
public class ProtocolParse {
    private String topic;
    private int length;
    private int dataIndex;
    public ProtocolParse(byte[] bytes) {
        parse(bytes);
    }
    private void parse(byte[] bytes) {
        try {
            String[] split = new String(bytes, "utf-8").split("\r\n");
            setTopic(parseTopic(split));
            setLength(parseLength(split));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static String parseTopic(String[] bytes) {
        try {
            for (String s : bytes) {
                if (s.startsWith("Topic:")) {
                    return s.replace("Topic:", "");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    private static int parseLength(String[] bytes) {
        try {
            for (String s : bytes) {
                if (!TextUtils.isEmpty(s) && s.startsWith("Length:")) {
                    return Integer.parseInt(s.replace("Length:", ""));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }
    public String getTopic() {
        return topic;
    }
    public void setTopic(String topic) {
        this.topic = topic;
    }
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
}
HDLSDK/hdl-connect/src/test/java/com/hdl/sdk/connect/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.hdl.sdk.connect;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
HDLSDK/hdl-socket/.gitignore
New file
@@ -0,0 +1 @@
/build
HDLSDK/hdl-socket/README.md
New file
@@ -0,0 +1,8 @@
# hdl-socket库
##说明
tcp+udp 通用封装,实现基本连接
##用法
1、创建Tcp对象
2、创建Udp对象
HDLSDK/hdl-socket/build.gradle
New file
@@ -0,0 +1,33 @@
plugins {
    id 'com.android.library'
}
android {
    compileSdkVersion rootProject.compileSdkVersion
    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    api project(path: ':hdl-common')
}
HDLSDK/hdl-socket/consumer-rules.pro
HDLSDK/hdl-socket/proguard-rules.pro
New file
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
HDLSDK/hdl-socket/src/androidTest/java/com/hdl/sdk/socket/ExampleInstrumentedTest.java
New file
@@ -0,0 +1,26 @@
package com.hdl.sdk.socket;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.hdl.sdk.socket.test", appContext.getPackageName());
    }
}
HDLSDK/hdl-socket/src/main/AndroidManifest.xml
New file
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hdl.sdk.socket">
</manifest>
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketBoot.java
New file
@@ -0,0 +1,327 @@
package com.hdl.sdk.socket;
import android.text.TextUtils;
import android.util.Log;
import androidx.collection.ArrayMap;
import com.hdl.sdk.common.utils.ThreadToolUtils;
import com.hdl.sdk.socket.annotation.ConnectStatus;
import com.hdl.sdk.socket.client.IClient;
import com.hdl.sdk.socket.listener.SendListener;
import java.net.ConnectException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * Created by Tong on 2021/9/26.
 * Tcp/Udp 启动器
 */
public class SocketBoot {
    private ExecutorService connectThread;
    private ScheduledExecutorService heartbeatThread;
    private ExecutorService sendThread;
    private ExecutorService receiveThread;
    private ScheduledExecutorService delayThread;
    private final IClient client;
    /**
     * socket是否在运行
     */
    private final AtomicBoolean isRun = new AtomicBoolean(false);
    private final AtomicBoolean isOpenRetry = new AtomicBoolean(false);
    private final AtomicInteger resendCount = new AtomicInteger(0);
    private final BlockingQueue<SocketRequest> mMessageQueue = new LinkedBlockingDeque<>();
    private final ArrayMap<String, SendListener> sendMap = new ArrayMap<>();
    public SocketBoot(IClient client) {
        this.client = client;
    }
    public ScheduledExecutorService getHeartBeat() {
        if (heartbeatThread == null) {
            heartbeatThread = ThreadToolUtils.getInstance().newScheduledThreadPool(1);
        }
        return heartbeatThread;
    }
    public void connect() {
        resendCount.set(0);
        resetConnect(true);
        isOpenRetry.set(true);
    }
    public synchronized void resetConnect(boolean isFirst) {
        final int maxRetry = client.getOptions().getMaxRetry();
        if (maxRetry == 0 && resendCount.get() > 0 ||
                (maxRetry > 0 && maxRetry + 1 < resendCount.get())) {
            Log.d("====", "===重连次数达到最大==");
            return;
        }
        if (!client.isConnect()) {
            if (connectThread == null) {
                connectThread = ThreadToolUtils.getInstance().newFixedThreadPool(1);
            }
            connectThread.execute(new Runnable() {
                @Override
                public void run() {
                    client.onConnectStatus(ConnectStatus.CONNECTING);
                    if (!isFirst) {
                        try {
                            resendCount.set(resendCount.get() + 1);
                            Thread.sleep(300L);
                            Log.d("====", "==重连第" + resendCount + "次==");
                        } catch (Exception ignored) {
                        }
                    }
                    try {
                        client.connect();
                        isRun.set(true);
                        if (client.isConnect()) {
                            Log.d("====", "====连接成功====");
                            startHeartbeat();
                            initSendThread();
                            initReceiveThread();
                            client.onConnectStatus(ConnectStatus.CONNECTED);
                            resendCount.set(0);
                        } else {
                            throw new ConnectException();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.d("====", "===连接失败===" + e);
                        //再判断一下有没有连接
                        if (!client.isConnect()) {
                            isRun.set(false);
                            client.onConnectStatus(ConnectStatus.DISCONNECT);
                            stopHeartbeat();
                            disconnectError();
                        }
                    }
                }
            });
        }
    }
    public void initSendThread() {
        if (sendThread == null) {
            sendThread = ThreadToolUtils.getInstance().newFixedThreadPool(1);
        }
        sendThread.execute(new Runnable() {
            @Override
            public void run() {
                while (isRun.get()) {
                    if (client.isConnect()) {
                        Log.d("=====", "==发送数据==");
                        try {
                            SocketRequest socketRequest = mMessageQueue.take();
                            final String action = socketRequest.getAction();
                            try {
                                client.sendMsg(socketRequest.getData());
                                if (!TextUtils.isEmpty(action)) {
                                    SendListener sendListener = sendMap.get(action);
                                    if (sendListener != null) {
                                        sendListener.onSucceed();
                                    }
                                }
                            } catch (Exception e) {
                                if (!TextUtils.isEmpty(action)) {
                                    SendListener sendListener = sendMap.get(action);
                                    if (sendListener != null) {
                                        sendListener.onError();
                                    }
                                }
                                stopHeartbeat();
                                if (sendThread != null) {
                                    sendThread.shutdownNow();
                                }
                                if (isRun.get()) {
                                    disconnectError();
                                }
                            }
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
                Log.d("=====", "==发送线程关闭==");
            }
        });
    }
    public void initReceiveThread() {
        if (receiveThread == null) {
            receiveThread = ThreadToolUtils.getInstance().newFixedThreadPool(1);
        }
        receiveThread.execute(new Runnable() {
            @Override
            public void run() {
                while (isRun.get()) {
                    if (client.isConnect()) {
                        try {
                            //读取数据
                            client.onHandleResponse();
                        } catch (Exception e) {
                            e.printStackTrace();
                            Log.d("====", "断开连接" + e.getMessage());
                            disconnectError();
                        }
                    }
                }
            }
        });
    }
    public void startHeartbeat() {
        if (heartbeatThread != null) {
            heartbeatThread.shutdownNow();
            heartbeatThread = null;
        }
        if (client.getOptions() == null || client.getOptions().getHeartbeatTimeInterval() <= 0 || !client.getOptions().isEnabledHeartbeat()) {
            return;
        }
        getHeartBeat().scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                if (isRun.get()) {
                    Log.d("====", "===发送心跳包===");
                    if (client.getOptions() != null) {
                        final byte[] heartBeat = client.getOptions().getHeartbeatData();
                        if (heartBeat != null) {
                            sendMsg(heartBeat, false, null);
                        } else {
                            sendMsg(new byte[0], false, null);
                        }
                    }
                }
            }
        }, client.getOptions().getHeartbeatTimeInterval(), client.getOptions().getHeartbeatTimeInterval(), TimeUnit.MILLISECONDS);
    }
    public void stopHeartbeat() {
        if (heartbeatThread != null) {
            heartbeatThread.shutdownNow();
            heartbeatThread = null;
        }
    }
    public void sendMsg(byte[] msg) {
        sendMsg(msg, true, null);
    }
    public void sendMsg(byte[] msg, SendListener listener) {
        sendMsg(msg, true, listener);
    }
    /**
     * @param listener 一般情况无需监听
     */
    private void sendMsg(byte[] msg, boolean isRefreshRetry, SendListener listener) {
        if (isRefreshRetry) {
            //重置连接次数
            resendCount.set(0);
        }
        try {
            SocketRequest request = new SocketRequest(msg);
            if (listener != null && !TextUtils.isEmpty(request.getAction())) {
                sendMap.put(request.getAction(), listener);
            }
            mMessageQueue.put(request);
        } catch (InterruptedException ignored) {
        }
        if (!client.isConnect()) {
            resetConnect(false);
        }
    }
    /**
     * 发生错误,重连
     */
    private void disconnectError() {
        disconnect();
        isRun.set(false);
        if (isOpenRetry.get()) {
            if (delayThread != null) {
                delayThread.shutdownNow();
            }
            delayThread = ThreadToolUtils.getInstance().newScheduledThreadPool(1);
            delayThread.schedule(new Runnable() {
                @Override
                public void run() {
                    if (!client.isConnect() && isOpenRetry.get()) {
                        resetConnect(false);
                    }
                }
            }, 3000, TimeUnit.MILLISECONDS);
        }
    }
    private synchronized void disconnect() {
        if (client.isConnect()) {
            client.disconnect();
            //断开连接
            client.onConnectStatus(ConnectStatus.DISCONNECT);
        }
    }
    public synchronized void close() {
        isOpenRetry.set(false);
        isRun.set(false);
        if (connectThread != null) {
            connectThread.shutdownNow();
            connectThread = null;
        }
        if (heartbeatThread != null) {
            heartbeatThread.shutdownNow();
            heartbeatThread = null;
        }
        if (sendThread != null) {
            sendThread.shutdownNow();
            sendThread = null;
        }
        if (receiveThread != null) {
            receiveThread.shutdownNow();
            receiveThread = null;
        }
        sendMap.clear();
        client.disconnect();
        mMessageQueue.clear();
    }
    public synchronized void release() {
        close();
        if (client != null && client.getOptions() != null) {
            client.getOptions().clearConnectStatusListener();
        }
    }
    public boolean isConnect() {
        return client.isConnect();
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketOptions.java
New file
@@ -0,0 +1,130 @@
package com.hdl.sdk.socket;
import com.hdl.sdk.socket.codec.IHandleMessage;
import com.hdl.sdk.socket.listener.ConnectStatusListener;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by Tong on 2021/9/22.
 * socket配置
 */
public class SocketOptions {
    //设置读取缓存
    private int readMaxBufferSize = 512;
    //发送心跳包
    private boolean isEnabledHeartbeat = true;
    //心跳包
    private byte[] heartbeatData;
    //心跳包时间间隔
    private long heartbeatTimeInterval = 300L;
    //处理数据
    private IHandleMessage handleMessage;
    //监听状态
    private List<ConnectStatusListener> mConnectStatusListener;
    //最大重连次数,小于0无限次数,等于0不重连
    private int maxRetry = -1;
    private boolean isTcpNoDelay;
    private boolean isReuseAddress;
    //保持活动状态
    private boolean isKeepAlive;
    private boolean isOOBInline;
    private int sendBufferSize;
    private int receiveBufferSize;
    private int soTimeout;
    private boolean soLinger;
    public IHandleMessage getHandleMessage() {
        return handleMessage;
    }
    public void setHandleMessage(IHandleMessage handleMessage) {
        this.handleMessage = handleMessage;
    }
    public boolean isEnabledHeartbeat() {
        return isEnabledHeartbeat;
    }
    public void setEnabledHeartbeat(boolean enabledHeartbeat) {
        isEnabledHeartbeat = enabledHeartbeat;
    }
    public byte[] getHeartbeatData() {
        return heartbeatData;
    }
    public void setHeartbeatData(byte[] heartbeatData) {
        this.heartbeatData = heartbeatData;
    }
    public void setHeartbeatData(String heartbeatData) {
        try {
            this.heartbeatData = heartbeatData.getBytes("utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    public long getHeartbeatTimeInterval() {
        return heartbeatTimeInterval;
    }
    public void setHeartbeatTimeInterval(long heartbeatTimeInterval) {
        this.heartbeatTimeInterval = heartbeatTimeInterval;
    }
    public void clearConnectStatusListener() {
        if (mConnectStatusListener != null && !mConnectStatusListener.isEmpty()) {
            mConnectStatusListener.clear();
        }
    }
    public void addConnectStatusListener(ConnectStatusListener connectStatusListener) {
        if (mConnectStatusListener == null) {
            mConnectStatusListener = new ArrayList<>();
        }
        mConnectStatusListener.add(connectStatusListener);
    }
    public void removeConnectStatusListener(ConnectStatusListener connectStatusListener) {
        if (mConnectStatusListener != null) {
            mConnectStatusListener.remove(connectStatusListener);
        }
    }
    public List<ConnectStatusListener> getConnectStatusListener() {
        return mConnectStatusListener;
    }
    public int getMaxRetry() {
        return maxRetry;
    }
    public void setMaxRetry(int maxRetry) {
        this.maxRetry = maxRetry;
    }
    public int getReadMaxBufferSize() {
        return readMaxBufferSize;
    }
    public void setReadMaxBufferSize(int readMaxBufferSize) {
        this.readMaxBufferSize = readMaxBufferSize;
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/SocketRequest.java
New file
@@ -0,0 +1,34 @@
package com.hdl.sdk.socket;
import java.util.UUID;
/**
 * Created by Tong on 2021/9/22.
 */
public class SocketRequest {
    private String action;
    private byte[] data;
    public SocketRequest(byte[] data) {
        this.data = data;
        action = UUID.randomUUID().toString();
    }
    public String getAction() {
        return action;
    }
    public void setAction(String action) {
        this.action = action;
    }
    public byte[] getData() {
        return data;
    }
    public void setData(byte[] data) {
        this.data = data;
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/annotation/ConnectStatus.java
New file
@@ -0,0 +1,31 @@
package com.hdl.sdk.socket.annotation;
import androidx.annotation.IntDef;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
 * Created by Tong on 2021/9/22.
 */
@Target({ElementType.TYPE_PARAMETER, ElementType.PARAMETER})
@IntDef({ConnectStatus.CONNECTING,
        ConnectStatus.CONNECTED,
        ConnectStatus.DISCONNECT})
public @interface ConnectStatus {
    /**
     * 连接中
     */
    int CONNECTING = 0;
    /**
     * 连接成功
     */
    int CONNECTED = 1;
    /**
     * 连接关闭
     */
    int DISCONNECT = 2;
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/ClientPool.java
New file
@@ -0,0 +1,57 @@
package com.hdl.sdk.socket.client;
import android.net.Uri;
import androidx.collection.ArrayMap;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
/**
 * Created by Tong on 2021/10/8.
 */
public class ClientPool {
    private final ArrayMap<String, Socket> mTcpPool = new ArrayMap<>();
    private final ArrayMap<String, DatagramSocket> mUdpPool = new ArrayMap<>();
    private ClientPool() {
    }
    private static class SingletonInstance {
        private static final ClientPool INSTANCE = new ClientPool();
    }
    public static ClientPool getInstance() {
        return SingletonInstance.INSTANCE;
    }
    public Socket getTcpSocket(String ip, int port) {
        final StringBuilder key = new StringBuilder();
        key.append(ip).append(":").append(port);
        if (mTcpPool.containsKey(key)) {
            Socket socket = mTcpPool.get(key);
            if (socket != null && !socket.isClosed()) {
                return socket;
            }
        }
        return new Socket();
    }
    public DatagramSocket getUdpSocket(String ip, int port) throws SocketException {
        final StringBuilder key = new StringBuilder();
        key.append(ip).append(":").append(port);
        if (mUdpPool.containsKey(key)) {
            DatagramSocket socket = mUdpPool.get(key);
            if (socket != null && !socket.isClosed()) {
                return socket;
            }
        }
        return new DatagramSocket(port);
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/IClient.java
New file
@@ -0,0 +1,37 @@
package com.hdl.sdk.socket.client;
import com.hdl.sdk.socket.SocketOptions;
/**
 * Created by Tong on 2021/9/22.
 */
public interface IClient {
    void connect() throws Exception;
    void disconnect();
    /**
     * 是否已经连接
     */
    boolean isConnect();
    SocketOptions getOptions();
    /**
     * 监听数据
     */
    void onHandleResponse() throws Exception;
    /**
     * 发送消息
     */
    void sendMsg(byte[] msg) throws Exception;
    /**
     * 连接状态
     */
    void onConnectStatus(int status);
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/TcpClient.java
New file
@@ -0,0 +1,176 @@
package com.hdl.sdk.socket.client;
import com.hdl.sdk.common.utils.ThreadToolUtils;
import com.hdl.sdk.socket.SocketBoot;
import com.hdl.sdk.socket.SocketOptions;
import com.hdl.sdk.socket.annotation.ConnectStatus;
import com.hdl.sdk.socket.codec.IHandleMessage;
import com.hdl.sdk.socket.listener.ConnectStatusListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
/**
 * Created by Tong on 2021/9/15.
 */
public final class TcpClient implements IClient {
    private SocketOptions socketOptions;
    private final String ip;
    private final int port;
    private Socket mSocket;
    private byte[] readBuffer;
    private TcpClient(String ip, int port, SocketOptions socketOptions) {
        this.socketOptions = socketOptions;
        this.ip = ip;
        this.port = port;
    }
    public static SocketBoot init(String ip, int port, SocketOptions options) {
        return new SocketBoot(new TcpClient(ip, port, options));
    }
    @Override
    public void connect() throws Exception {
        mSocket = getSocket();
        SocketOptions options = getOptions();
        mSocket.connect(new InetSocketAddress(ip, port));
        mSocket.setTcpNoDelay(true);
        mSocket.setReuseAddress(true);
        mSocket.setKeepAlive(true);
        readBuffer = new byte[options.getReadMaxBufferSize()];
    }
    @Override
    public void disconnect() {
        if (mSocket != null) {
            try {
                mSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public boolean isConnect() {
        if (mSocket == null) {
            return false;
        }
        return mSocket.isConnected() && !mSocket.isClosed();
    }
    @Override
    public synchronized SocketOptions getOptions() {
        if (socketOptions == null) {
            socketOptions = new SocketOptions();
        }
        return socketOptions;
    }
    @Override
    public void onHandleResponse() throws Exception {
        final InputStream stream = getInputStream();
        if (stream != null && getOptions() != null) {
            readBuffer = new byte[1024];
            while ((getInputStream().read(readBuffer)) != -1) {
                IHandleMessage handleMessage = getOptions().getHandleMessage();
                if (handleMessage != null) {
                    handleMessage.read(readBuffer);
                }
            }
        }
    }
    @Override
    public void sendMsg(byte[] msg) throws Exception {
        final OutputStream outputStream = getOutStream();
        if (outputStream != null && getOptions() != null) {
            try {
                IHandleMessage handleMessage = getOptions().getHandleMessage();
                handleMessage.write(handleMessage.write(msg));
                getOutStream().write(msg);
            } finally {
                outputStream.flush();
            }
        }
    }
    /**
     * 处理连接状态
     */
    public void onConnectStatus(int status) {
        ThreadToolUtils.getInstance().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                final List<ConnectStatusListener> list = getOptions().getConnectStatusListener();
                if (list != null && !list.isEmpty()) {
                    for (ConnectStatusListener listener : list) {
                        switch (status) {
                            case ConnectStatus
                                    .CONNECTING:
                                listener.onConnecting();
                                break;
                            case ConnectStatus
                                    .CONNECTED:
                                listener.onConnected();
                                break;
                            case ConnectStatus
                                    .DISCONNECT:
                                listener.onConnectFailed();
                                break;
                        }
                    }
                }
            }
        });
    }
    private synchronized Socket getSocket() {
        return new Socket();
    }
    private InputStream getInputStream() {
        if (mSocket != null && mSocket.isConnected() && !mSocket.isClosed()) {
            try {
                return mSocket.getInputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    private OutputStream getOutStream() {
        if (mSocket != null && mSocket.isConnected() && !mSocket.isClosed()) {
            try {
                return mSocket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/client/UdpClient.java
New file
@@ -0,0 +1,206 @@
package com.hdl.sdk.socket.client;
import android.util.Log;
import com.hdl.sdk.common.HDLSdk;
import com.hdl.sdk.common.utils.IpUtils;
import com.hdl.sdk.common.utils.ThreadToolUtils;
import com.hdl.sdk.socket.SocketBoot;
import com.hdl.sdk.socket.SocketOptions;
import com.hdl.sdk.socket.annotation.ConnectStatus;
import com.hdl.sdk.socket.codec.IHandleMessage;
import com.hdl.sdk.socket.listener.ConnectStatusListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * Created by Tong on 2021/9/15.
 * 组播需要android.permission.CHANGE_WIFI_MULTICAST_STATE权限
 * MulticastSocket
 */
public class UdpClient implements IClient {
    private static DatagramSocket mSocket;
    private DatagramPacket receivePacket;
    private final int BUFFER = 4 * 1024;
    private final byte[] receiveByte;
    private final String ip;
    private final int port;
    private int monitorPort;
    private int sendPort;
    private SocketOptions socketOptions;
    private final AtomicBoolean isConnect = new AtomicBoolean(false);
    /**
     * @param sendPort -1 表示随机端口
     */
    private UdpClient(String ip, int port, int monitorPort, int sendPort, SocketOptions socketOptions) {
        this.socketOptions = socketOptions;
        this.ip = ip;
        this.port = port;
        this.sendPort = sendPort;
        this.monitorPort = monitorPort;
        this.receiveByte = new byte[BUFFER];
    }
    public UdpClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
        this.receiveByte = new byte[BUFFER];
    }
    public static SocketBoot init(String ip, int port, int monitorPort, int sendPort, SocketOptions options) {
        return new SocketBoot(new UdpClient(ip, port, monitorPort, sendPort, options));
    }
    public static SocketBoot init(String ip, int port, int monitorPort, SocketOptions options) {
        return init(ip, port, monitorPort, -1, options);
    }
    public static SocketBoot init(String ip, int port, SocketOptions options) {
        return init(ip, port, port, -1, options);
    }
    @Override
    public void connect() throws Exception {
        try {
            mSocket = ClientPool.getInstance().getUdpSocket(ip, monitorPort);
            mSocket.setBroadcast(true);
            mSocket.setReuseAddress(true);
            isConnect.set(true);
            if (receivePacket == null) {
                receivePacket = new DatagramPacket(receiveByte, BUFFER);
            }
        } catch (Exception e) {
            isConnect.set(false);
            throw e;
        }
    }
    @Override
    public void disconnect() {
        if (mSocket != null) {
            mSocket.close();
        }
        isConnect.set(false);
    }
    @Override
    public boolean isConnect() {
        return isConnect.get();
    }
    @Override
    public synchronized SocketOptions getOptions() {
        if (socketOptions == null) {
            socketOptions = new SocketOptions();
        }
        return socketOptions;
    }
    @Override
    public void onHandleResponse() throws Exception {
        if (receivePacket == null || mSocket == null) {
            return;
        }
        try {
            mSocket.receive(receivePacket);
        } catch (IOException e) {
            e.printStackTrace();
            isConnect.set(false);
        }
        if (receivePacket.getLength() == 0) {
            return;
        }
        //排除自己发出去的
        try {
            if (receivePacket.getAddress().getHostAddress()
                    .equals(IpUtils.getIP(HDLSdk.getInstance().getContext()))) {
                return;
            }
        } catch (Exception ignored) {
        }
        IHandleMessage handleMessage = getOptions().getHandleMessage();
        if (handleMessage != null) {
            handleMessage.read(receivePacket.getData());
        }
        final String receive = new String(receivePacket.getData(), 0, receivePacket.getLength());
        Log.d("---->", receive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
        //重置长度
        if (receivePacket != null) {
            receivePacket.setLength(BUFFER);
        }
    }
    @Override
    public void sendMsg(byte[] msg) throws Exception {
        if (msg == null) {
            msg = new byte[1];
        }
        InetAddress serverAddress = InetAddress.getByName(ip);
        final DatagramPacket sendPacket = new DatagramPacket(msg, msg.length, serverAddress, port);
        if (sendPort < 0) {
            final DatagramSocket sendSocket = new DatagramSocket();
            sendSocket.send(sendPacket);
            sendSocket.close();
        } else if (sendPort == monitorPort) {
            mSocket.send(sendPacket);
        } else {
            final DatagramSocket sendSocket = new DatagramSocket(sendPort);
            sendSocket.send(sendPacket);
            sendSocket.close();
        }
    }
    @Override
    public void onConnectStatus(int status) {
        ThreadToolUtils.getInstance().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                final List<ConnectStatusListener> list = getOptions().getConnectStatusListener();
                if (list != null && !list.isEmpty()) {
                    for (ConnectStatusListener listener : list) {
                        switch (status) {
                            case ConnectStatus
                                    .CONNECTING:
                                listener.onConnecting();
                                break;
                            case ConnectStatus
                                    .CONNECTED:
                                listener.onConnected();
                                break;
                            case ConnectStatus
                                    .DISCONNECT:
                                listener.onConnectFailed();
                                break;
                        }
                    }
                }
            }
        });
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/ByteToMessageDecoder.java
New file
@@ -0,0 +1,20 @@
package com.hdl.sdk.socket.codec;
/**
 * Created by Tong on 2021/9/22.
 */
public abstract class ByteToMessageDecoder<T> implements IHandleFlow<T> {
    protected abstract T decoder(Object msg)
            throws Exception;
    @Override
    public final T read(Object data) throws Exception {
        return decoder(data);
    }
    @Override
    public final byte[] write(byte[] data) {
        return data;
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IHandleFlow.java
New file
@@ -0,0 +1,12 @@
package com.hdl.sdk.socket.codec;
/**
 * Created by Tong on 2021/9/23.
 */
public interface IHandleFlow<T> {
    T read(Object data) throws Exception;
    byte[] write(byte[] data) throws Exception;
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IHandleMessage.java
New file
@@ -0,0 +1,10 @@
package com.hdl.sdk.socket.codec;
/**
 * Created by Tong on 2021/9/27.
 */
public interface IHandleMessage {
    void read(byte[] data) throws Exception;
    byte[] write(byte[] data) throws Exception;
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/IMessagePipeLine.java
New file
@@ -0,0 +1,11 @@
package com.hdl.sdk.socket.codec;
/**
 * Created by Tong on 2021/9/23.
 */
public interface IMessagePipeLine {
    void add(IHandleFlow flow);
    void clear();
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/MessagePipeLine.java
New file
@@ -0,0 +1,51 @@
package com.hdl.sdk.socket.codec;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
 * Created by Tong on 2021/9/23.
 */
public class MessagePipeLine implements IMessagePipeLine, IHandleMessage {
    public final static List<IHandleFlow> queue = new ArrayList<>();
    @Override
    public void add(IHandleFlow flow) {
        queue.add(flow);
    }
    @Override
    public synchronized void clear() {
        queue.clear();
    }
    @Override
    public void read(byte[] data) throws Exception {
        Object out = data;
        for (int i = 0; i < queue.size(); i++) {
            IHandleFlow flow = queue.get(i);
            Object read = flow.read(out);
            try {
                out = Objects.requireNonNull(read);
            } catch (Exception ignored) {
            }
        }
    }
    @Override
    public byte[] write(byte[] data) throws Exception {
        byte[] out = data;
        for (int i = 0; i < queue.size(); i++) {
            IHandleFlow flow = queue.get(i);
            byte[] write = flow.write(out);
            try {
                out = Objects.requireNonNull(write);
            } catch (Exception ignored) {
            }
        }
        return new byte[0];
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/codec/MessageToByteEncoder.java
New file
@@ -0,0 +1,24 @@
package com.hdl.sdk.socket.codec;
/**
 * Created by Tong on 2021/9/22.
 */
public abstract class MessageToByteEncoder implements IHandleFlow {
    protected abstract byte[] encode(byte[] data)
            throws Exception;
    @Override
    public final Object read(Object data) {
        return data;
    }
    @Override
    public byte[] write(byte[] data) throws Exception {
        return encode(data);
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/listener/ConnectStatusListener.java
New file
@@ -0,0 +1,27 @@
package com.hdl.sdk.socket.listener;
/**
 * Created by Tong on 2021/9/22.
 * 连接状态
 */
public interface ConnectStatusListener {
    /**
     * 连接中
     */
    default void onConnecting() {
    }
    /**
     * 连接成功
     */
    default void onConnected() {
    }
    /**
     * 连接失败
     */
    default void onConnectFailed() {
    }
}
HDLSDK/hdl-socket/src/main/java/com/hdl/sdk/socket/listener/SendListener.java
New file
@@ -0,0 +1,11 @@
package com.hdl.sdk.socket.listener;
/**
 * Created by Tong on 2021/9/22.
 */
public interface SendListener {
    void onSucceed();
    void onError();
}
HDLSDK/hdl-socket/src/test/java/com/hdl/sdk/socket/ExampleUnitTest.java
New file
@@ -0,0 +1,17 @@
package com.hdl.sdk.socket;
import org.junit.Test;
import static org.junit.Assert.*;
/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
HDLSDK/settings.gradle
New file
@@ -0,0 +1,6 @@
include ':app'
include ':hdl-socket'
include ':hdl-connect'
include ':hdl-common'