Browse Source

first commit

DESKTOP-6R169VE\hrj 1 year ago
commit
025793553a
97 changed files with 3804 additions and 0 deletions
  1. 14 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 1 0
      .idea/.name
  4. 123 0
      .idea/codeStyles/Project.xml
  5. 5 0
      .idea/codeStyles/codeStyleConfig.xml
  6. 6 0
      .idea/compiler.xml
  7. 21 0
      .idea/gradle.xml
  8. 30 0
      .idea/jarRepositories.xml
  9. 6 0
      .idea/kotlinc.xml
  10. 9 0
      .idea/misc.xml
  11. 36 0
      README.en.md
  12. 39 0
      README.md
  13. 1 0
      app/.gitignore
  14. 82 0
      app/build.gradle
  15. BIN
      app/libs/arm64-v8a/libBDSpeechDecoder_V1.so
  16. BIN
      app/libs/arm64-v8a/libbd_etts.so
  17. BIN
      app/libs/arm64-v8a/libgnustl_shared.so
  18. BIN
      app/libs/armeabi-v7a/libBDSpeechDecoder_V1.so
  19. BIN
      app/libs/armeabi-v7a/libbd_etts.so
  20. BIN
      app/libs/armeabi-v7a/libgnustl_shared.so
  21. BIN
      app/libs/armeabi/libBDSpeechDecoder_V1.so
  22. BIN
      app/libs/armeabi/libbd_etts.so
  23. BIN
      app/libs/armeabi/libgnustl_shared.so
  24. BIN
      app/libs/com.baidu.tts_2.5.1.103.20190806_a05bdd9.jar
  25. BIN
      app/libs/x86/libBDSpeechDecoder_V1.so
  26. BIN
      app/libs/x86/libbd_etts.so
  27. BIN
      app/libs/x86/libgnustl_shared.so
  28. BIN
      app/libs/x86_64/libBDSpeechDecoder_V1.so
  29. BIN
      app/libs/x86_64/libbd_etts.so
  30. BIN
      app/libs/x86_64/libgnustl_shared.so
  31. 21 0
      app/proguard-rules.pro
  32. 75 0
      app/src/main/AndroidManifest.xml
  33. 13 0
      app/src/main/assets/auth.properties
  34. BIN
      app/src/main/assets/bd_etts_common_speech_as_mand_eng_high_am-mgc_v3.6.0_20190117.dat
  35. BIN
      app/src/main/assets/bd_etts_common_speech_f7_mand_eng_high_am-mgc_v3.6.0_20190117.dat
  36. BIN
      app/src/main/assets/bd_etts_common_speech_m15_mand_eng_high_am-mgc_v3.6.0_20190117.dat
  37. BIN
      app/src/main/assets/bd_etts_common_speech_yyjw_mand_eng_high_am-mgc_v3.6.0_20190117.dat
  38. BIN
      app/src/main/assets/bd_etts_common_text_txt_all_mand_eng_middle_big_v3.4.2_20190221.dat
  39. 5 0
      app/src/main/assets/readme.txt
  40. 52 0
      app/src/main/java/com/makeit/callservice/App.java
  41. 29 0
      app/src/main/java/com/makeit/callservice/MyBroadcastReceiver.java
  42. 21 0
      app/src/main/java/com/makeit/callservice/bean/Person.kt
  43. 13 0
      app/src/main/java/com/makeit/callservice/tts/MainHandlerConstant.java
  44. 202 0
      app/src/main/java/com/makeit/callservice/tts/TtsHelper.java
  45. 91 0
      app/src/main/java/com/makeit/callservice/tts/control/InitConfig.java
  46. 244 0
      app/src/main/java/com/makeit/callservice/tts/control/MySyntherizer.java
  47. 86 0
      app/src/main/java/com/makeit/callservice/tts/control/NonBlockSyntherizer.java
  48. 141 0
      app/src/main/java/com/makeit/callservice/tts/listener/FileSaveListener.java
  49. 107 0
      app/src/main/java/com/makeit/callservice/tts/listener/MessageListener.java
  50. 70 0
      app/src/main/java/com/makeit/callservice/tts/listener/UiMessageListener.java
  51. 102 0
      app/src/main/java/com/makeit/callservice/tts/util/Auth.java
  52. 467 0
      app/src/main/java/com/makeit/callservice/tts/util/AutoCheck.java
  53. 73 0
      app/src/main/java/com/makeit/callservice/tts/util/FileUtil.java
  54. 86 0
      app/src/main/java/com/makeit/callservice/tts/util/OfflineResource.java
  55. 135 0
      app/src/main/java/com/makeit/callservice/ui/HomeActivity.kt
  56. 75 0
      app/src/main/java/com/makeit/callservice/ui/HomeAdapter.kt
  57. 202 0
      app/src/main/java/com/makeit/callservice/ui/socket/StompSocket.java
  58. 46 0
      app/src/main/java/com/makeit/callservice/util/MarkUtil.java
  59. BIN
      app/src/main/res/drawable/app_icon_your_company.png
  60. 9 0
      app/src/main/res/drawable/default_background.xml
  61. 10 0
      app/src/main/res/drawable/home_head_middle_blue_bg.xml
  62. 10 0
      app/src/main/res/drawable/home_head_time_blue_bg.xml
  63. 10 0
      app/src/main/res/drawable/home_left_bottom_orange_bg.xml
  64. 10 0
      app/src/main/res/drawable/home_right_name_white_bg.xml
  65. 10 0
      app/src/main/res/drawable/home_right_top_blue_bg.xml
  66. BIN
      app/src/main/res/drawable/movie.png
  67. 198 0
      app/src/main/res/layout-large/activity_home.xml
  68. 76 0
      app/src/main/res/layout-large/activity_home_adapter_item.xml
  69. 202 0
      app/src/main/res/layout/activity_home.xml
  70. 76 0
      app/src/main/res/layout/activity_home_adapter_item.xml
  71. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  72. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  73. BIN
      app/src/main/res/mipmap-xhdpi/call_home_clock.png
  74. BIN
      app/src/main/res/mipmap-xhdpi/call_home_hdlogo.png
  75. BIN
      app/src/main/res/mipmap-xhdpi/call_home_headbg.png
  76. BIN
      app/src/main/res/mipmap-xhdpi/call_home_headmid.png
  77. BIN
      app/src/main/res/mipmap-xhdpi/call_home_item.jpg
  78. BIN
      app/src/main/res/mipmap-xhdpi/call_home_itemacti.jpg
  79. BIN
      app/src/main/res/mipmap-xhdpi/call_home_itemtip.png
  80. BIN
      app/src/main/res/mipmap-xhdpi/call_home_leftbtm.jpg
  81. BIN
      app/src/main/res/mipmap-xhdpi/call_home_leftth.jpg
  82. BIN
      app/src/main/res/mipmap-xhdpi/call_home_rightbtm.jpg
  83. BIN
      app/src/main/res/mipmap-xhdpi/call_home_rightth.jpg
  84. BIN
      app/src/main/res/mipmap-xhdpi/call_home_tip.png
  85. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  86. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  87. 19 0
      app/src/main/res/values/colors.xml
  88. 25 0
      app/src/main/res/values/dimens.xml
  89. 30 0
      app/src/main/res/values/strings.xml
  90. 6 0
      app/src/main/res/values/styles.xml
  91. 29 0
      build.gradle
  92. 21 0
      gradle.properties
  93. BIN
      gradle/wrapper/gradle-wrapper.jar
  94. 6 0
      gradle/wrapper/gradle-wrapper.properties
  95. 234 0
      gradlew
  96. 89 0
      gradlew.bat
  97. 2 0
      settings.gradle

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+*.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

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 1 - 0
.idea/.name

@@ -0,0 +1 @@
+CallService

+ 123 - 0
.idea/codeStyles/Project.xml

@@ -0,0 +1,123 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JetCodeStyleSettings>
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <codeStyleSettings language="XML">
+      <option name="FORCE_REARRANGE_MODE" value="1" />
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>

+ 5 - 0
.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="1.8" />
+  </component>
+</project>

+ 21 - 0
.idea/gradle.xml

@@ -0,0 +1,21 @@
+<?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="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="D:/develop/gradle/gradle-4.10-rc-3" />
+        <option name="gradleJvm" value="Android Studio java home" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 30 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,30 @@
+<?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="BintrayJCenter" />
+      <option name="name" value="BintrayJCenter" />
+      <option name="url" value="https://jcenter.bintray.com/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven" />
+      <option name="name" value="maven" />
+      <option name="url" value="https://jitpack.io" />
+    </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>

+ 6 - 0
.idea/kotlinc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="KotlinJpsPluginSettings">
+    <option name="version" value="1.6.21" />
+  </component>
+</project>

+ 9 - 0
.idea/misc.xml

@@ -0,0 +1,9 @@
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 36 - 0
README.en.md

@@ -0,0 +1,36 @@
+# 呼叫系统-中山医院
+
+#### Description
+{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
+
+#### Software Architecture
+Software architecture description
+
+#### Installation
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Instructions
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Contribution
+
+1.  Fork the repository
+2.  Create Feat_xxx branch
+3.  Commit your code
+4.  Create Pull Request
+
+
+#### Gitee Feature
+
+1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
+2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
+3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
+4.  The most valuable open source project [GVP](https://gitee.com/gvp)
+5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
+6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 39 - 0
README.md

@@ -0,0 +1,39 @@
+# 呼叫系统-中山医院 callservice
+
+#### 介绍
+{**以下是码云平台说明,您可以替换此简介**
+码云是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
+无论是个人、团队、或是企业,都能够用码云实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
+
+#### 软件架构
+软件架构说明
+
+
+#### 安装教程
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### 使用说明
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### 参与贡献
+
+1.  Fork 本仓库
+2.  新建 Feat_xxx 分支
+3.  提交代码
+4.  新建 Pull Request
+
+
+#### 码云特技
+
+1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
+2.  码云官方博客 [blog.gitee.com](https://blog.gitee.com)
+3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目
+4.  [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目
+5.  码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
+6.  码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 82 - 0
app/build.gradle

@@ -0,0 +1,82 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.makeit.callservice"
+        minSdkVersion 21
+        targetSdkVersion 28
+        versionCode 5
+        versionName "1.0.5"
+
+        ndk {
+            // 设置支持的SO库架构
+            abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'//'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
+        }
+        multiDexEnabled = true
+    }
+
+    android.applicationVariants.all { variant ->
+        variant.outputs.all {
+                def releaseTime = new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
+                outputFileName = "医院呼叫机V" + defaultConfig.versionName + "_" + releaseTime + ".apk"
+//                output.outputFile = new File(file.parent, filename)
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+
+        debug {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+    buildToolsVersion '28.0.3'
+
+
+}
+
+dependencies {
+    implementation fileTree(exclude: '*.zip', dir: 'libs')
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation 'androidx.leanback:leanback:1.0.0'
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    implementation 'com.github.bumptech.glide:glide:3.8.0'
+    implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
+    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
+    implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
+
+    implementation 'com.squareup.okhttp3:okhttp:3.14.4'
+
+    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
+    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
+    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
+
+    implementation 'com.github.NaikSoftware:StompProtocolAndroid:1.6.4'
+    implementation 'org.java-websocket:Java-WebSocket:1.3.0'
+
+    api "com.tencent.bugly:crashreport_upgrade:1.3.3"
+
+    api "org.greenrobot:eventbus:3.1.1"
+
+    api 'com.android.support:multidex:1.0.3'
+}

BIN
app/libs/arm64-v8a/libBDSpeechDecoder_V1.so


BIN
app/libs/arm64-v8a/libbd_etts.so


BIN
app/libs/arm64-v8a/libgnustl_shared.so


BIN
app/libs/armeabi-v7a/libBDSpeechDecoder_V1.so


BIN
app/libs/armeabi-v7a/libbd_etts.so


BIN
app/libs/armeabi-v7a/libgnustl_shared.so


BIN
app/libs/armeabi/libBDSpeechDecoder_V1.so


BIN
app/libs/armeabi/libbd_etts.so


BIN
app/libs/armeabi/libgnustl_shared.so


BIN
app/libs/com.baidu.tts_2.5.1.103.20190806_a05bdd9.jar


BIN
app/libs/x86/libBDSpeechDecoder_V1.so


BIN
app/libs/x86/libbd_etts.so


BIN
app/libs/x86/libgnustl_shared.so


BIN
app/libs/x86_64/libBDSpeechDecoder_V1.so


BIN
app/libs/x86_64/libbd_etts.so


BIN
app/libs/x86_64/libgnustl_shared.so


+ 21 - 0
app/proguard-rules.pro

@@ -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

+ 75 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,75 @@
+<?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.makeit.callservice">
+
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+    <uses-feature
+        android:name="android.software.leanback"
+        android:required="true" />
+
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+
+    <application
+        android:name=".App"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme"
+        android:usesCleartextTraffic="true"
+        >
+
+        <uses-library
+            android:name="org.apache.http.legacy"
+            android:required="false" />
+
+        <activity
+            android:name=".ui.HomeActivity"
+            android:banner="@drawable/app_icon_your_company"
+            android:icon="@drawable/app_icon_your_company"
+            android:label="@string/app_name"
+            android:launchMode="singleTask"
+            android:logo="@drawable/app_icon_your_company"
+            android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <!--注册接收系统开机广播消息的广播接收者-->
+        <receiver
+            android:name=".MyBroadcastReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter android:priority="1000">
+                <action android:name="android.intent.action.MEDIA_MOUNTED" />
+                <action android:name="android.intent.action.MEDIA_EJECT" />
+
+                <data android:scheme="file" />
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+</manifest>

+ 13 - 0
app/src/main/assets/auth.properties

@@ -0,0 +1,13 @@
+# 网页上应用的appId,申请纯离线SDK的必备信息
+appId=17941091
+#  网页上应用的appKey
+appKey=PALh7B99ZoeT9Mpv3ZE6YZLG
+# 网页上应用的secretKey
+secretKey=GqkoKCHR43kgb9GSOEuZVOcUkAcqoGA6
+# 测试或正式购买的SN,随邮件一起提供
+#sn=9c51436c-7949ec99-0195-00cf-21085
+sn=7b0e9091-623a1ebd-0195-000f-21084
+#samsung 测试机
+#sn=9bb1eed1-848fc8bc-0195-00ad-21082
+# 包名,这个值必须和app/build.gradle 里 defaultConfig.applicationId一致,即必须为context.getPackageName()
+applicationId=com.makeit.callservice

BIN
app/src/main/assets/bd_etts_common_speech_as_mand_eng_high_am-mgc_v3.6.0_20190117.dat


BIN
app/src/main/assets/bd_etts_common_speech_f7_mand_eng_high_am-mgc_v3.6.0_20190117.dat


BIN
app/src/main/assets/bd_etts_common_speech_m15_mand_eng_high_am-mgc_v3.6.0_20190117.dat


BIN
app/src/main/assets/bd_etts_common_speech_yyjw_mand_eng_high_am-mgc_v3.6.0_20190117.dat


BIN
app/src/main/assets/bd_etts_common_text_txt_all_mand_eng_middle_big_v3.4.2_20190221.dat


+ 5 - 0
app/src/main/assets/readme.txt

@@ -0,0 +1,5 @@
+m15 离线男声
+f7 离线女声
+yyjw 度逍遥
+as 度丫丫
+text_txt_all 通用资源(必须包含)

+ 52 - 0
app/src/main/java/com/makeit/callservice/App.java

@@ -0,0 +1,52 @@
+package com.makeit.callservice;
+
+import android.app.Application;
+import android.content.Context;
+
+import androidx.multidex.MultiDex;
+
+import com.makeit.callservice.ui.HomeActivity;
+import com.tencent.bugly.Bugly;
+import com.tencent.bugly.beta.Beta;
+
+/**
+ * Created by chenfeng on 2019/11/12.
+ * <p>
+ * 用途:
+ */
+public class App extends Application {
+
+    private static Context appContext;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        MultiDex.install(this);
+        appContext = this;
+        iniTinker();
+    }
+
+    private void iniTinker() {
+        Beta.enableHotfix = true;
+        // 设置是否自动下载补丁
+        Beta.canAutoDownloadPatch = true;
+        // 设置是否提示用户重启
+        Beta.canNotifyUserRestart = false;
+        // 设置是否自动合成补丁
+        Beta.canAutoPatch = true;
+
+        Beta.autoInit = true;
+        Beta.autoCheckUpgrade = true;
+        Beta.upgradeCheckPeriod = 3 * 24 * 60 * 60 * 1000;
+        Beta.canShowUpgradeActs.add(HomeActivity.class);
+
+        // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId,调试时将第三个参数设置为true
+        Bugly.init(this, "280bfc664a", BuildConfig.DEBUG);
+    }
+
+
+    public static Context getContext() {
+        return appContext;
+    }
+
+}

+ 29 - 0
app/src/main/java/com/makeit/callservice/MyBroadcastReceiver.java

@@ -0,0 +1,29 @@
+package com.makeit.callservice;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.makeit.callservice.ui.HomeActivity;
+
+/**
+ * Created by chenfeng on 2019/9/16.
+ * <p>
+ * 用途:
+ */
+public class MyBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String boot = "android.intent.action.BOOT_COMPLETED";
+        if (TextUtils.equals(boot, intent.getAction())) {
+            Intent intentMainActivity = new Intent(context, HomeActivity.class);
+            intentMainActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intentMainActivity);
+//            Toast.makeText(context, "重启成功", Toast.LENGTH_SHORT).show();
+        }
+    }
+}

+ 21 - 0
app/src/main/java/com/makeit/callservice/bean/Person.kt

@@ -0,0 +1,21 @@
+package com.makeit.callservice.bean
+
+/**
+ * Created by chenfeng on 2019/11/20.
+ *
+ *用途:
+ */
+data class Person(val list: List<User>) {
+//ws://192.168.31.132:80/gs-guide-websocket/websocket
+    data class User(
+        val id: String,
+        val name: String,
+        val scanNo: String,
+        val scanTotalNum: String,
+        val scanFinishNum: String,
+        val scanNoFinishNum: String,
+        val treatStatus: String,
+        var status: Int = 0
+    )
+}
+

+ 13 - 0
app/src/main/java/com/makeit/callservice/tts/MainHandlerConstant.java

@@ -0,0 +1,13 @@
+package com.makeit.callservice.tts;
+
+/**
+ * Created by fujiayi on 2017/9/13.
+ */
+
+public interface MainHandlerConstant {
+    static final int PRINT = 0;
+    static final int UI_CHANGE_INPUT_TEXT_SELECTION = 1;
+    static final int UI_CHANGE_SYNTHES_TEXT_SELECTION = 2;
+
+    static final int INIT_SUCCESS = 2;
+}

+ 202 - 0
app/src/main/java/com/makeit/callservice/tts/TtsHelper.java

@@ -0,0 +1,202 @@
+package com.makeit.callservice.tts;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
+import com.baidu.tts.client.SpeechSynthesizer;
+import com.baidu.tts.client.SpeechSynthesizerListener;
+import com.baidu.tts.client.TtsMode;
+import com.makeit.callservice.App;
+import com.makeit.callservice.BuildConfig;
+import com.makeit.callservice.tts.control.InitConfig;
+import com.makeit.callservice.tts.control.MySyntherizer;
+import com.makeit.callservice.tts.control.NonBlockSyntherizer;
+import com.makeit.callservice.tts.listener.UiMessageListener;
+import com.makeit.callservice.tts.util.Auth;
+import com.makeit.callservice.tts.util.AutoCheck;
+import com.makeit.callservice.tts.util.OfflineResource;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author chenfeng
+ * @date 2019/11/12
+ * <p>
+ * 用途:
+ */
+public class TtsHelper {
+
+    private static final String TAG = "TtsHelper";
+
+    // ================== 初始化参数设置开始 ==========================
+    /**
+     * 本demo的包名是com.baidu.tts.sample.offline,定义在app/build.gradle中。
+     * 以下4个鉴权信息在assets/auth,properties中设置,在onCreate方法里读取
+     */
+    protected String appId;
+
+    protected String appKey;
+
+    protected String secretKey;
+
+    protected String sn;
+
+    // TtsMode.OFFLINE 纯离线;TtsMode.MIX; 离在线融合,在线优先;TtsMode.ONLINE 纯在线
+    protected TtsMode ttsMode = TtsMode.OFFLINE;
+
+    // 离线发音选择,VOICE_FEMALE即为离线女声发音。
+    // assets目录下bd_etts_common_speech_m15_mand_eng_high_am-mgc_v3.6.0_20190117.dat为离线男声模型;
+    // assets目录下bd_etts_common_speech_f7_mand_eng_high_am-mix_v3.6.0_20190117.dat为离线女声模型
+    protected String offlineVoice = OfflineResource.VOICE_FEMALE;
+
+    // ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================
+
+    // 主控制类,所有合成控制方法从这个类开始
+    protected MySyntherizer synthesizer;
+
+    protected String desc = "请先看完说明。之后点击“合成并播放”按钮即可正常测试。\n"
+            + "测试离线合成功能需要首次联网。\n"
+            + "使用本Demo AppId AppKey SecretKey 包名 序列号SN 5个信息必须完全正确后,SDK会自动下载鉴权文件。否则会有-102错误。 .\n"
+            + "其中 AppId AppKey SecretKey 包名 序列号SN 包名这5个信息 需要填写在assets/auth.propertes中。\n\n";
+
+
+    private Handler mainHandler;
+
+    public TtsHelper(Handler handler) {
+        this.mainHandler = handler;
+        appId = Auth.getInstance(App.getContext()).getAppId();
+        appKey = Auth.getInstance(App.getContext()).getAppKey();
+        secretKey = Auth.getInstance(App.getContext()).getSecretKey();
+        sn = Auth.getInstance(App.getContext()).getSn();
+    }
+
+
+    /**
+     * 初始化引擎,需要的参数均在InitConfig类里
+     * <p>
+     * DEMO中提供了3个SpeechSynthesizerListener的实现
+     * MessageListener 仅仅用log.i记录日志,在logcat中可以看见
+     * UiMessageListener 在MessageListener的基础上,对handler发送消息,实现UI的文字更新
+     * FileSaveListener 在UiMessageListener的基础上,使用 onSynthesizeDataArrived回调,获取音频流
+     */
+    public void initialTts(Context context) {
+        // 日志打印在logcat中
+        LoggerProxy.printable(BuildConfig.DEBUG);
+        Log.i(TAG, "initialTts: SynthesizerTool.getEngineVersion();");
+        // 设置初始化参数
+        // 此处可以改为 含有您业务逻辑的SpeechSynthesizerListener的实现类
+        SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);
+
+        Map<String, String> params = getParams();
+
+        // sn必须在初始化填写,auth后调用无效
+        params.put(SpeechSynthesizer.PARAM_AUTH_SN, sn);
+
+        // appId appKey secretKey 网站上您申请的应用获取。注意使用离线合成功能的话,需要应用中填写您app的包名。包名在build.gradle中获取。
+        InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
+
+        initSynthesizer(context, initConfig);
+
+    }
+
+
+    private void initSynthesizer(Context context, InitConfig initConfig) {
+
+        // 如果您集成中出错,请将下面一段代码放在和demo中相同的位置,并复制InitConfig 和 AutoCheck到您的项目中
+        // 上线时请删除AutoCheck的调用
+        if (BuildConfig.DEBUG) {
+
+            AutoCheck.getInstance(App.getContext()).check(initConfig, new Handler() {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (msg.what == 100) {
+                        AutoCheck autoCheck = (AutoCheck) msg.obj;
+                        synchronized (autoCheck) {
+                            String message = autoCheck.obtainDebugMessage();
+//                        toPrint(message); // 可以用下面一行替代,在logcat中查看代码
+                            Log.w("AutoCheckMessage", message);
+                        }
+                    }
+                }
+
+            });
+        }
+
+        // 此处可以改为MySyntherizer 了解调用过程
+        synthesizer = new NonBlockSyntherizer(context, initConfig, mainHandler);
+    }
+
+    /**
+     * 合成的参数,可以初始化时填写,也可以在合成前设置。
+     *
+     * @return
+     */
+    protected Map<String, String> getParams() {
+        Map<String, String> params = new HashMap<String, String>();
+
+
+        // 以下参数均为选填
+        // 设置在线发声音人: 0 普通女声(默认) 1 普通男声 2 特别男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
+        params.put(SpeechSynthesizer.PARAM_SPEAKER, "0");
+        // 设置合成的音量,0-15 ,默认 5
+        params.put(SpeechSynthesizer.PARAM_VOLUME, "15");
+        // 设置合成的语速,0-15 ,默认 5
+        params.put(SpeechSynthesizer.PARAM_SPEED, "5");
+        // 设置合成的语调,0-15 ,默认 5
+        params.put(SpeechSynthesizer.PARAM_PITCH, "5");
+
+        params.put(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
+        // 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
+        // MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
+        // MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
+        // MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
+        // MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
+
+        // 离线资源文件, 从assets目录中复制到临时目录,需要在initTTs方法前完成
+        OfflineResource offlineResource = createOfflineResource(offlineVoice);
+        // 声学模型文件路径 (离线引擎使用), 请确认下面两个文件存在
+        params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, offlineResource.getTextFilename());
+        params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, offlineResource.getModelFilename());
+        return params;
+    }
+
+
+    private OfflineResource createOfflineResource(String voiceType) {
+        OfflineResource offlineResource = null;
+        try {
+            offlineResource = new OfflineResource(App.getContext(), voiceType);
+        } catch (IOException e) {
+            // IO 错误自行处理
+            e.printStackTrace();
+//            toPrint("【error】:copy files from assets failed." + e.getMessage());
+        }
+        return offlineResource;
+    }
+
+    /**
+     * speak 实际上是调用 synthesize后,获取音频流,然后播放。
+     * 获取音频流的方式见SaveFileActivity及FileSaveListener
+     * 需要合成的文本text的长度不能超过1024个GBK字节。
+     */
+    public void speak(String content) {
+
+        if (TextUtils.isEmpty(content)) {
+            return;
+        }
+
+        // 合成前可以修改参数:
+        Map<String, String> params = getParams();
+        synthesizer.setParams(params);
+        int result = synthesizer.speak(content);
+//        checkResult(result, "speak");
+    }
+
+
+}

+ 91 - 0
app/src/main/java/com/makeit/callservice/tts/control/InitConfig.java

@@ -0,0 +1,91 @@
+package com.makeit.callservice.tts.control;
+
+import com.baidu.tts.client.SpeechSynthesizerListener;
+import com.baidu.tts.client.TtsMode;
+
+import java.util.Map;
+
+/**
+ * 合成引擎的初始化参数
+ * <p>
+ * Created by fujiayi on 2017/9/13.
+ */
+
+public class InitConfig {
+    /**
+     * appId appKey 和 secretKey。注意如果需要离线合成功能,请在您申请的应用中填写包名。
+     * 本demo的包名是com.baidu.test,定义在build.gradle中。
+     */
+    private String appId;
+
+    private String appKey;
+
+    private String secretKey;
+
+    /**
+     * 纯离线、纯在线或者离在线融合
+     */
+    private TtsMode ttsMode;
+
+
+    /**
+     * 初始化的其它参数,用于setParam
+     */
+    private Map<String, String> params;
+
+    /**
+     * 合成引擎的回调
+     */
+    private SpeechSynthesizerListener listener;
+
+    private InitConfig() {
+
+    }
+
+    public InitConfig(String appId, String appKey, String secretKey, TtsMode ttsMode,
+                      Map<String, String> params, SpeechSynthesizerListener listener) {
+        this.appId = appId;
+        this.appKey = appKey;
+        this.secretKey = secretKey;
+        this.ttsMode = ttsMode;
+        this.params = params;
+        this.listener = listener;
+    }
+
+    public SpeechSynthesizerListener getListener() {
+        return listener;
+    }
+
+    public Map<String, String> getParams() {
+        return params;
+    }
+
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public TtsMode getTtsMode() {
+        return ttsMode;
+    }
+
+    @Override
+    public String toString() {
+        return "InitConfig{" +
+                "appId='" + appId + '\'' +
+                ", appKey='" + appKey + '\'' +
+                ", secretKey='" + secretKey + '\'' +
+                ", ttsMode=" + ttsMode +
+                ", params=" + params +
+                ", listener=" + listener +
+                '}';
+    }
+}

+ 244 - 0
app/src/main/java/com/makeit/callservice/tts/control/MySyntherizer.java

@@ -0,0 +1,244 @@
+package com.makeit.callservice.tts.control;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.Pair;
+
+import com.baidu.tts.auth.AuthInfo;
+import com.baidu.tts.client.SpeechSynthesizeBag;
+import com.baidu.tts.client.SpeechSynthesizer;
+import com.baidu.tts.client.TtsMode;
+import com.makeit.callservice.tts.MainHandlerConstant;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 该类是对SpeechSynthesizer的封装
+ * <p>
+ *
+ * @author fujiayi
+ * @date 2017/5/24
+ */
+
+public class MySyntherizer implements MainHandlerConstant {
+
+    protected SpeechSynthesizer mSpeechSynthesizer;
+    protected Context context;
+    protected Handler mainHandler;
+
+    private static final String TAG = "NonBlockSyntherizer";
+
+    protected static volatile boolean isInitied = false;
+
+    private boolean isCheckFile = true;
+
+    public MySyntherizer(Context context, InitConfig initConfig, Handler mainHandler) {
+        this(context, mainHandler);
+        init(initConfig);
+    }
+
+
+    protected MySyntherizer(Context context, Handler mainHandler) {
+        if (isInitied) {
+            // SpeechSynthesizer.getInstance() 不要连续调用
+//            throw new RuntimeException("MySynthesizer 类里面 SpeechSynthesizer还未释放,请勿新建一个新类");
+        }
+        this.context = context;
+        this.mainHandler = mainHandler;
+        isInitied = true;
+    }
+
+    /**
+     * 注意该方法需要在新线程中调用。且该线程不能结束。详细请参见NonBlockSyntherizer的实现
+     *
+     * @param config
+     * @return
+     */
+    protected boolean init(InitConfig config) {
+
+        sendToUiThread("初始化开始" + config.toString());
+        mSpeechSynthesizer = SpeechSynthesizer.getInstance();
+        mSpeechSynthesizer.setContext(context);
+        Log.i("MySyntherizer", "包名:" + context.getPackageName());
+        mSpeechSynthesizer.setSpeechSynthesizerListener(config.getListener());
+
+        // 请替换为语音开发者平台上注册应用得到的App ID ,AppKey ,Secret Key ,填写在SynthActivity的开始位置
+
+        mSpeechSynthesizer.setAppId(config.getAppId());
+        mSpeechSynthesizer.setApiKey(config.getAppKey(), config.getSecretKey());
+
+        Map<String, String> params = config.getParams();
+        if (params.containsKey(SpeechSynthesizer.PARAM_AUTH_SN)) {
+            mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_AUTH_SN, params.get(SpeechSynthesizer.PARAM_AUTH_SN));
+//            params.remove(SpeechSynthesizer.PARAM_AUTH_SN);
+        } else {
+            throw new RuntimeException("SN not set");
+        }
+
+//        boolean isOfflineEnabled = config.getTtsMode().equals(TtsMode.MIX)
+//                || config.getTtsMode().equals(TtsMode.OFFLINE);
+//        if (isOfflineEnabled) {
+//
+//            // 授权检测接口(只是通过AuthInfo进行检验授权是否成功。选择纯在线可以不必调用auth方法。
+            AuthInfo authInfo = mSpeechSynthesizer.auth(config.getTtsMode());
+            if (!authInfo.isSuccess()) {
+                // 离线授权需要网站上的应用填写包名。本demo的包名是com.baidu.test ,定义在build.gradle中
+                String errorMsg = authInfo.getTtsError().getDetailMessage();
+                sendToUiThread("鉴权失败 =" + errorMsg);
+                return false;
+            } else {
+                sendToUiThread("验证通过,离线正式授权文件存在。");
+            }
+//        }
+        setParams(params);
+        // 初始化tts
+        int result = mSpeechSynthesizer.initTts(config.getTtsMode());
+        if (result != 0) {
+            sendToUiThread("【error】initTts 初始化失败 + errorCode:" + result);
+            return false;
+        }
+        // 此时可以调用 speak和synthesize方法
+        sendToUiThread(INIT_SUCCESS, "合成引擎初始化成功");
+        return true;
+    }
+
+    /**
+     * 合成并播放
+     *
+     * @param text 小于1024 GBK字节,即512个汉字或者字母数字
+     * @return
+     */
+    public int speak(String text) {
+        if (!isInitied) {
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        Log.i(TAG, "speak text:" + text);
+        return mSpeechSynthesizer.speak(text);
+    }
+
+    /**
+     * 合成并播放
+     *
+     * @param text        小于1024 GBK字节,即512个汉字或者字母数字
+     * @param utteranceId 用于listener的回调,默认"0"
+     * @return
+     */
+    public int speak(String text, String utteranceId) {
+        if (!isInitied) {
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        return mSpeechSynthesizer.speak(text, utteranceId);
+    }
+
+    /**
+     * 只合成不播放
+     *
+     * @param text
+     * @return
+     */
+    public int synthesize(String text) {
+        if (!isInitied) {
+            // SpeechSynthesizer.getInstance() 不要连续调用
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        return mSpeechSynthesizer.synthesize(text);
+    }
+
+    public int synthesize(String text, String utteranceId) {
+        if (!isInitied) {
+            // SpeechSynthesizer.getInstance() 不要连续调用
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        return mSpeechSynthesizer.synthesize(text, utteranceId);
+    }
+
+    public int batchSpeak(List<Pair<String, String>> texts) {
+        if (!isInitied) {
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        List<SpeechSynthesizeBag> bags = new ArrayList<SpeechSynthesizeBag>();
+        for (Pair<String, String> pair : texts) {
+            SpeechSynthesizeBag speechSynthesizeBag = new SpeechSynthesizeBag();
+            speechSynthesizeBag.setText(pair.first);
+            if (pair.second != null) {
+                speechSynthesizeBag.setUtteranceId(pair.second);
+            }
+            bags.add(speechSynthesizeBag);
+
+        }
+        return mSpeechSynthesizer.batchSpeak(bags);
+    }
+
+    public void setParams(Map<String, String> params) {
+        if (params != null) {
+            for (Map.Entry<String, String> e : params.entrySet()) {
+                mSpeechSynthesizer.setParam(e.getKey(), e.getValue());
+            }
+        }
+    }
+
+    public int pause() {
+        return mSpeechSynthesizer.pause();
+    }
+
+    public int resume() {
+        return mSpeechSynthesizer.resume();
+    }
+
+    public int stop() {
+        return mSpeechSynthesizer.stop();
+    }
+
+    /**
+     * 引擎在合成时该方法不能调用!!!
+     * 注意 只有 TtsMode.MIX 才可以切换离线发音
+     *
+     * @return
+     */
+    public int loadModel(String modelFilename, String textFilename) {
+        int res = mSpeechSynthesizer.loadModel(modelFilename, textFilename);
+        sendToUiThread("切换离线发音人成功。");
+        return res;
+    }
+
+    /**
+     * 设置播放音量,默认已经是最大声音
+     * 0.0f为最小音量,1.0f为最大音量
+     *
+     * @param leftVolume  [0-1] 默认1.0f
+     * @param rightVolume [0-1] 默认1.0f
+     */
+    public void setStereoVolume(float leftVolume, float rightVolume) {
+        mSpeechSynthesizer.setStereoVolume(leftVolume, rightVolume);
+    }
+
+    public void release() {
+        if (!isInitied) {
+            throw new RuntimeException("TTS 还未初始化");
+        }
+        mSpeechSynthesizer.stop();
+        mSpeechSynthesizer.release();
+        mSpeechSynthesizer = null;
+        isInitied = false;
+    }
+
+
+    protected void sendToUiThread(String message) {
+        sendToUiThread(PRINT, message);
+    }
+
+    protected void sendToUiThread(int action, String message) {
+        Log.i(TAG, message);
+        if (mainHandler == null) { // 可以不依赖mainHandler
+            return;
+        }
+        Message msg = Message.obtain();
+        msg.what = action;
+        msg.obj = message + "\n";
+        mainHandler.sendMessage(msg);
+    }
+}

+ 86 - 0
app/src/main/java/com/makeit/callservice/tts/control/NonBlockSyntherizer.java

@@ -0,0 +1,86 @@
+package com.makeit.callservice.tts.control;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+/**
+ * 在新线程中调用initTTs方法。防止UI柱塞
+ * <p>
+ * Created by fujiayi on 2017/5/24.
+ */
+
+public class NonBlockSyntherizer extends MySyntherizer {
+
+    private static final int INIT = 1;
+
+    private static final int RELEASE = 11;
+    private HandlerThread hThread;
+    private Handler tHandler;
+
+
+    private static final String TAG = "NonBlockSyntherizer";
+
+
+    public NonBlockSyntherizer(Context context, InitConfig initConfig, Handler mainHandler) {
+        super(context, mainHandler);
+        initThread();
+        runInHandlerThread(INIT, initConfig);
+    }
+
+
+    protected void initThread() {
+        hThread = new HandlerThread("NonBlockSyntherizer-thread");
+        hThread.start();
+        tHandler = new Handler(hThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                super.handleMessage(msg);
+                switch (msg.what) {
+                    case INIT:
+                        InitConfig config = (InitConfig) msg.obj;
+                        boolean isSuccess = init(config);
+                        if (isSuccess) {
+                            // speak("初始化成功");
+                            sendToUiThread("NonBlockSyntherizer 初始化成功");
+                        } else {
+                            sendToUiThread("合成引擎初始化失败, 请查看日志");
+                        }
+                        break;
+                    case RELEASE:
+                        NonBlockSyntherizer.super.release();
+                        if (Build.VERSION.SDK_INT < 18) {
+                            getLooper().quit();
+                        }
+                        break;
+                    default:
+                        break;
+                }
+
+            }
+        };
+    }
+
+    @Override
+    public void release() {
+        runInHandlerThread(RELEASE);
+        if (Build.VERSION.SDK_INT >= 18) {
+            hThread.quitSafely();
+        }
+    }
+
+
+    private void runInHandlerThread(int action) {
+        runInHandlerThread(action, null);
+    }
+
+    private void runInHandlerThread(int action, Object obj) {
+        Message msg = Message.obtain();
+        msg.what = action;
+        msg.obj = obj;
+        tHandler.sendMessage(msg);
+    }
+
+}

+ 141 - 0
app/src/main/java/com/makeit/callservice/tts/listener/FileSaveListener.java

@@ -0,0 +1,141 @@
+package com.makeit.callservice.tts.listener;
+
+import android.os.Handler;
+import android.util.Log;
+
+import com.baidu.tts.client.SpeechError;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * 保存回调音频流到文件。您也可以直接处理音频流
+ * FileSaveListener 在UiMessageListener的基础上,使用 onSynthesizeDataArrived回调,获取音频流
+ * Created by fujiayi on 2017/9/15.
+ */
+
+public class FileSaveListener extends UiMessageListener {
+
+    /**
+     * 保存的文件名 baseName + utteranceId, 通常是 output-0.pcm
+     */
+    private String baseName = "output-";
+
+    /**
+     * 保存文件的目录
+     */
+    private String destDir;
+
+    /**
+     * 文件
+     */
+    private File ttsFile;
+
+
+    /**
+     * ttsFile 文件流
+     */
+    private FileOutputStream ttsFileOutputStream;
+
+    /**
+     * ttsFile 文件buffer流
+     */
+    private BufferedOutputStream ttsFileBufferedOutputStream;
+
+    private static final String TAG = "FileSaveListener";
+
+
+    public FileSaveListener(Handler mainHandler, String destDir) {
+        super(mainHandler);
+        this.destDir = destDir;
+    }
+
+    @Override
+    public void onSynthesizeStart(String utteranceId) {
+        String filename = baseName + utteranceId + ".pcm";
+        // 保存的语音文件是 16K采样率 16bits编码 单声道 pcm文件。
+        ttsFile = new File(destDir, filename);
+        Log.i(TAG, "try to write audio file to " + ttsFile.getAbsolutePath());
+        try {
+            if (ttsFile.exists()) {
+                ttsFile.delete();
+            }
+            ttsFile.createNewFile();
+            // 创建FileOutputStream对象
+            FileOutputStream ttsFileOutputStream = new FileOutputStream(ttsFile);
+            // 创建BufferedOutputStream对象
+            ttsFileBufferedOutputStream = new BufferedOutputStream(ttsFileOutputStream);
+        } catch (IOException e) {
+            // 请自行做错误处理
+            e.printStackTrace();
+            sendMessage("创建文件失败:" + destDir + "/" + filename);
+            throw new RuntimeException(e);
+        }
+        sendMessage("创建文件成功:" + destDir + "/" + filename);
+
+    }
+
+    /**
+     * 语音流 16K采样率 16bits编码 单声道 。
+     *
+     * @param utteranceId
+     * @param data        二进制语音 ,注意可能有空data的情况,可以忽略
+     * @param progress    如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。
+     * @param engineType  0 为在线引擎合成的声音,1 为离线
+     */
+    @Override
+    public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress, int engineType) {
+        super.onSynthesizeDataArrived(utteranceId, data, progress, engineType);
+        Log.i(TAG, "合成进度回调, progress:" + progress + ";序列号:" + utteranceId + ";发音引擎:"
+                + (engineType == 0 ? "在线" : "离线"));
+        try {
+            ttsFileBufferedOutputStream.write(data);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void onSynthesizeFinish(String utteranceId) {
+        super.onSynthesizeFinish(utteranceId);
+        close();
+    }
+
+    /**
+     * 当合成或者播放过程中出错时回调此接口
+     *
+     * @param utteranceId
+     * @param speechError 包含错误码和错误信息
+     */
+    @Override
+    public void onError(String utteranceId, SpeechError speechError) {
+        close();
+        super.onError(utteranceId, speechError);
+    }
+
+    /**
+     * 关闭流,注意可能stop导致该方法没有被调用
+     */
+    private void close() {
+        if (ttsFileBufferedOutputStream != null) {
+            try {
+                ttsFileBufferedOutputStream.flush();
+                ttsFileBufferedOutputStream.close();
+                ttsFileBufferedOutputStream = null;
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        }
+        if (ttsFileOutputStream != null) {
+            try {
+                ttsFileOutputStream.close();
+                ttsFileOutputStream = null;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        sendMessage("关闭文件成功");
+    }
+}

+ 107 - 0
app/src/main/java/com/makeit/callservice/tts/listener/MessageListener.java

@@ -0,0 +1,107 @@
+package com.makeit.callservice.tts.listener;
+
+import android.util.Log;
+
+import com.baidu.tts.client.SpeechError;
+import com.baidu.tts.client.SpeechSynthesizerListener;
+import com.makeit.callservice.tts.MainHandlerConstant;
+
+/**
+ * SpeechSynthesizerListener 简单地实现,仅仅记录日志
+ * Created by fujiayi on 2017/5/19.
+ */
+
+public class MessageListener implements SpeechSynthesizerListener, MainHandlerConstant {
+
+    private static final String TAG = "MessageListener";
+
+    /**
+     * 播放开始,每句播放开始都会回调
+     *
+     * @param utteranceId
+     */
+    @Override
+    public void onSynthesizeStart(String utteranceId) {
+        sendMessage("准备开始合成,序列号:" + utteranceId);
+    }
+
+    /**
+     * 语音流 16K采样率 16bits编码 单声道 。
+     *
+     * @param utteranceId
+     * @param bytes       二进制语音 ,注意可能有空data的情况,可以忽略
+     * @param progress    如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法和合成到第几个字对应。
+     * @param engineType  0 为在线引擎合成的声音,1 为离线
+     */
+    @Override
+    public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress, int engineType) {
+        // Log.i("MessageListener","粗略进度 : " + progress );
+    }
+
+
+    /**
+     * 合成正常结束,每句合成正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
+     *
+     * @param utteranceId
+     */
+    @Override
+    public void onSynthesizeFinish(String utteranceId) {
+        sendMessage("合成结束回调, 序列号:" + utteranceId);
+    }
+
+    @Override
+    public void onSpeechStart(String utteranceId) {
+        sendMessage("播放开始回调, 序列号:" + utteranceId);
+    }
+
+    /**
+     * 播放进度回调接口,分多次回调
+     *
+     * @param utteranceId
+     * @param progress    如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。
+     */
+    @Override
+    public void onSpeechProgressChanged(String utteranceId, int progress) {
+        //  Log.i(TAG, "播放进度回调, progress:" + progress + ";序列号:" + utteranceId );
+    }
+
+    /**
+     * 播放正常结束,每句播放正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口
+     *
+     * @param utteranceId
+     */
+    @Override
+    public void onSpeechFinish(String utteranceId) {
+        sendMessage("播放结束回调, 序列号:" + utteranceId);
+    }
+
+    /**
+     * 当合成或者播放过程中出错时回调此接口
+     *
+     * @param utteranceId
+     * @param speechError 包含错误码和错误信息
+     */
+    @Override
+    public void onError(String utteranceId, SpeechError speechError) {
+        sendErrorMessage("错误发生:" + speechError.description + ",错误编码:"
+                + speechError.code + ",序列号:" + utteranceId);
+    }
+
+    private void sendErrorMessage(String message) {
+        sendMessage(message, true);
+    }
+
+
+    private void sendMessage(String message) {
+        sendMessage(message, false);
+    }
+    
+    protected void sendMessage(String message, boolean isError) {
+        if (isError) {
+            Log.e(TAG, message);
+        } else {
+            Log.i(TAG, message);
+        }
+
+    }
+}

+ 70 - 0
app/src/main/java/com/makeit/callservice/tts/listener/UiMessageListener.java

@@ -0,0 +1,70 @@
+package com.makeit.callservice.tts.listener;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * 在 MessageListener的基础上,和UI配合。
+ * Created by fujiayi on 2017/9/14.
+ */
+
+public class UiMessageListener extends MessageListener {
+
+    private Handler mainHandler;
+
+    private static final String TAG = "UiMessageListener";
+
+    public UiMessageListener(Handler mainHandler) {
+        super();
+        this.mainHandler = mainHandler;
+    }
+
+    /**
+     * 合成数据和进度的回调接口,分多次回调。
+     * 注意:progress表示进度,与播放到哪个字无关
+     * @param utteranceId
+     * @param data 合成的音频数据。该音频数据是采样率为16K,2字节精度,单声道的pcm数据。
+     * @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3
+     * @param  engineType 目前均为1,请忽略
+     */
+    @Override
+    public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress, int engineType) {
+        super.onSynthesizeDataArrived(utteranceId, data, progress, engineType);
+        mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_SYNTHES_TEXT_SELECTION, progress, 0));
+    }
+
+    /**
+     * 播放进度回调接口,分多次回调
+     * 注意:progress表示进度,与播放到哪个字无关
+     *
+     * @param utteranceId
+     * @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3
+     */
+    @Override
+    public void onSpeechProgressChanged(String utteranceId, int progress) {
+        // sendMessage("onSpeechProgressChanged");
+        mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_INPUT_TEXT_SELECTION, progress, 0));
+    }
+
+    protected void sendMessage(String message) {
+        sendMessage(message, false);
+    }
+
+    @Override
+    protected void sendMessage(String message, boolean isError) {
+        sendMessage(message, isError, PRINT);
+    }
+
+
+    protected void sendMessage(String message, boolean isError, int action) {
+        super.sendMessage(message, isError);
+        if (mainHandler != null) {
+            Message msg = Message.obtain();
+            msg.what = action;
+            msg.obj = message + "\n";
+            mainHandler.sendMessage(msg);
+            Log.i(TAG, message);
+        }
+    }
+}

+ 102 - 0
app/src/main/java/com/makeit/callservice/tts/util/Auth.java

@@ -0,0 +1,102 @@
+package com.makeit.callservice.tts.util;
+
+import android.content.Context;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Auth {
+    private static volatile Auth ourInstance;
+
+    private String appId;
+
+    private String appKey;
+
+    private String secretKey;
+
+    private String sn;
+
+    private Auth(Context context) {
+        Properties prop = load(context);
+        String applicationId = getProperty(prop, "applicationId");
+
+        if (!context.getPackageName().equals(applicationId)) {
+            throw new AuthCheckException("包名不一致,请在app/build.gradle 里 修改defaultConfig.applicationId。"
+                    + "auth.properties里写的包名是:'" + applicationId
+                    + "' ; 实际app的包名是:'" + context.getPackageName() + "'");
+        }
+
+        appId = getProperty(prop, "appId");
+        appKey = getProperty(prop, "appKey");
+        secretKey = getProperty(prop, "secretKey");
+        sn = getProperty(prop, "sn");
+    }
+
+    public static Auth getInstance(Context context) {
+        if (ourInstance == null) {
+            synchronized (Auth.class) {
+                ourInstance = new Auth(context);
+            }
+        }
+        return ourInstance;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    private String getProperty(Properties properties, String key) {
+        String value = properties.getProperty(key);
+        if (value == null) {
+            throw new AuthCheckException("在 assets/auth.properties里没有设置 " + key);
+        }
+        return value.trim();
+    }
+
+    private Properties load(Context context) {
+        try {
+            InputStream is = context.getAssets().open("auth.properties");
+            Properties prop = new Properties();
+            prop.load(is);
+            is.close();
+            return prop;
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new AuthCheckException(e);
+        }
+    }
+
+    public static class AuthCheckException extends RuntimeException {
+        public AuthCheckException(String message) {
+            super(message);
+        }
+
+        public AuthCheckException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+
+    @Override
+    public String toString() {
+        return "Auth{" +
+                "appId='" + appId + '\'' +
+                ", appKey='" + appKey + '\'' +
+                ", secretKey='" + secretKey + '\'' +
+                ", sn='" + sn + '\'' +
+                '}';
+    }
+}

+ 467 - 0
app/src/main/java/com/makeit/callservice/tts/util/AutoCheck.java

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

+ 73 - 0
app/src/main/java/com/makeit/callservice/tts/util/FileUtil.java

@@ -0,0 +1,73 @@
+package com.makeit.callservice.tts.util;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.os.Environment;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by fujiayi on 2017/5/19.
+ */
+
+public class FileUtil {
+
+    // 创建一个临时目录,用于复制临时文件,如assets目录下的离线资源文件
+    public static String createTmpDir(Context context) {
+        String sampleDir = "baiduTTS";
+        String tmpDir = "/sdcard/baiduTTS";
+        if (!FileUtil.makeDir(tmpDir)) {
+            tmpDir = context.getExternalFilesDir(sampleDir).getAbsolutePath();
+            if (!FileUtil.makeDir(tmpDir)) {
+                throw new RuntimeException("create model resources dir failed :" + tmpDir);
+            }
+        }
+        return tmpDir;
+    }
+
+    public static boolean fileCanRead(String filename) {
+        File f = new File(filename);
+        return f.canRead();
+    }
+
+    public static boolean makeDir(String dirPath) {
+        File file = new File(dirPath);
+        if (!file.exists()) {
+            return file.mkdirs();
+        } else {
+            return true;
+        }
+    }
+
+    public static void copyFromAssets(AssetManager assets, String source, String dest, boolean isCover)
+            throws IOException {
+        File file = new File(dest);
+        if (isCover || (!isCover && !file.exists())) {
+            InputStream is = null;
+            FileOutputStream fos = null;
+            try {
+                is = assets.open(source);
+                String path = dest;
+                fos = new FileOutputStream(path);
+                byte[] buffer = new byte[1024];
+                int size = 0;
+                while ((size = is.read(buffer, 0, 1024)) >= 0) {
+                    fos.write(buffer, 0, size);
+                }
+            } finally {
+                if (fos != null) {
+                    try {
+                        fos.close();
+                    } finally {
+                        if (is != null) {
+                            is.close();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 86 - 0
app/src/main/java/com/makeit/callservice/tts/util/OfflineResource.java

@@ -0,0 +1,86 @@
+package com.makeit.callservice.tts.util;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import static android.content.ContentValues.TAG;
+
+
+/**
+ * Created by fujiayi on 2017/5/19.
+ */
+
+public class OfflineResource {
+
+    public static final String VOICE_FEMALE = "F";
+
+    public static final String VOICE_MALE = "M";
+
+
+    public static final String VOICE_DUYY = "Y";
+
+    public static final String VOICE_DUXY = "X";
+
+    private static final String SAMPLE_DIR = "baiduTTS";
+
+    private AssetManager assets;
+    private String destPath;
+
+    private String textFilename;
+    private String modelFilename;
+
+    private static HashMap<String, Boolean> mapInitied = new HashMap<String, Boolean>();
+
+    public OfflineResource(Context context, String voiceType) throws IOException {
+        context = context.getApplicationContext();
+        this.assets = context.getApplicationContext().getAssets();
+        this.destPath = FileUtil.createTmpDir(context);
+        setOfflineVoiceType(voiceType);
+    }
+
+    public String getModelFilename() {
+        return modelFilename;
+    }
+
+    public String getTextFilename() {
+        return textFilename;
+    }
+
+    public void setOfflineVoiceType(String voiceType) throws IOException {
+        String text = "bd_etts_common_text_txt_all_mand_eng_middle_big_v3.4.2_20190221.dat";
+        String model;
+        if (VOICE_MALE.equals(voiceType)) {
+            model = "bd_etts_common_speech_m15_mand_eng_high_am-mgc_v3.6.0_20190117.dat";
+        } else if (VOICE_FEMALE.equals(voiceType)) {
+            model = "bd_etts_common_speech_f7_mand_eng_high_am-mgc_v3.6.0_20190117.dat";
+        } else if (VOICE_DUXY.equals(voiceType)) {
+            model = "bd_etts_common_speech_yyjw_mand_eng_high_am-mgc_v3.6.0_20190117.dat";
+        } else if (VOICE_DUYY.equals(voiceType)) {
+            model = "bd_etts_common_speech_as_mand_eng_high_am-mgc_v3.6.0_20190117.dat";
+        } else {
+            throw new RuntimeException("voice type is not in list");
+        }
+        textFilename = copyAssetsFile(text);
+        modelFilename = copyAssetsFile(model);
+
+    }
+
+
+    private String copyAssetsFile(String sourceFilename) throws IOException {
+        String destFilename = destPath + "/" + sourceFilename;
+        boolean recover = false;
+        Boolean existed = mapInitied.get(sourceFilename); // 启动时完全覆盖一次
+        if (existed == null || !existed) {
+            recover = true;
+        }
+        FileUtil.copyFromAssets(assets, sourceFilename, destFilename, recover);
+        Log.i(TAG, "文件复制成功:" + destFilename);
+        return destFilename;
+    }
+
+
+}

+ 135 - 0
app/src/main/java/com/makeit/callservice/ui/HomeActivity.kt

@@ -0,0 +1,135 @@
+package com.makeit.callservice.ui
+
+import android.Manifest
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.text.TextUtils
+import android.view.View
+import androidx.fragment.app.FragmentActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.makeit.callservice.R
+import com.makeit.callservice.bean.Person
+import com.makeit.callservice.tts.MainHandlerConstant
+import com.makeit.callservice.tts.TtsHelper
+import com.makeit.callservice.ui.socket.StompSocket
+import com.makeit.callservice.util.MarkUtil
+import com.tbruyelle.rxpermissions2.RxPermissions
+import kotlinx.android.synthetic.main.activity_home.*
+
+
+/**
+ * Created by chenfeng on 2019/11/4.
+ *
+ *用途:
+ */
+class HomeActivity : FragmentActivity(), MainHandlerConstant {
+
+    private lateinit var adapter: HomeAdapter
+    private val data = mutableListOf<Person.User>()
+
+    private lateinit var socket: StompSocket
+
+    private val handler = Handler {
+        when (it.what) {
+            //列表
+            1001 -> {
+                activity_home_rl_tip.visibility = View.GONE
+                data.clear()
+                data.addAll(it.obj as List<Person.User>)
+                adapter.notifyDataSetChanged(data)
+            }
+
+            //呼叫
+            1002 -> {
+                val user = it.obj as Person.User
+
+                for (bean in data) {
+                    if (TextUtils.equals(user.id, bean.id)) {
+                        bean.status = -1
+                        break
+                    }
+                }
+
+                activity_home_rl_tip.visibility = View.VISIBLE
+
+                val name =
+                    if (user.name.length >= 3) {
+                        MarkUtil.idMask(user.name, 1, user.name.length - 2)
+                    } else {
+                        "${user.name.subSequence(0, 1)}*"
+                    }
+
+                adapter.notifyDataSetChanged(data)
+                activity_home_tv_tip.text = getString(R.string.home_tips, name)
+                speak(getString(R.string.home_tips, user.name))
+
+            }
+
+            // 进行中
+            1003 -> {
+                val user = it.obj as Person.User
+
+
+                if (TextUtils.isEmpty(user.name)) {
+                    activity_home_tv_name.text = ""
+                } else {
+
+                    val name =
+                        if (user.name.length >= 3) {
+                            MarkUtil.idMask(user.name, 1, user.name.length - 2)
+                        } else {
+                            "${user.name.subSequence(0, 1)}*"
+                        }
+
+                    activity_home_tv_name.text = name
+                }
+            }
+        }
+
+        true
+    }
+
+    private val ttsHelper = TtsHelper(handler)
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_home)
+        initView()
+
+        socket = StompSocket()
+        socket.connectStomp(handler)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+//        ttsHelper.release()
+    }
+
+    private fun speak(content: String) {
+        ttsHelper.speak(content)
+    }
+
+    private fun initView() {
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            val rxPermissions = RxPermissions(this)
+            rxPermissions.request(
+                Manifest.permission.MODIFY_AUDIO_SETTINGS,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                Manifest.permission.READ_PHONE_STATE
+            ).subscribe {
+                if (it) {
+                    ttsHelper.initialTts(this)
+                }
+            }
+        } else {
+            ttsHelper.initialTts(this)
+        }
+
+        adapter = HomeAdapter(this, data)
+        activity_home_rv.layoutManager = LinearLayoutManager(this)
+        activity_home_rv.adapter = adapter
+    }
+}

+ 75 - 0
app/src/main/java/com/makeit/callservice/ui/HomeAdapter.kt

@@ -0,0 +1,75 @@
+package com.makeit.callservice.ui
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.makeit.callservice.R
+import com.makeit.callservice.bean.Person
+import com.makeit.callservice.util.MarkUtil
+
+/**
+ * Created by chenfeng on 2019/11/4.
+ *
+ *用途:
+ */
+class HomeAdapter(private val context: Context, private var data: List<Person.User>) :
+    RecyclerView.Adapter<HomeAdapter.HomeViewHolder>() {
+
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewHolder {
+        val view =
+            LayoutInflater.from(context).inflate(R.layout.activity_home_adapter_item, parent, false)
+        return HomeViewHolder(view)
+    }
+
+
+    override fun getItemCount(): Int {
+        return data.size
+    }
+
+    override fun onBindViewHolder(holder: HomeViewHolder, position: Int) {
+
+        val item = data[position]
+        holder.noTv.text = (position + 1).toString()
+        if (item.name.length >= 3) {
+            holder.nameTv.text = MarkUtil.idMask(item.name, 1, item.name.length - 2)
+        } else {
+            holder.nameTv.text = "${item.name.subSequence(0, 1)}*"
+        }
+
+        holder.times.text = item.scanNoFinishNum
+        holder.total.text = item.scanTotalNum
+
+        if (item.status == -1) {
+            holder.iconIv.visibility = View.VISIBLE
+            holder.iconIv.setImageResource(R.mipmap.call_home_itemtip)
+            holder.itemView.background = context.getDrawable(R.mipmap.call_home_itemacti)
+        } else {
+            holder.iconIv.visibility = View.INVISIBLE
+            holder.itemView.background = context.getDrawable(R.mipmap.call_home_item)
+
+        }
+    }
+
+    fun notifyDataSetChanged(data: List<Person.User>) {
+        this.data = data
+        notifyDataSetChanged()
+
+    }
+
+
+    class HomeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+        val iconIv = itemView.findViewById<ImageView>(R.id.home_adapter_item_iv_icon)
+        val noTv = itemView.findViewById<TextView>(R.id.home_adapter_item_tv_no)
+        val nameTv = itemView.findViewById<TextView>(R.id.home_adapter_item_tv_name)
+        val times = itemView.findViewById<TextView>(R.id.home_adapter_item_tv_times)
+        val total = itemView.findViewById<TextView>(R.id.home_adapter_item_tv_total)
+
+    }
+}

+ 202 - 0
app/src/main/java/com/makeit/callservice/ui/socket/StompSocket.java

@@ -0,0 +1,202 @@
+package com.makeit.callservice.ui.socket;
+
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.gson.Gson;
+import com.makeit.callservice.BuildConfig;
+import com.makeit.callservice.bean.Person;
+
+import java.io.IOException;
+
+import io.reactivex.disposables.Disposable;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import ua.naiksoftware.stomp.Stomp;
+import ua.naiksoftware.stomp.StompClient;
+
+/**
+ * @author chenfeng
+ * @date 2019/11/19
+ * <p>
+ * 用途:
+ */
+public class StompSocket {
+
+//    private static final String ROOT_DEV = "47.112.221.31:8086";
+    private static final String ROOT_DEV = "192.168.2.11:8080";
+
+    private static final String ROOT_PROP = "192.0.33.104:8080";
+
+    private static final String ROOT = BuildConfig.DEBUG ? ROOT_DEV : ROOT_PROP;
+
+    private static final String URI = "ws://" + ROOT + "/gs-guide-websocket/websocket";
+    private static final String LIST_URL = "http://" + ROOT + "/api/websocket/call/list";
+//    private static final String URI = "ws://192.0.33.104:8080/gs-guide-websocket/websocket";
+//    private static final String URI = "ws://192.168.2.11:8989/gs-guide-websocket/websocket";
+
+    private StompClient mStompClient;
+
+    private final String TAG = "StompSocket";
+
+    private Handler handler;
+
+    private Gson gson = new Gson();
+
+    private boolean first = true;
+
+    private Runnable reconnectSocketRun = this::disconnect;
+
+    public void connectStomp(Handler handler) {
+
+        this.handler = handler;
+
+        createStompClient();
+        registerStompTopic();
+    }
+
+    private void disconnect() {
+        mStompClient.disconnect();
+    }
+
+    private void createStompClient() {
+        Log.e(TAG, "createStompClient init.....");
+        mStompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, URI);
+        mStompClient.withClientHeartbeat(3000).withServerHeartbeat(3000);
+
+        Disposable subscribe = mStompClient.lifecycle().subscribe(lifecycleEvent -> {
+            switch (lifecycleEvent.getType()) {
+                case OPENED:
+                    Log.e(TAG, "Stomp connection opened");
+                    handler.postDelayed(this::request, 300);
+
+//                        toast("连接已开启");
+                    break;
+
+                case ERROR:
+                    Log.e(TAG, "Stomp Error ", lifecycleEvent.getException());
+//                        toast("连接出错");
+//                    createStompClient();
+                    break;
+                case CLOSED:
+                    Log.e(TAG, "Stomp connection closed 重新连接");
+//                        toast("连接关闭");
+                    mStompClient.connect();
+                    registerStompTopic();
+                    break;
+                case FAILED_SERVER_HEARTBEAT:
+                    Log.e(TAG, "Stomp failed server heartbeat");
+//                    mStompClient.connect();
+//                    registerStompTopic();
+                    break;
+                default:
+                    break;
+            }
+        });
+
+        mStompClient.connect();
+
+    }
+
+
+    private void registerStompTopic() {
+        Log.e(TAG, "registerStompTopic init.....");
+
+        Disposable subscribe1 = mStompClient.topic("/topic/call/treatList").subscribe(stompMessage -> {
+                    Log.e(TAG, "列表: " + stompMessage.getPayload());
+
+
+                    Person person = gson.fromJson(stompMessage.getPayload(), Person.class);
+                    if (person != null) {
+                        Message msg = Message.obtain();
+                        msg.obj = person.getList();
+                        msg.what = 1001;
+                        handler.sendMessage(msg);
+                    }
+                }
+                , throwable -> {
+                    Log.e(TAG, "列表错误: " + throwable.getMessage());
+
+                });
+
+        Disposable subscribe2 = mStompClient.topic("/topic/call/treat").subscribe(stompMessage -> {
+                    Log.e(TAG, "呼叫: " + stompMessage.getPayload());
+
+                    Person.User user = gson.fromJson(stompMessage.getPayload(), Person.User.class);
+
+                    if (user != null && !TextUtils.isEmpty(user.getId())) {
+                        Message msg = Message.obtain();
+                        msg.obj = user;
+                        msg.what = 1002;
+                        handler.sendMessage(msg);
+                    }
+                }
+                , throwable -> {
+                    Log.e(TAG, "呼叫错误: " + throwable.getMessage());
+                });
+
+        Disposable subscribe = mStompClient.topic("/topic/call/treatSure").subscribe(stompMessage -> {
+            Log.e(TAG, "进行中: " + stompMessage.getPayload());
+//            showMessage(stompMessage);
+            Person.User user = gson.fromJson(stompMessage.getPayload(), Person.User.class);
+
+            Message msg = Message.obtain();
+            msg.obj = user;
+            msg.what = 1003;
+            handler.sendMessage(msg);
+
+        }, throwable -> {
+            Log.e(TAG, "进行中错误: " + throwable.getMessage());
+
+        });
+
+
+        Disposable subscribe3 = mStompClient.topic("/topic/healthCheck").subscribe(stompMessage -> {
+//            Log.e(TAG, "心跳包: " + stompMessage.getPayload());
+            if (!TextUtils.isEmpty(stompMessage.getPayload())) {
+                Log.e(TAG, "ready to disconnect after 9s");
+                handler.removeCallbacks(reconnectSocketRun);
+                handler.postDelayed(reconnectSocketRun, 9000);
+            }
+
+        }, throwable -> {
+            Log.e(TAG, "心跳错误: " + throwable.getMessage());
+        });
+    }
+
+
+    private void request() {
+
+        if (!first) {
+            return;
+        }
+        Log.e(TAG, "正在发送请求数据");
+
+        OkHttpClient client = new OkHttpClient();
+        Request request = new Request.Builder()
+                .url(LIST_URL)
+                .get()
+                .build();
+
+        client.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                Log.e(TAG, "错误:" + e.getMessage());
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+                Log.e(TAG, "code:" + response.code() + " message:" + response.message());
+                if (response.code() == 200) {
+                    first = true;
+                }
+            }
+        });
+
+    }
+}

+ 46 - 0
app/src/main/java/com/makeit/callservice/util/MarkUtil.java

@@ -0,0 +1,46 @@
+package com.makeit.callservice.util;
+
+
+import android.text.TextUtils;
+
+/**
+ * @author chenfeng
+ * @date 2018/10/11 20:19
+ */
+public class MarkUtil {
+
+    /**
+     * 用户身份证号码的打码隐藏加星号加*
+     * <p>18位和非18位身份证处理均可成功处理</p>
+     * <p>参数异常直接返回null</p>
+     *
+     * @param idCardNum 身份证号码
+     * @param front     需要显示前几位
+     * @param end       需要显示末几位
+     * @return 处理完成的身份证
+     */
+    public static String idMask(String idCardNum, int front, int end) {
+        //身份证不能为空
+        if (TextUtils.isEmpty(idCardNum)) {
+            return "";
+        }
+        //需要截取的长度不能大于身份证号长度
+        if ((front + end) > idCardNum.length()) {
+            return "";
+        }
+        //需要截取的不能小于0
+        if (front < 0 || end < 0) {
+            return "";
+        }
+        //计算*的数量
+        int asteriskCount = idCardNum.length() - (front + end);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < asteriskCount; i++) {
+            sb.append("*");
+        }
+        String regex = "(\\w{" + String.valueOf(front) + "})(\\w+)(\\w{" + String.valueOf(end) + "})";
+        return idCardNum.replaceAll(regex, "$1" + sb + "$3");
+    }
+
+
+}

BIN
app/src/main/res/drawable/app_icon_your_company.png


+ 9 - 0
app/src/main/res/drawable/default_background.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="-270"
+        android:endColor="@color/background_gradient_end"
+        android:startColor="@color/background_gradient_start" />
+</shape>

+ 10 - 0
app/src/main/res/drawable/home_head_middle_blue_bg.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_085fc4" />
+    <corners
+        android:bottomLeftRadius="15dp"
+        android:bottomRightRadius="15dp" />
+
+</shape>

+ 10 - 0
app/src/main/res/drawable/home_head_time_blue_bg.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_085fc4" />
+    <corners
+        android:bottomLeftRadius="300dp"
+        android:topLeftRadius="300dp" />
+
+</shape>

+ 10 - 0
app/src/main/res/drawable/home_left_bottom_orange_bg.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_fd9b34" />
+    <corners
+        android:bottomLeftRadius="10dp"
+        android:bottomRightRadius="10dp" />
+
+</shape>

+ 10 - 0
app/src/main/res/drawable/home_right_name_white_bg.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_e8f4ff" />
+    <corners
+        android:bottomRightRadius="10dp"
+        android:bottomLeftRadius="10dp" />
+
+</shape>

+ 10 - 0
app/src/main/res/drawable/home_right_top_blue_bg.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/text_color_3485e9" />
+    <corners
+        android:topRightRadius="10dp"
+        android:topLeftRadius="10dp" />
+
+</shape>

BIN
app/src/main/res/drawable/movie.png


+ 198 - 0
app/src/main/res/layout-large/activity_home.xml

@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/white"
+    tools:context=".ui.HomeActivity">
+
+    <Button
+        android:id="@+id/remove"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dp_100"
+        android:paddingBottom="@dimen/dp_18"
+        android:visibility="visible">
+
+        <ImageView
+            android:id="@+id/activity_home_iv_logo"
+            android:layout_width="300dp"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:contentDescription="logo"
+            android:src="@mipmap/call_home_hdlogo"
+            android:visibility="visible" />
+
+        <TextView
+            android:id="@+id/activity_home_tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_marginStart="@dimen/dp_20"
+            android:layout_toEndOf="@+id/activity_home_iv_logo"
+            android:background="@drawable/home_head_middle_blue_bg"
+            android:gravity="center"
+           android:padding="@dimen/dp_16"
+            android:text="@string/activity_home_department_of_tumor_radiotherapy"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/sp_36"
+            android:textStyle="bold"
+            android:visibility="visible"
+            tools:text="肿瘤放疗科" />
+
+        <TextClock
+            android:id="@+id/activity_home_tv_date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="10dp"
+            android:layout_toStartOf="@+id/activity_home_tv_time"
+            android:format12Hour="EE\nyyyy年MM月dd日"
+            android:format24Hour="EE\nyyyy年MM月dd日"
+            android:textColor="@color/text_color_161617"
+            android:textSize="@dimen/sp_24"
+            tools:text="周一\n2019-10-10" />
+
+        <TextClock
+            android:id="@+id/activity_home_tv_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:background="@drawable/home_head_time_blue_bg"
+            android:drawableStart="@mipmap/call_home_clock"
+            android:format12Hour="a hh:mm"
+            android:format24Hour="HH:mm"
+            android:gravity="center"
+            android:padding="@dimen/dp_16"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/sp_24"
+            tools:text="上午 10:30" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/text_color_0e4382"
+        android:baselineAligned="false"
+        android:orientation="horizontal"
+        android:padding="@dimen/dp_18">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2"
+            android:orientation="vertical">
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_right_top_blue_bg"
+                android:paddingTop="@dimen/dp_16"
+                android:paddingBottom="@dimen/dp_16"
+                android:visibility="visible">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/dp_30"
+                    android:text="@string/activity_home_order"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true"
+                    android:text="@string/activity_home_patient"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+                <TextView
+                    android:id="@+id/activity_home_times"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginRight="@dimen/dp_30"
+                    android:text="@string/activity_home_undone_total"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+            </RelativeLayout>
+
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/activity_home_rv"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:background="@color/text_color_f2f9ff"
+                tools:ignore="NestedWeights"
+                tools:listitem="@layout/activity_home_adapter_item" />
+
+            <RelativeLayout
+                android:id="@+id/activity_home_rl_tip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_left_bottom_orange_bg"
+                android:gravity="center"
+                android:padding="@dimen/dp_16"
+                android:visibility="gone">
+
+                <TextView
+                    android:id="@+id/activity_home_tv_tip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:drawableStart="@mipmap/call_home_tip"
+                    android:drawablePadding="@dimen/dp_36"
+                    android:gravity="center"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32"
+                    tools:text="请亲是浪费你的说法你" />
+
+            </RelativeLayout>
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginStart="@dimen/dp_18"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_right_top_blue_bg"
+                android:gravity="center"
+                android:paddingTop="@dimen/dp_16"
+                android:paddingBottom="@dimen/dp_16"
+                android:text="@string/activity_home_radiotherapy"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/sp_32" />
+
+            <TextView
+                android:id="@+id/activity_home_tv_name"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:background="@drawable/home_right_name_white_bg"
+                android:gravity="center"
+                android:textColor="@color/text_color_001345"
+                android:textSize="@dimen/sp_114"
+                tools:ignore="NestedWeights"
+                tools:text="哈哈" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+</LinearLayout>

+ 76 - 0
app/src/main/res/layout-large/activity_home_adapter_item.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="90dp"
+    android:background="@mipmap/call_home_item"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/home_adapter_item_iv_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:visibility="invisible"
+        android:layout_marginStart="10dp"
+        android:src="@mipmap/call_home_itemtip" />
+
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_no"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="10dp"
+        android:layout_toEndOf="@+id/home_adapter_item_iv_icon"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_32"
+        tools:text="1" />
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_32"
+        tools:text="李*敏" />
+
+
+    <TextView
+        android:id="@+id/activity_home_times"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="@dimen/dp_80"
+        android:text="@string/activity_home_undone_total"
+        android:visibility="invisible"
+        android:textColor="@android:color/white"
+        android:textSize="@dimen/sp_32" />
+
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_times"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toStartOf="@+id/home_adapter_item_tv_total"
+        android:textColor="@color/text_color_0065dc"
+        android:textSize="@dimen/sp_32"
+        android:textStyle="bold"
+        android:visibility="gone"
+        tools:text="35" />
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_total"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="@dimen/dp_60"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_32"
+        tools:text="50" />
+
+</RelativeLayout>

+ 202 - 0
app/src/main/res/layout/activity_home.xml

@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".ui.HomeActivity">
+
+    <Button
+        android:id="@+id/remove"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dp_100"
+        android:background="@android:color/white"
+        android:paddingBottom="@dimen/dp_18"
+        android:visibility="visible">
+
+        <ImageView
+            android:id="@+id/activity_home_iv_logo"
+            android:layout_width="150dp"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:contentDescription="logo"
+            android:src="@mipmap/call_home_hdlogo"
+            android:visibility="visible" />
+
+        <TextView
+            android:id="@+id/activity_home_tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_marginStart="@dimen/dp_20"
+            android:layout_toEndOf="@+id/activity_home_iv_logo"
+            android:background="@drawable/home_head_middle_blue_bg"
+            android:gravity="center"
+            android:padding="@dimen/dp_16"
+            android:text="@string/activity_home_department_of_tumor_radiotherapy"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/sp_36"
+            android:textStyle="bold"
+            android:visibility="visible"
+            tools:text="肿瘤放疗科" />
+
+        <TextClock
+            android:id="@+id/activity_home_tv_date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="10dp"
+            android:layout_toStartOf="@+id/activity_home_tv_time"
+            android:format12Hour="EE\nyyyy年MM月dd日"
+            android:format24Hour="EE\nyyyy年MM月dd日"
+            android:textColor="@color/text_color_161617"
+            android:textSize="@dimen/sp_24"
+            tools:text="周一\n2019-10-10" />
+
+        <TextClock
+            android:id="@+id/activity_home_tv_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:background="@drawable/home_head_time_blue_bg"
+            android:drawableStart="@mipmap/call_home_clock"
+            android:format12Hour="a hh:mm"
+            android:format24Hour="HH:mm"
+            android:gravity="center"
+            android:paddingStart="@dimen/dp_10"
+            android:paddingTop="@dimen/dp_16"
+            android:paddingEnd="@dimen/dp_10"
+            android:paddingBottom="@dimen/dp_16"
+            android:textColor="@android:color/white"
+            android:textSize="@dimen/sp_24"
+            tools:text="上午 10:30" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/text_color_0e4382"
+        android:baselineAligned="false"
+        android:orientation="horizontal"
+        android:padding="@dimen/dp_18">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="2"
+            android:orientation="vertical">
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_right_top_blue_bg"
+                android:paddingTop="@dimen/dp_16"
+                android:paddingBottom="@dimen/dp_16"
+                android:visibility="visible">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerVertical="true"
+                    android:layout_marginStart="@dimen/dp_30"
+                    android:text="@string/activity_home_order"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginEnd="@dimen/dp_100"
+                    android:layout_toStartOf="@+id/activity_home_times"
+                    android:text="@string/activity_home_patient"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+                <TextView
+                    android:id="@+id/activity_home_times"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentEnd="true"
+                    android:layout_centerVertical="true"
+                    android:layout_marginRight="@dimen/dp_30"
+                    android:text="@string/activity_home_undone_total"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32" />
+
+            </RelativeLayout>
+
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/activity_home_rv"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:background="@color/text_color_f2f9ff"
+                tools:ignore="NestedWeights"
+                tools:listitem="@layout/activity_home_adapter_item" />
+
+            <RelativeLayout
+                android:id="@+id/activity_home_rl_tip"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_left_bottom_orange_bg"
+                android:gravity="center"
+                android:padding="@dimen/dp_16"
+                android:visibility="gone">
+
+                <TextView
+                    android:id="@+id/activity_home_tv_tip"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:drawableStart="@mipmap/call_home_tip"
+                    android:drawablePadding="@dimen/dp_36"
+                    android:gravity="center"
+                    android:textColor="@android:color/white"
+                    android:textSize="@dimen/sp_32"
+                    tools:text="请亲是浪费你的说法你" />
+
+            </RelativeLayout>
+
+        </LinearLayout>
+
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_marginStart="@dimen/dp_18"
+            android:layout_weight="1"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/home_right_top_blue_bg"
+                android:gravity="center"
+                android:paddingTop="@dimen/dp_16"
+                android:paddingBottom="@dimen/dp_16"
+                android:text="@string/activity_home_radiotherapy"
+                android:textColor="@android:color/white"
+                android:textSize="@dimen/sp_32" />
+
+            <TextView
+                android:id="@+id/activity_home_tv_name"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:background="@drawable/home_right_name_white_bg"
+                android:gravity="center"
+                android:textColor="@color/text_color_001345"
+                android:textSize="@dimen/sp_114"
+                tools:ignore="NestedWeights"
+                tools:text="哈哈" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+</LinearLayout>

+ 76 - 0
app/src/main/res/layout/activity_home_adapter_item.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/dp_60"
+    android:background="@mipmap/call_home_item"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/home_adapter_item_iv_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:src="@mipmap/call_home_itemtip"
+        android:visibility="invisible" />
+
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_no"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="6dp"
+        android:layout_toEndOf="@+id/home_adapter_item_iv_icon"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_16"
+        tools:text="1" />
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="@dimen/dp_80"
+        android:layout_toStartOf="@+id/activity_home_times"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_16"
+        tools:text="李*敏" />
+
+
+    <TextView
+        android:id="@+id/activity_home_times"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginRight="@dimen/dp_30"
+        android:text="@string/activity_home_undone_total"
+        android:textColor="@android:color/white"
+        android:textSize="@dimen/sp_16"
+        android:visibility="invisible" />
+
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_times"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toStartOf="@+id/home_adapter_item_tv_total"
+        android:textColor="@color/text_color_0065dc"
+        android:textSize="@dimen/sp_16"
+        android:textStyle="bold"
+        tools:text="35" />
+
+    <TextView
+        android:id="@+id/home_adapter_item_tv_total"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:layout_marginEnd="@dimen/dp_60"
+        android:textColor="@color/text_color_000f3d"
+        android:textSize="@dimen/sp_16"
+        tools:text="/50" />
+
+</RelativeLayout>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_clock.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_hdlogo.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_headbg.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_headmid.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_item.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_itemacti.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_itemtip.png


BIN
app/src/main/res/mipmap-xhdpi/call_home_leftbtm.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_leftth.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_rightbtm.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_rightth.jpg


BIN
app/src/main/res/mipmap-xhdpi/call_home_tip.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


+ 19 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,19 @@
+<resources>
+    <color name="background_gradient_start">#000000</color>
+    <color name="background_gradient_end">#DDDDDD</color>
+    <color name="fastlane_background">#0096a6</color>
+    <color name="search_opaque">#ffaa3f</color>
+    <color name="selected_background">#ffaa3f</color>
+    <color name="default_background">#3d3d3d</color>
+    
+    <color name="text_color_161617">#161617</color>
+    <color name="text_color_085fc4">#085FC4</color>
+    <color name="text_color_0e4382">#0e4382</color>
+    <color name="text_color_f2f9ff">#f2f9ff</color>
+    <color name="text_color_e8f4ff">#e8f4ff</color>
+    <color name="text_color_001345">#001345</color>
+    <color name="text_color_3485e9">#3485E9</color>
+    <color name="text_color_fd9b34">#FD9B34</color>
+    <color name="text_color_000f3d">#000f3d</color>
+    <color name="text_color_0065dc">#0065dc</color>
+</resources>

+ 25 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="dp_10">10dp</dimen>
+    <dimen name="dp_16">16dp</dimen>
+    <dimen name="dp_18">18dp</dimen>
+    <dimen name="dp_20">20dp</dimen>
+    <dimen name="dp_25">25dp</dimen>
+    <dimen name="dp_30">30dp</dimen>
+    <dimen name="dp_36">36dp</dimen>
+    <dimen name="dp_60">60dp</dimen>
+    <dimen name="dp_80">90dp</dimen>
+    <dimen name="dp_100">100dp</dimen>
+    <dimen name="dp_120">120dp</dimen>
+
+
+
+    <dimen name="sp_16">16sp</dimen>
+    <dimen name="sp_24">24sp</dimen>
+    <dimen name="sp_32">32sp</dimen>
+    <dimen name="sp_114">114sp</dimen>
+    <dimen name="dp_108">108dp</dimen>
+    <dimen name="sp_36">36sp</dimen>
+    <dimen name="sp_49">49sp</dimen>
+    <dimen name="sp_64">64sp</dimen>
+</resources>

+ 30 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,30 @@
+<resources>
+    <string name="app_name">CallService</string>
+    <string name="browse_title"><![CDATA[Videos by Your Company]]></string>
+    <string name="related_movies">Related Videos</string>
+    <string name="grid_view">Grid View</string>
+    <string name="error_fragment">Error Fragment</string>
+    <string name="personal_settings">Personal Settings</string>
+    <string name="watch_trailer_1">Watch trailer</string>
+    <string name="watch_trailer_2">FREE</string>
+    <string name="rent_1">Rent By Day</string>
+    <string name="rent_2">From $1.99</string>
+    <string name="buy_1">Buy and Own</string>
+    <string name="buy_2">AT $9.99</string>
+    <string name="movie">Movie</string>
+
+    <!-- Error messages -->
+    <string name="error_fragment_message">An error occurred</string>
+    <string name="dismiss_error">Dismiss</string>
+
+
+
+    <string name="home_tips">请%s到放疗室放疗</string>
+    <string name="home_total">/%s</string>
+    <string name="activity_home_department_of_tumor_radiotherapy">肿瘤放疗科</string>
+    <string name="activity_home_order">序号</string>
+    <string name="activity_home_patient">患者</string>
+    <string name="activity_home_undone_total">总次数</string>
+    <string name="activity_home_radiotherapy">正在放疗</string>
+
+</resources>

+ 6 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+<!--    <style name="AppTheme" parent="@style/Theme.Leanback" />-->
+    <style name="AppTheme" parent="@android:style/Theme.NoTitleBar.Fullscreen" />
+</resources>

+ 29 - 0
build.gradle

@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    ext.kotlin_version = '1.3.50'
+    repositories {
+        google()
+        jcenter()
+        
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath 'com.tencent.bugly:tinker-support:1.1.2'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        maven { url "https://jitpack.io" }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 21 - 0
gradle.properties

@@ -0,0 +1,21 @@
+# 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=-Xmx1536m
+# 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
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+#distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 234 - 0
gradlew

@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright ? 2015-2021 the original 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 POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions ?$var?, ?${var}?, ?${var:-default}?, ?${var+SET}?,
+#           ?${var#prefix}?, ?${var%suffix}?, and ?$( cmd )?;
+#         * compound commands having a testable exit status, especially ?case?;
+#         * various built-in commands including ?command?, ?set?, and ?ulimit?.
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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 "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# 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  ;; #(
+  MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -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

+ 2 - 0
settings.gradle

@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name='CallService'