Kaynağa Gözat

手持POS工程上传

suxinghua 4 yıl önce
ebeveyn
işleme
78e200afcc
100 değiştirilmiş dosya ile 7499 ekleme ve 0 silme
  1. 15 0
      HandPos/.gitignore
  2. 3 0
      HandPos/.idea/.gitignore
  3. 6 0
      HandPos/.idea/compiler.xml
  4. 22 0
      HandPos/.idea/gradle.xml
  5. 85 0
      HandPos/.idea/jarRepositories.xml
  6. 14 0
      HandPos/.idea/misc.xml
  7. 10 0
      HandPos/.idea/runConfigurations.xml
  8. 6 0
      HandPos/.idea/vcs.xml
  9. 55 0
      HandPos/JitPackUpload.gradle
  10. 1 0
      HandPos/app/.gitignore
  11. 97 0
      HandPos/app/build.gradle
  12. BIN
      HandPos/app/libs/Msc.jar
  13. BIN
      HandPos/app/libs/NeptuneLiteApi_V3.23.00_20210409.jar
  14. BIN
      HandPos/app/libs/PaxEEmv_V1.00.11_20180820.jar
  15. BIN
      HandPos/app/libs/PaxGL_V1.00.08_20171101.jar
  16. BIN
      HandPos/app/libs/cocoatools.jar
  17. BIN
      HandPos/app/libs/zxing.jar
  18. 21 0
      HandPos/app/proguard-rules.pro
  19. 18 0
      HandPos/app/release/output-metadata.json
  20. 26 0
      HandPos/app/src/androidTest/java/com/yijia/handpos/ExampleInstrumentedTest.java
  21. 55 0
      HandPos/app/src/main/AndroidManifest.xml
  22. 12 0
      HandPos/app/src/main/assets/call.bnf
  23. 8 0
      HandPos/app/src/main/assets/grammar_sample.abnf
  24. BIN
      HandPos/app/src/main/assets/iattest.wav
  25. BIN
      HandPos/app/src/main/assets/iflytek/recognize.xml
  26. BIN
      HandPos/app/src/main/assets/iflytek/voice_bg.9.png
  27. BIN
      HandPos/app/src/main/assets/iflytek/voice_empty.png
  28. BIN
      HandPos/app/src/main/assets/iflytek/voice_full.png
  29. BIN
      HandPos/app/src/main/assets/iflytek/waiting.png
  30. BIN
      HandPos/app/src/main/assets/iflytek/warning.png
  31. BIN
      HandPos/app/src/main/assets/tts/common.jet
  32. BIN
      HandPos/app/src/main/assets/tts/xiaofeng.jet
  33. BIN
      HandPos/app/src/main/assets/tts/xiaoyan.jet
  34. 1 0
      HandPos/app/src/main/assets/userwords
  35. 8 0
      HandPos/app/src/main/assets/wake.bnf
  36. 8 0
      HandPos/app/src/main/assets/wake_grammar_sample.abnf
  37. 522 0
      HandPos/app/src/main/java/com/yijia/handpos/MainActivity.java
  38. 152 0
      HandPos/app/src/main/java/com/yijia/handpos/PosApplication.java
  39. 85 0
      HandPos/app/src/main/java/com/yijia/handpos/adapter/MyOrderAdapter.java
  40. 88 0
      HandPos/app/src/main/java/com/yijia/handpos/adapter/OffdutedAdapter.java
  41. 65 0
      HandPos/app/src/main/java/com/yijia/handpos/custom/CustomUpdateParser.java
  42. 105 0
      HandPos/app/src/main/java/com/yijia/handpos/custom/CustomUpdatePrompter.java
  43. 115 0
      HandPos/app/src/main/java/com/yijia/handpos/custom/XUpdateServiceParser.java
  44. 117 0
      HandPos/app/src/main/java/com/yijia/handpos/device/ISysteHelper.java
  45. 377 0
      HandPos/app/src/main/java/com/yijia/handpos/device/SysteHelper.java
  46. 106 0
      HandPos/app/src/main/java/com/yijia/handpos/device/constants/FieldDefinition.java
  47. 46 0
      HandPos/app/src/main/java/com/yijia/handpos/device/constants/PrintDefine.java
  48. 202 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvAid.java
  49. 136 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvCapk.java
  50. 237 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvContactlessListenerImpl.java
  51. 31 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvDeviceListenerImpl.java
  52. 33 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvListenerImpl.java
  53. 86 0
      HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvTags.java
  54. 133 0
      HandPos/app/src/main/java/com/yijia/handpos/device/m1card/M1CardReadHelper.java
  55. 200 0
      HandPos/app/src/main/java/com/yijia/handpos/device/print/PrintBean.java
  56. 10 0
      HandPos/app/src/main/java/com/yijia/handpos/device/print/PrintCallBack.java
  57. 22 0
      HandPos/app/src/main/java/com/yijia/handpos/device/scan/ScanResultListener.java
  58. 749 0
      HandPos/app/src/main/java/com/yijia/handpos/device/trade/DeviceController.java
  59. 109 0
      HandPos/app/src/main/java/com/yijia/handpos/device/trade/IDeviceController.java
  60. 56 0
      HandPos/app/src/main/java/com/yijia/handpos/device/trade/TradeListener.java
  61. 81 0
      HandPos/app/src/main/java/com/yijia/handpos/device/utils/PanUtils.java
  62. 81 0
      HandPos/app/src/main/java/com/yijia/handpos/device/utils/TrackUtils.java
  63. 85 0
      HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/ConstellationAdapter.java
  64. 49 0
      HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/DeviceUtils.java
  65. 265 0
      HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/DropDownMenu.java
  66. 85 0
      HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/GirdDropDownAdapter.java
  67. 84 0
      HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/ListDropDownAdapter.java
  68. 74 0
      HandPos/app/src/main/java/com/yijia/handpos/entity/ApiResult.java
  69. 187 0
      HandPos/app/src/main/java/com/yijia/handpos/entity/AppVersionInfo.java
  70. 43 0
      HandPos/app/src/main/java/com/yijia/handpos/entity/CustomResult.java
  71. 148 0
      HandPos/app/src/main/java/com/yijia/handpos/http/OKHttpUpdateHttpService.java
  72. 124 0
      HandPos/app/src/main/java/com/yijia/handpos/http/XHttpUpdateHttpService.java
  73. 38 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/ContentData.java
  74. 204 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/Data.java
  75. 39 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/DealList.java
  76. 28 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSum.java
  77. 17 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSumGroupByOilName.java
  78. 17 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSumGroupByPayType.java
  79. 14 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/MessageContent.java
  80. 130 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/MyOrderBean.java
  81. 18 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/OffdutedItemBean.java
  82. 33 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/OrderList.java
  83. 15 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/PrintRootBean.java
  84. 31 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/ResponseBean.java
  85. 17 0
      HandPos/app/src/main/java/com/yijia/handpos/pojo/TimeList.java
  86. 37 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/account/AccountFragment.java
  87. 19 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/account/AccountViewModel.java
  88. 91 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/account/VersionActivity.java
  89. 175 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/component/CustomDialog.java
  90. 93 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/home/HomeFragment.java
  91. 19 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/home/HomeViewModel.java
  92. 17 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoggedInUserView.java
  93. 590 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginActivity.java
  94. 40 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginFormState.java
  95. 31 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginResult.java
  96. 70 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginViewModel.java
  97. 26 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginViewModelFactory.java
  98. 29 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/LoginDataSource.java
  99. 54 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/LoginRepository.java
  100. 48 0
      HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/Result.java

+ 15 - 0
HandPos/.gitignore

@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 3 - 0
HandPos/.idea/.gitignore

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

+ 6 - 0
HandPos/.idea/compiler.xml

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

+ 22 - 0
HandPos/.idea/gradle.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="PLATFORM" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="/usr/local/Cellar/gradle/6.8.3/libexec" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+            <option value="$PROJECT_DIR$/xupdate-lib" />
+          </set>
+        </option>
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 85 - 0
HandPos/.idea/jarRepositories.xml

@@ -0,0 +1,85 @@
+<?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="Google" />
+      <option name="name" value="Google" />
+      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
+    </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="MavenRepo" />
+      <option name="name" value="MavenRepo" />
+      <option name="url" value="https://repo.maven.apache.org/maven2/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="Google2" />
+      <option name="name" value="Google2" />
+      <option name="url" value="https://maven.aliyun.com/repository/google" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenRepo2" />
+      <option name="name" value="MavenRepo2" />
+      <option name="url" value="https://maven.aliyun.com/repository/central" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="BintrayJCenter2" />
+      <option name="name" value="BintrayJCenter2" />
+      <option name="url" value="https://maven.aliyun.com/repository/jcenter" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven3" />
+      <option name="name" value="maven3" />
+      <option name="url" value="http://maven.aliyun.com/nexus/content/groups/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven4" />
+      <option name="name" value="maven4" />
+      <option name="url" value="https://dl.bintray.com/umsdk/release" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven5" />
+      <option name="name" value="maven5" />
+      <option name="url" value="https://oss.sonatype.org/content/repositories/public" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven6" />
+      <option name="name" value="maven6" />
+      <option name="url" value="file:$PROJECT_DIR$/LocalRepository" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="MavenLocal" />
+      <option name="name" value="MavenLocal" />
+      <option name="url" value="file:$USER_HOME$/.m2/repository/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven6" />
+      <option name="name" value="maven6" />
+      <option name="url" value="file:$PROJECT_DIR$/xupdate-lib/LocalRepository" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="maven6" />
+      <option name="name" value="maven6" />
+      <option name="url" value="file:$PROJECT_DIR$/app/LocalRepository" />
+    </remote-repository>
+  </component>
+</project>

+ 14 - 0
HandPos/.idea/misc.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CMakeSettings">
+    <configurations>
+      <configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
+    </configurations>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" 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>

+ 10 - 0
HandPos/.idea/runConfigurations.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>

+ 6 - 0
HandPos/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/volley" vcs="Git" />
+  </component>
+</project>

+ 55 - 0
HandPos/JitPackUpload.gradle

@@ -0,0 +1,55 @@
+apply plugin: 'com.github.dcendents.android-maven'
+
+// 指定group,com.github.<用户名>,这里我默认填写的是我的github账号,请换成你自己的。
+group='com.github.shamoxiaoniqiu2008'
+
+//---------------------------------------------
+
+
+// 指定编码
+tasks.withType(JavaCompile) {
+    options.encoding = "UTF-8"
+}
+
+tasks.withType(Javadoc) {
+    options.encoding = 'UTF-8'
+}
+
+if (project.hasProperty("android")) { // Android libraries
+    task sourcesJar(type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.java.srcDirs
+    }
+
+    task javadoc(type: Javadoc) {
+        failOnError  false
+        source = android.sourceSets.main.java.srcDirs
+        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+    }
+} else { // Java libraries
+    task sourcesJar(type: Jar, dependsOn: classes) {
+        classifier = 'sources'
+        from sourceSets.main.allSource
+    }
+}
+
+javadoc {
+    options {
+        encoding "UTF-8"
+        charSet 'UTF-8'
+        author true
+        version true
+        links "http://docs.oracle.com/javase/7/docs/api"
+    }
+}
+
+// 制作文档(Javadoc)
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+artifacts {
+    archives javadocJar
+    archives sourcesJar
+}

+ 1 - 0
HandPos/app/.gitignore

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

+ 97 - 0
HandPos/app/build.gradle

@@ -0,0 +1,97 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.xuexiang.xaop' //引用xaop插件
+apply plugin: 'img-optimizer'
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.3"
+
+    defaultConfig {
+        applicationId "com.yijia.handpos"
+        minSdkVersion 19
+        targetSdkVersion 30
+        versionCode 5
+        versionName "1.0"
+        multiDexEnabled true
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+        ndk {
+            // 设置支持的SO库架构
+            abiFilters 'armeabi' ,'x86', 'armeabi-v7a', 'x86_64','arm64-v8a'
+        }
+    }
+    buildTypes {
+        release {
+            // true - 打开混淆
+            minifyEnabled true
+            // true - 打开资源压缩
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: "libs", include: ["*.jar"])
+    implementation files('libs/cocoatools.jar')
+    implementation files('libs/PaxEEmv_V1.00.11_20180820.jar')
+    implementation files('libs/NeptuneLiteApi_V3.23.00_20210409.jar')
+    implementation files('libs/Msc.jar')
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+    implementation 'androidx.appcompat:appcompat:1.1.0'
+    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
+    implementation 'androidx.navigation:navigation-fragment:2.2.2'
+    implementation 'androidx.navigation:navigation-ui:2.2.2'
+    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'androidx.annotation:annotation:1.1.0'
+    implementation 'com.android.volley:volley:1.2.0'
+    implementation 'com.github.d-max:spots-dialog:1.1@aar'
+    implementation 'com.google.code.gson:gson:2.8.5'
+    // ButterKnife的sdk
+    implementation 'com.jakewharton:butterknife:10.2.3'
+    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
+
+    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
+    implementation 'com.rabbitmq:amqp-client:5.12.0'
+    implementation 'org.projectlombok:lombok:1.18.20'
+    annotationProcessor 'org.projectlombok:lombok:1.18.20'
+    implementation 'com.leon:lsettingviewlibrary:1.7.0'
+    implementation 'de.hdodenhof:circleimageview:3.1.0'
+
+    implementation 'com.github.xuexiangjys.XUtil:xutil-core:2.0.0'
+    implementation 'com.github.xuexiangjys.XUtil:xutil-sub:2.0.0'
+    implementation 'com.github.xuexiangjys.XAOP:xaop-runtime:1.1.0'  //添加依赖
+
+    //xupdate-lib
+    implementation project(':xupdate-lib')
+    // XPage
+    implementation 'com.github.xuexiangjys.XPage:xpage-lib:3.1.1'
+    annotationProcessor 'com.github.xuexiangjys.XPage:xpage-compiler:3.1.1'
+
+    //如果开启了内存泄漏监测leak,就需要加上这个依赖
+//    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
+
+    //网络请求的实现一
+    implementation 'com.zhy:okhttputils:2.6.2'
+
+    //网络请求的实现二
+    implementation 'com.github.xuexiangjys:XHttp2:2.0.1'
+    implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.2'
+    implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
+    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+
+    implementation 'com.android.support:multidex:1.0.3'
+    //日志框架Logger
+    implementation 'com.orhanobut:logger:2.2.0'
+
+}

BIN
HandPos/app/libs/Msc.jar


BIN
HandPos/app/libs/NeptuneLiteApi_V3.23.00_20210409.jar


BIN
HandPos/app/libs/PaxEEmv_V1.00.11_20180820.jar


BIN
HandPos/app/libs/PaxGL_V1.00.08_20171101.jar


BIN
HandPos/app/libs/cocoatools.jar


BIN
HandPos/app/libs/zxing.jar


+ 21 - 0
HandPos/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

+ 18 - 0
HandPos/app/release/output-metadata.json

@@ -0,0 +1,18 @@
+{
+  "version": 2,
+  "artifactType": {
+    "type": "APK",
+    "kind": "Directory"
+  },
+  "applicationId": "com.yijia.handpos",
+  "variantName": "release",
+  "elements": [
+    {
+      "type": "SINGLE",
+      "filters": [],
+      "versionCode": 5,
+      "versionName": "1.0",
+      "outputFile": "app-release.apk"
+    }
+  ]
+}

+ 26 - 0
HandPos/app/src/androidTest/java/com/yijia/handpos/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.yijia.handpos;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.yijia.handpos", appContext.getPackageName());
+    }
+}

+ 55 - 0
HandPos/app/src/main/AndroidManifest.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.yijia.handpos">
+
+    <application
+        android:name=".PosApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.HandPos">
+        <activity android:name=".ui.account.VersionActivity"></activity>
+        <activity
+            android:name=".ui.login.LoginActivity"
+            android:label="@string/title_activity_login">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".MainActivity" />
+        <activity android:name=".ui.order.OrderDetailActivity" />
+        <activity android:name=".ui.work.OffdutyedDetailActivity" />
+        <activity android:name=".ui.work.OffdutyedActivity" />
+        <activity android:name=".ui.work.OffdutyActivity" />
+        <activity android:name=".ui.statistic.StatisticActivity" />
+    </application>
+    <!--连接网络权限,用于执行云端语音能力 -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <!--读取网络信息状态 -->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <!--获取当前wifi状态 -->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <!--允许程序改变网络连接状态 -->
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <!--读取手机信息权限 -->
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <!--读取联系人权限,上传联系人需要用到此权限 -->
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <!--外存储写权限,构建语法需要用到此权限 -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <!--外存储读权限,构建语法需要用到此权限 -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
+    <!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
+    <uses-permission android:name="android.permission.CAMERA" />
+
+
+</manifest>

+ 12 - 0
HandPos/app/src/main/assets/call.bnf

@@ -0,0 +1,12 @@
+#BNF+IAT 1.0 UTF-8;
+!grammar call;
+!slot <contact>;
+!slot <callPre>;
+!slot <callPhone>;
+!slot <callTo>;
+!start <callStart>;
+<callStart>:[<callPre>][<callTo>]<contact><callPhone>|[<callPre>]<callPhone>[<callTo>]<contact>;
+<contact>:张海洋;
+<callPre>:我要|我想|我想要;
+<callPhone>:打电话;
+<callTo>:给;

+ 8 - 0
HandPos/app/src/main/assets/grammar_sample.abnf

@@ -0,0 +1,8 @@
+#ABNF 1.0 UTF-8;
+language zh-CN; 
+mode voice;
+
+root $main;
+$main = $place1 到 $place2;
+$place1 = 北京|武汉|南京|天津|东京;
+$place2 = 上海|合肥;

BIN
HandPos/app/src/main/assets/iattest.wav


BIN
HandPos/app/src/main/assets/iflytek/recognize.xml


BIN
HandPos/app/src/main/assets/iflytek/voice_bg.9.png


BIN
HandPos/app/src/main/assets/iflytek/voice_empty.png


BIN
HandPos/app/src/main/assets/iflytek/voice_full.png


BIN
HandPos/app/src/main/assets/iflytek/waiting.png


BIN
HandPos/app/src/main/assets/iflytek/warning.png


BIN
HandPos/app/src/main/assets/tts/common.jet


BIN
HandPos/app/src/main/assets/tts/xiaofeng.jet


BIN
HandPos/app/src/main/assets/tts/xiaoyan.jet


+ 1 - 0
HandPos/app/src/main/assets/userwords

@@ -0,0 +1 @@
+{"userword":[{"name":"我的常用词","words":["佳晨实业","蜀南庭苑","高兰路","复联二"]},{"name":"我的好友","words":["李馨琪","鹿晓雷","张集栋","周家莉","叶震珂","熊泽萌"]}]}

+ 8 - 0
HandPos/app/src/main/assets/wake.bnf

@@ -0,0 +1,8 @@
+#BNF+IAT 1.0 UTF-8;
+!grammar wake;
+!slot <callCmd>;
+!slot <contact>;
+!start <callStart>;
+<callStart>:[<callCmd>]<callName>;
+<callCmd>:讯飞语音|讯飞语点|叮咚叮咚;
+<callName>:张三|李四|张海洋;

+ 8 - 0
HandPos/app/src/main/assets/wake_grammar_sample.abnf

@@ -0,0 +1,8 @@
+#ABNF 1.0 UTF-8;
+language zh-CN; 
+mode voice;
+
+root $main;
+$main = [$call] $name;
+$call = 讯飞语音|讯飞语点|叮咚叮咚;
+$name = 张三|李四|张海洋;

+ 522 - 0
HandPos/app/src/main/java/com/yijia/handpos/MainActivity.java

@@ -0,0 +1,522 @@
+package com.yijia.handpos;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.iflytek.cloud.ErrorCode;
+import com.iflytek.cloud.InitListener;
+import com.iflytek.cloud.SpeechConstant;
+import com.iflytek.cloud.SpeechError;
+import com.iflytek.cloud.SpeechEvent;
+import com.iflytek.cloud.SpeechSynthesizer;
+import com.iflytek.cloud.SpeechUtility;
+import com.iflytek.cloud.SynthesizerListener;
+import com.iflytek.cloud.util.ResourceUtil;
+import com.pax.dal.IDAL;
+import com.pax.dal.IPrinter;
+import com.pax.dal.exceptions.PrinterDevException;
+import com.pax.gl.IGL;
+import com.pax.gl.imgprocessing.IImgProcessing;
+import com.pax.gl.impl.GLProxy;
+import com.pax.neptunelite.api.NeptuneLiteUser;
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DefaultConsumer;
+import com.rabbitmq.client.Envelope;
+import com.yijia.handpos.pojo.PrintRootBean;
+import com.yijia.handpos.ui.login.LoginActivity;
+import com.yijia.handpos.util.ConsUtil;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.TimeoutException;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.navigation.NavController;
+import androidx.navigation.Navigation;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+
+public class MainActivity extends AppCompatActivity {
+    private static String TAG = MainActivity.class.getSimpleName();
+    public static IDAL dal;
+    public static IGL gl;
+    public static final int FONT_BIG = 36;
+    public static final int FONT_NORMAL = 32;
+    public static final int FONT_SMALL = 22;
+    NavController navController = null;
+    ConnectionFactory connectionFactory;
+    private PrintRootBean printRootBean;
+    // 语音合成对象
+    private SpeechSynthesizer mTts;
+    // 本地发音人列表
+    private String[] localVoicersEntries;
+    private String[] localVoicersValue;
+    // 默认云端发音人
+    public static String voicerCloud = "xiaoyan";
+    // 默认本地发音人
+    public static String voicerLocal = "xiaoyan";
+    public static String voicerXtts = "xiaoyan";
+    // 引擎类型
+    private String mEngineType = SpeechConstant.TYPE_LOCAL;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
+        // 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
+        // 参数间使用“,”分隔。
+        // 设置你申请的应用appid
+        // 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
+        StringBuffer param = new StringBuffer();
+        param.append("appid=" + getString(R.string.app_id));
+        param.append(",");
+        // 设置使用v5+
+        param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
+        SpeechUtility.createUtility(MainActivity.this, param.toString());
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        BottomNavigationView navView = findViewById(R.id.nav_view);
+        // Passing each menu ID as a set of Ids because each
+        // menu should be considered as top level destinations.
+        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
+                R.id.navigation_home, R.id.navigation_order, R.id.navigation_account)
+                .build();
+        navController = Navigation.findNavController(this, R.id.nav_host_fragment);
+        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
+        NavigationUI.setupWithNavController(navView, navController);
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().hide();
+        }
+        try {
+            dal = NeptuneLiteUser.getInstance().getDal(this);
+            gl = new GLProxy(this).getGL();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        // 初始化合成对象
+        mTts = SpeechSynthesizer.createSynthesizer(this, mTtsInitListener);
+        // 本地发音人名称列表
+        localVoicersEntries = getResources().getStringArray(R.array.voicer_local_entries);
+        localVoicersValue = getResources().getStringArray(R.array.voicer_local_values);
+
+        //RabitMq 连接设置
+        setupConnectionFactory();
+        //用于从线程中获取数据,更新ui
+        final Handler incomingMessageHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                String message = msg.getData().getString("msg");
+                Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
+                printRootBean = gson.fromJson(message, PrintRootBean.class);
+                if (Arrays.asList(ConsUtil.gunNoArray).contains(printRootBean.getMessageContent()
+                        .getContentData().get(0).getOilGun())) {
+                    doPrint(printRootBean);
+                    techSpeak(printRootBean);
+                }
+                Log.i("接收到的消息:", "msg:" + message);
+            }
+        };
+        //开启消费者线程
+        //subscribe(incomingMessageHandler);
+        new Thread(() -> basicConsume(incomingMessageHandler)).start();
+    }
+
+    /**
+     * 连接设置
+     */
+    private void setupConnectionFactory() {
+        PosApplication posApplication = (PosApplication) this.getApplicationContext();
+        connectionFactory = new ConnectionFactory();
+        connectionFactory.setHost(posApplication.getHost());
+        connectionFactory.setPort(posApplication.getPort());
+        connectionFactory.setUsername(posApplication.getUsername());
+        connectionFactory.setPassword(posApplication.getPassword());
+    }
+
+    /**
+     * 收消息(从发布者那边订阅消息)
+     */
+    private void basicConsume(final Handler handler) {
+        try {
+            //连接
+            Connection connection = connectionFactory.newConnection();
+            //通道
+            final Channel channel = connection.createChannel();
+            //实现Consumer的最简单方法是将便捷类DefaultConsumer子类化。可以在basicConsume 调用上传递此子类的对象以设置订阅:
+            channel.basicConsume(ConsUtil.tusnNo, false, new DefaultConsumer(channel) {
+                @Override
+                public void handleDelivery(String consumerTag, Envelope envelope,
+                                           AMQP.BasicProperties properties, byte[] body) throws IOException {
+                    super.handleDelivery(consumerTag, envelope, properties, body);
+                    String msg = new String(body, "utf-8");
+                    long deliveryTag = envelope.getDeliveryTag();
+                    channel.basicAck(deliveryTag, false);
+                    //从message池中获取msg对象更高效
+                    Message uimsg = handler.obtainMessage();
+                    Bundle bundle = new Bundle();
+                    bundle.putString("msg", msg);
+                    uimsg.setData(bundle);
+                    handler.sendMessage(uimsg);
+                }
+            });
+        } catch (IOException e) {
+            Log.d(TAG,e.toString());
+        } catch (TimeoutException e) {
+            Log.d(TAG,e.toString());
+        }
+    }
+
+
+    public void backLoginConfirmDialog() {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        // 自定义 title样式
+        TextView title = new TextView(this);
+        title.setText("提示");
+        title.setTextSize(18);
+        title.setGravity(Gravity.CENTER);
+        title.setBackgroundColor(Color.rgb(255, 165, 0));
+        title.setPadding(0, 20, 0, 20);
+        builder.setCustomTitle(title);
+        // 中间的信息以一个view的形式设置进去
+        TextView msg = new TextView(this);
+        msg.setText("确定要返回登录界面吗?");
+        msg.setTextSize(16);
+        msg.setGravity(Gravity.LEFT);
+        msg.setPadding(20, 40, 20, 40);
+        builder.setView(msg);
+        builder.setPositiveButton("确认", (dialogInterface, i) -> {
+            dialogInterface.dismiss();
+            Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+            startActivity(intent);
+        }).setNegativeButton("取消", null);
+        // 调用 show()方法后得到 dialog对象
+        AlertDialog dialog = builder.show();
+        dialog.setCancelable(false);
+        final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+        final Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+        LinearLayout.LayoutParams positiveParams = (LinearLayout.LayoutParams) positiveButton.getLayoutParams();
+        positiveParams.gravity = Gravity.CENTER;
+        positiveParams.setMargins(10, 10, 10, 10);
+        positiveParams.width = 0;
+        // 安卓下面有三个位置的按钮,默认权重为 1,设置成 500或更大才能让两个按钮看起来均分
+        positiveParams.weight = 500;
+        LinearLayout.LayoutParams negativeParams = (LinearLayout.LayoutParams) negativeButton.getLayoutParams();
+        negativeParams.gravity = Gravity.CENTER;
+        negativeParams.setMargins(10, 10, 10, 10);
+        negativeParams.width = 0;
+        negativeParams.weight = 500;
+        positiveButton.setLayoutParams(positiveParams);
+        negativeButton.setLayoutParams(negativeParams);
+        positiveButton.setBackgroundColor(Color.parseColor("#018786"));
+        positiveButton.setTextColor(Color.parseColor("#FFFFFF"));
+        negativeButton.setBackgroundColor(Color.parseColor("#808080"));
+        positiveButton.setTextSize(16);
+        negativeButton.setTextSize(16);
+    }
+
+
+    private void doPrint(PrintRootBean printRootBean) {
+        IPrinter iPrinter = dal.getPrinter();
+        IImgProcessing.IPage page = gl.getImgProcessing().createPage();
+//        page.addLine().addUnit((msg), FONT_BIG, IImgProcessing.IPage.EAlign.CENTER);
+        page.addLine().addUnit(("支付小票"), FONT_BIG, IImgProcessing.IPage.EAlign.CENTER);
+        page.addLine().addUnit("-------------------------------------", FONT_NORMAL,
+                IImgProcessing.IPage.EAlign.CENTER);
+        page.addLine().addUnit("订单号:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getOrderNo(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+//        page.addLine().addUnit("交易单号:" + printRootBean.getMessageContent().getContentData()
+//                .get(0).getTransactionId(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("支付方式:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getPayType(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("下单时间:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getCreatedDate(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("付款时间:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getPayDate(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("油量:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getOrderLiters() + "升", FONT_NORMAL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("油枪:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getOilGun() + "号枪", FONT_NORMAL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("原价:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getReceivableAmt() + "元", FONT_NORMAL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("油品:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getOilName(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("实付:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getAmt() + "元  " + "共优惠:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getDiscountAmt() + "元", FONT_NORMAL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("单价:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getOilPirce() + "元", FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("获得积分:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getIntegral() + "积分", FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("手机号:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getMobilePhone(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("油站名:" + printRootBean.getMessageContent().getContentData()
+                .get(0).getStationName(), FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        page.addLine().addUnit("\n", FONT_SMALL, IImgProcessing.IPage.EAlign.LEFT);
+        IImgProcessing imgProcessing = gl.getImgProcessing();
+        Bitmap bitmap = imgProcessing.pageToBitmap(page, 384);
+        try {
+            iPrinter.init();
+            int gray = 300;
+            iPrinter.setGray(gray);
+            iPrinter.printBitmap(bitmap);
+            int ret = iPrinter.start();
+            if (ret == 0) {
+
+            } else {
+                Toast.makeText(this, getPrintErrorMsg(ret), Toast.LENGTH_SHORT).show();
+            }
+        } catch (PrinterDevException e) {
+            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    // 获取打印状态
+    private String getPrintErrorMsg(int status) {
+        String result;
+        switch (status) {
+            case 2:// 缺纸
+                result = "打印机缺纸";
+                break;
+            case 8:// 打印机过热
+                result = "打印机过热";
+                break;
+            case 9:// 电量低
+                result = "电池电量低";
+                break;
+            default:
+                result = "其它错误";
+        }
+        return result;
+    }
+
+    public void techSpeak(PrintRootBean printRootBean){
+        if (null == mTts) {
+            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
+            String errorMsg="创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化";
+            Log.d(TAG, errorMsg);
+            Toast.makeText(this,errorMsg,Toast.LENGTH_SHORT).show();
+        }
+        String oilGun=printRootBean.getMessageContent().getContentData().get(0).getOilGun();
+        String oilName=printRootBean.getMessageContent().getContentData().get(0).getOilName();
+        String payType=printRootBean.getMessageContent().getContentData().get(0).getPayType();
+        String amt=printRootBean.getMessageContent().getContentData().get(0).getAmt();
+        oilName=oilName.replace("#","");
+        String text = oilGun +"号枪" +oilName +"号油品"+payType+"支付到账"+amt+"元";
+        // 设置参数
+        setParam();
+        int code = mTts.startSpeaking(text, mTtsListener);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG, "语音合成失败,错误码: " + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        }
+    }
+
+    //监听返回键
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
+            backLoginConfirmDialog();
+        }
+        if (null == mTts) {
+            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
+            Log.d(TAG, "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化");
+            return false;
+        }
+        String text = "1号枪92号油品微信支付到账246.52元";
+        // 设置参数
+        setParam();
+        int code = mTts.startSpeaking(text, mTtsListener);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG, "语音合成失败,错误码: " + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        }
+        return false;
+    }
+
+
+    /**
+     * 初始化监听。
+     */
+    private InitListener mTtsInitListener = code -> {
+        Log.d(TAG, "InitListener init() code = " + code);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG, "初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        } else {
+            // 初始化成功,之后可以调用startSpeaking方法
+            // 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
+            // 正确的做法是将onCreate中的startSpeaking调用移至这里
+        }
+    };
+
+    /**
+     * 合成回调监听。
+     */
+    private SynthesizerListener mTtsListener = new SynthesizerListener() {
+
+        @Override
+        public void onSpeakBegin() {
+            Log.d(TAG, "开始播放:" + System.currentTimeMillis());
+        }
+
+        @Override
+        public void onSpeakPaused() {
+            Log.d(TAG, "暂停播放");
+        }
+
+        @Override
+        public void onSpeakResumed() {
+            Log.d(TAG, "继续播放");
+        }
+
+        @Override
+        public void onBufferProgress(int percent, int beginPos, int endPos,
+                                     String info) {
+        }
+
+        @Override
+        public void onSpeakProgress(int percent, int beginPos, int endPos) {
+
+        }
+
+        @Override
+        public void onCompleted(SpeechError error) {
+            if (error == null) {
+                Log.d(TAG, "播放完成");
+            } else if (error != null) {
+                Log.d(TAG, error.getPlainDescription(true));
+            }
+        }
+
+        @Override
+        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
+            // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
+            // 若使用本地能力,会话id为null
+            if (SpeechEvent.EVENT_SESSION_ID == eventType) {
+                String sid = obj.getString(SpeechEvent.KEY_EVENT_AUDIO_URL);
+                Log.d(TAG, "session id =" + sid);
+            }
+        }
+    };
+
+    /**
+     * 参数设置
+     */
+    private void setParam() {
+        // 清空参数
+        mTts.setParameter(SpeechConstant.PARAMS, null);
+        //设置合成
+        if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
+            //设置使用云端引擎
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerCloud);
+
+        } else if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
+            //设置使用本地引擎
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
+            //设置发音人资源路径
+            mTts.setParameter(ResourceUtil.TTS_RES_PATH, getResourcePath());
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerLocal);
+        } else {
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_XTTS);
+            //设置发音人资源路径
+            mTts.setParameter(ResourceUtil.TTS_RES_PATH, getResourcePath());
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerXtts);
+        }
+        //mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY,"1");//支持实时音频流抛出,仅在synthesizeToUri条件下支持
+        //设置合成语速
+        mTts.setParameter(SpeechConstant.SPEED, "50");
+        //设置合成音调
+        mTts.setParameter(SpeechConstant.PITCH, "50");
+        //设置合成音量
+        mTts.setParameter(SpeechConstant.VOLUME, "50");
+        //设置播放器音频流类型
+        mTts.setParameter(SpeechConstant.STREAM_TYPE, "3");
+        //	mTts.setParameter(SpeechConstant.STREAM_TYPE, AudioManager.STREAM_MUSIC+"");
+        // 设置播放合成音频打断音乐播放,默认为true
+        mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
+        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
+        mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
+        mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/tts.wav");
+    }
+
+    //获取发音人资源路径
+    private String getResourcePath() {
+        StringBuffer tempBuffer = new StringBuffer();
+        String type = "tts";
+        if (mEngineType.equals(SpeechConstant.TYPE_XTTS)) {
+            type = "xtts";
+        }
+        //合成通用资源
+        tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/common.jet"));
+        tempBuffer.append(";");
+        //发音人资源
+        if (mEngineType.equals(SpeechConstant.TYPE_XTTS)) {
+            tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/" + MainActivity.voicerXtts + ".jet"));
+        } else {
+            tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/" + MainActivity.voicerLocal + ".jet"));
+        }
+
+        return tempBuffer.toString();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        // 获取从其他Activity发送过来跳转Fragment的标志fragment_flag(名称随意)
+        int fragmentFlag = this.getIntent().getIntExtra("fragment_flag", 0);
+        switch (fragmentFlag) {
+            case 1:
+                // 控制跳转到底部导航项(navigation_home为该Fragment的对应控件的id值)
+                navController.navigate(R.id.navigation_home);
+                break;
+            case 2:
+                navController.navigate(R.id.navigation_order);
+                break;
+            case 3:
+                navController.navigate(R.id.navigation_account);
+                break;
+        }
+        super.onResume();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (null != mTts) {
+            mTts.stopSpeaking();
+            // 退出时释放连接
+            mTts.destroy();
+        }
+    }
+
+}

+ 152 - 0
HandPos/app/src/main/java/com/yijia/handpos/PosApplication.java

@@ -0,0 +1,152 @@
+package com.yijia.handpos;
+
+import android.app.Application;
+import android.content.Context;
+import com.orhanobut.logger.AndroidLogAdapter;
+import com.orhanobut.logger.Logger;
+import com.xuexiang.xaop.XAOP;
+import com.xuexiang.xhttp2.XHttp;
+import com.xuexiang.xhttp2.XHttpSDK;
+import com.xuexiang.xpage.PageConfig;
+import com.xuexiang.xupdate.XUpdate;
+import com.xuexiang.xupdate.utils.UpdateUtils;
+import com.xuexiang.xutil.common.StringUtils;
+import com.xuexiang.xutil.tip.ToastUtils;
+import com.yijia.handpos.http.OKHttpUpdateHttpService;
+import com.zhy.http.okhttp.OkHttpUtils;
+
+import java.util.concurrent.TimeUnit;
+
+import androidx.multidex.MultiDex;
+import okhttp3.OkHttpClient;
+import static com.xuexiang.xupdate.entity.UpdateError.ERROR.CHECK_NO_NEW_VERSION;
+
+/**
+ * 自定义的PosApplication继承Application
+ *
+ * @author suxinghua
+ *
+ */
+
+public class PosApplication extends Application {
+    /**
+     * 引发异常:在一些不规范的代码中经常看到Activity或者是Service当中定义许多静态成员属性。这样做可能会造成许多莫名其妙的 null pointer异常。
+     */
+
+    /**
+     * 异常分析:Java虚拟机的垃圾回收机制会主动回收没有被引用的对象或属性。在内存不足时,虚拟机会主动回收处于后台的Activity或
+     * Service所占用的内存。当应用再次去调用静态属性或对象的时候,就会造成null pointer异常
+     */
+
+    /**
+     * 解决异常:Application在整个应用中,只要进程存在,Application的静态成员变量就不会被回收,不会造成null pointer异常
+     */
+
+    private String host="47.105.116.204";
+    private int port=5672;
+    private String username="root";
+    private String password="admin@123#";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        PageConfig.getInstance()
+                .debug("PageLog")
+                .init(this);
+        // 初始化插件
+        XAOP.init(this);
+        // 日志打印切片开启
+        XAOP.debug(true);
+        //设置动态申请权限切片 申请权限被拒绝的事件响应监听
+        XAOP.setOnPermissionDeniedListener(permissionsDenied ->
+                ToastUtils.toast("权限申请被拒绝:" + StringUtils.listToString(permissionsDenied, ","))
+        );
+        initXHttp();
+        initOKHttpUtils();
+        initUpdate();
+        //Logger日志初始化
+        Logger.addLogAdapter(new AndroidLogAdapter());
+    }
+
+    private void initUpdate() {
+        XUpdate.get()
+                .debug(true)
+                //默认设置只在wifi下检查版本更新
+                .isWifiOnly(false)
+                //默认设置使用get请求检查版本
+                .isGet(true)
+                //默认设置非自动模式,可根据具体使用配置
+                .isAutoMode(false)
+                //设置默认公共请求参数
+                .param("versionCode", UpdateUtils.getVersionCode(this))
+                .param("appKey", getPackageName())
+                //设置版本更新出错的监听
+                .setOnUpdateFailureListener(error -> {
+                    error.printStackTrace();
+                    //对不同错误进行处理
+                    if (error.getCode() != CHECK_NO_NEW_VERSION) {
+                        ToastUtils.toast(error.toString());
+                    }
+                })
+                //设置是否支持静默安装,默认是true
+                .supportSilentInstall(false)
+                //这个必须设置!实现网络请求功能。
+                .setIUpdateHttpService(new OKHttpUpdateHttpService())
+                //这个必须初始化
+                .init(this);
+    }
+
+    private void initXHttp() {
+        //初始化网络请求框架,必须首先执行
+        XHttpSDK.init(this);
+        //需要调试的时候执行
+        XHttpSDK.debug("XHttp");
+        XHttp.getInstance().setTimeout(20000);
+    }
+
+    private void initOKHttpUtils() {
+        OkHttpClient okHttpClient = new OkHttpClient.Builder()
+                .connectTimeout(20000L, TimeUnit.MILLISECONDS)
+                .readTimeout(20000L, TimeUnit.MILLISECONDS)
+                .build();
+        OkHttpUtils.initClient(okHttpClient);
+    }
+
+    @Override
+    protected void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        MultiDex.install(this);
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 85 - 0
HandPos/app/src/main/java/com/yijia/handpos/adapter/MyOrderAdapter.java

@@ -0,0 +1,85 @@
+package com.yijia.handpos.adapter;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.yijia.handpos.R;
+import com.yijia.handpos.pojo.MyOrderBean;
+import com.yijia.handpos.ui.order.OrderDetailActivity;
+
+import java.util.List;
+
+public class MyOrderAdapter extends BaseAdapter {
+    private LayoutInflater mInflater;
+    private Context mContext;
+    private List<MyOrderBean> myOrderBeanList;
+
+    public MyOrderAdapter(Context context, List<MyOrderBean> myOrderBeans) {
+        mInflater = LayoutInflater.from(context);
+        mContext = context;
+        myOrderBeanList = myOrderBeans;
+    }
+
+    @Override
+    public int getCount() {
+        return myOrderBeanList.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return myOrderBeanList.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    //这个方法才是重点,我们要为它编写一个ViewHolder
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder = null;
+        if (convertView == null) {
+            viewHolder = new ViewHolder();
+            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_order, null);
+            viewHolder.payType_TextView = convertView.findViewById(R.id.payType_TextView);
+            viewHolder.payDate_TextView = convertView.findViewById(R.id.payDate_TextView);
+            viewHolder.payAmount_TextView = convertView.findViewById(R.id.payAmount_TextView);
+            viewHolder.payState_TextView=convertView.findViewById(R.id.payState_TextView);
+            viewHolder.payType_ImageView=convertView.findViewById(R.id.payType_ImageView);
+            viewHolder.todetail_imv=convertView.findViewById(R.id.todetail_imv);
+            viewHolder.orderlist_ll=convertView.findViewById(R.id.orderlist_ll);
+            convertView.setTag(viewHolder);
+        } else {//else里面说明,convertView已经被复用了,说明convertView中已经设置过tag了,即holder
+            viewHolder = (ViewHolder) convertView.getTag();
+        }
+        viewHolder.payType_TextView.setText(myOrderBeanList.get(position).getPayType());
+        viewHolder.payDate_TextView.setText(myOrderBeanList.get(position).getPayDate());
+        viewHolder.payAmount_TextView.setText(myOrderBeanList.get(position).getPayAmount());
+        viewHolder.payState_TextView.setText(myOrderBeanList.get(position).getPayState());
+        viewHolder.orderlist_ll.setOnClickListener(v -> {
+            Intent intent = new Intent(v.getContext(), OrderDetailActivity.class);
+            intent.putExtra("myOrderBean",myOrderBeanList.get(position));//传入对象
+            v.getContext().startActivity(intent);
+        });
+        return convertView;
+    }
+
+    //这个ViewHolder只能服务于当前这个特定的adapter,因为ViewHolder里会指定item的控件,不同的ListView,
+    // item可能不同,所以ViewHolder写成一个私有的类
+    private class ViewHolder {
+        TextView payType_TextView;
+        TextView payDate_TextView;
+        TextView payAmount_TextView;
+        TextView payState_TextView;
+        ImageView payType_ImageView;
+        ImageView todetail_imv;
+        LinearLayout orderlist_ll;
+    }
+
+}

+ 88 - 0
HandPos/app/src/main/java/com/yijia/handpos/adapter/OffdutedAdapter.java

@@ -0,0 +1,88 @@
+package com.yijia.handpos.adapter;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.yijia.handpos.R;
+import com.yijia.handpos.pojo.OffdutedItemBean;
+import com.yijia.handpos.ui.work.OffdutyedDetailActivity;
+import com.yijia.handpos.util.ConsUtil;
+
+import java.util.List;
+
+public class OffdutedAdapter extends BaseAdapter {
+    private LayoutInflater mInflater;
+    private Context mContext;
+    private List<OffdutedItemBean> offdutedItemBeans;
+
+    public OffdutedAdapter(Context context, List<OffdutedItemBean> mOffdutedItemBeans) {
+        mInflater = LayoutInflater.from(context);
+        mContext = context;
+        offdutedItemBeans = mOffdutedItemBeans;
+    }
+
+    @Override
+    public int getCount() {
+        return offdutedItemBeans.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return offdutedItemBeans.get(position);
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    //这个方法才是重点,我们要为它编写一个ViewHolder
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder = null;
+        if (convertView == null) {
+            viewHolder = new ViewHolder();
+            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_offdutyed, null);
+            viewHolder.mLinearLayout=convertView.findViewById(R.id.ll_item);
+            viewHolder.mClassNoTextView = convertView.findViewById(R.id.classesNo);
+            viewHolder.mClassManTextView = convertView.findViewById(R.id.classesMan);
+            viewHolder.mEndDateTextView = convertView.findViewById(R.id.endDate);
+            viewHolder.mTodetailImageView = convertView.findViewById(R.id.todetail);
+            convertView.setTag(viewHolder);
+        } else {//else里面说明,convertView已经被复用了,说明convertView中已经设置过tag了,即holder
+            viewHolder = (ViewHolder) convertView.getTag();
+        }
+        viewHolder.mClassNoTextView.setText(offdutedItemBeans.get(position).getClassesNo());
+        viewHolder.mClassManTextView.setText(offdutedItemBeans.get(position).getClassMan());
+        viewHolder.mEndDateTextView.setText(offdutedItemBeans.get(position).getEndDate());
+        viewHolder.mTodetailImageView.setOnClickListener(v -> {
+            Intent intent=new Intent(v.getContext(), OffdutyedDetailActivity.class);
+            ConsUtil.classStructureNo=offdutedItemBeans.get(position).getClassesNo();
+            v.getContext().startActivity(intent);
+        });
+        viewHolder.mLinearLayout.setOnClickListener(v -> {
+            Intent intent=new Intent(v.getContext(), OffdutyedDetailActivity.class);
+            ConsUtil.classStructureNo=offdutedItemBeans.get(position).getClassesNo();
+            v.getContext().startActivity(intent);
+        });
+        return convertView;
+    }
+
+    //这个ViewHolder只能服务于当前这个特定的adapter,因为ViewHolder里会指定item的控件,不同的ListView,
+    // item可能不同,所以ViewHolder写成一个私有的类
+    private class ViewHolder {
+        LinearLayout mLinearLayout;
+        TextView mClassNoTextView;
+        TextView mClassManTextView;
+        TextView mEndDateTextView;
+        ImageView mTodetailImageView;
+    }
+
+}

+ 65 - 0
HandPos/app/src/main/java/com/yijia/handpos/custom/CustomUpdateParser.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.custom;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xupdate.entity.UpdateEntity;
+import com.xuexiang.xupdate.listener.IUpdateParseCallback;
+import com.xuexiang.xupdate.proxy.IUpdateParser;
+import com.yijia.handpos.entity.CustomResult;
+import com.xuexiang.xutil.net.JsonUtil;
+
+/**
+ * 自定义更新解析器
+ *
+ * @author xuexiang
+ * @since 2018/7/12 下午3:46
+ */
+public class CustomUpdateParser implements IUpdateParser {
+    @Override
+    public UpdateEntity parseJson(String json) throws Exception {
+        return getParseResult(json);
+    }
+
+    private UpdateEntity getParseResult(String json) {
+        CustomResult result = JsonUtil.fromJson(json, CustomResult.class);
+        if (result != null) {
+            return new UpdateEntity()
+                    .setHasUpdate(result.hasUpdate)
+                    .setIsIgnorable(result.isIgnorable)
+                    .setVersionCode(result.versionCode)
+                    .setVersionName(result.versionName)
+                    .setUpdateContent(result.updateLog)
+                    .setDownloadUrl(result.apkUrl)
+                    .setSize(result.apkSize);
+        }
+        return null;
+    }
+
+    @Override
+    public void parseJson(String json, @NonNull IUpdateParseCallback callback) throws Exception {
+        //当isAsyncParser为 true时调用该方法, 所以当isAsyncParser为false可以不实现
+        callback.onParseResult(getParseResult(json));
+    }
+
+
+    @Override
+    public boolean isAsyncParser() {
+        return false;
+    }
+}

+ 105 - 0
HandPos/app/src/main/java/com/yijia/handpos/custom/CustomUpdatePrompter.java

@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.xuexiang.xupdatedemo.custom;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xupdate.entity.PromptEntity;
+import com.xuexiang.xupdate.entity.UpdateEntity;
+import com.xuexiang.xupdate.proxy.IUpdatePrompter;
+import com.xuexiang.xupdate.proxy.IUpdateProxy;
+import com.xuexiang.xupdate.service.OnFileDownloadListener;
+import com.xuexiang.xupdate.utils.UpdateUtils;
+import com.yijia.handpos.util.HProgressDialogUtils;
+
+import java.io.File;
+
+/**
+ * 自定义版本更新提示器
+ *
+ * @author xuexiang
+ * @since 2018/7/12 下午3:48
+ */
+public class CustomUpdatePrompter implements IUpdatePrompter {
+
+    /**
+     * 显示自定义提示
+     *
+     * @param updateEntity
+     * @param updateProxy
+     */
+    private void showUpdatePrompt(final @NonNull UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) {
+        String updateInfo = UpdateUtils.getDisplayUpdateInfo(updateProxy.getContext(), updateEntity);
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(updateProxy.getContext())
+                .setTitle(String.format("是否升级到%s版本?", updateEntity.getVersionName()))
+                .setMessage(updateInfo)
+                .setPositiveButton("升级", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        updateProxy.startDownload(updateEntity, new OnFileDownloadListener() {
+                            @Override
+                            public void onStart() {
+                                HProgressDialogUtils.showHorizontalProgressDialog(updateProxy.getContext(), "下载进度", false);
+                            }
+
+                            @Override
+                            public void onProgress(float progress, long total) {
+                                HProgressDialogUtils.setProgress(Math.round(progress * 100));
+                            }
+
+                            @Override
+                            public boolean onCompleted(File file) {
+                                HProgressDialogUtils.cancel();
+                                return true;
+                            }
+
+                            @Override
+                            public void onError(Throwable throwable) {
+                                HProgressDialogUtils.cancel();
+                            }
+                        });
+                    }
+                });
+        if (updateEntity.isIgnorable()) {
+            builder.setNegativeButton("暂不升级", new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    UpdateUtils.saveIgnoreVersion(updateProxy.getContext(), updateEntity.getVersionName());
+                }
+            }).setCancelable(true);
+        } else  {
+            builder.setCancelable(false);
+        }
+        builder.create().show();
+    }
+
+    /**
+     * 显示版本更新提示
+     *
+     * @param updateEntity 更新信息
+     * @param updateProxy  更新代理
+     * @param promptEntity 提示界面参数
+     */
+    @Override
+    public void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) {
+        showUpdatePrompt(updateEntity, updateProxy);
+    }
+}

+ 115 - 0
HandPos/app/src/main/java/com/yijia/handpos/custom/XUpdateServiceParser.java

@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.custom;
+
+import com.xuexiang.xupdate.XUpdate;
+import com.xuexiang.xupdate.entity.CheckVersionResult;
+import com.xuexiang.xupdate.entity.UpdateEntity;
+import com.xuexiang.xupdate.proxy.IUpdateHttpService;
+import com.xuexiang.xupdate.proxy.impl.AbstractUpdateParser;
+import com.xuexiang.xupdate.utils.UpdateUtils;
+import com.yijia.handpos.entity.ApiResult;
+import com.yijia.handpos.entity.AppVersionInfo;
+import com.yijia.handpos.http.XHttpUpdateHttpService;
+import com.yijia.handpos.util.SettingSPUtils;
+import com.xuexiang.xutil.net.JsonUtil;
+import com.xuexiang.xutil.net.type.TypeBuilder;
+
+import java.lang.reflect.Type;
+
+/**
+ * @author xuexiang
+ * @since 2018/7/30 下午12:00
+ */
+public class XUpdateServiceParser extends AbstractUpdateParser {
+    @Override
+    public UpdateEntity parseJson(String json) throws Exception {
+        ApiResult<AppVersionInfo> apiResult = JsonUtil.fromJson(json, getApiResultType(AppVersionInfo.class));
+        if (apiResult != null) {
+            AppVersionInfo appVersionInfo = doLocalCompare(apiResult.getData());
+
+            UpdateEntity updateEntity = new UpdateEntity();
+            if (appVersionInfo.getUpdateStatus() == CheckVersionResult.NO_NEW_VERSION) {
+                updateEntity.setHasUpdate(false);
+            } else {
+                if (appVersionInfo.getUpdateStatus() == CheckVersionResult.HAVE_NEW_VERSION_FORCED_UPLOAD) {
+                    updateEntity.setForce(true);
+                }
+                updateEntity.setHasUpdate(true)
+                        //兼容一下
+                        .setUpdateContent(appVersionInfo.getModifyContent().replaceAll("\\\\r\\\\n", "\r\n"))
+                        .setVersionCode(appVersionInfo.getVersionCode())
+                        .setVersionName(appVersionInfo.getVersionName())
+                        .setDownloadUrl(getDownLoadUrl(appVersionInfo.getDownloadUrl()))
+                        .setSize(appVersionInfo.getApkSize())
+                        .setMd5(appVersionInfo.getApkMd5());
+            }
+            return updateEntity;
+        }
+        return null;
+    }
+
+    /**
+     * 为请求的返回类型加上ApiResult包装类
+     * @param type
+     * @return
+     */
+    public static Type getApiResultType(Type type) {
+        return TypeBuilder
+                .newInstance(ApiResult.class)
+                .addTypeParam(type)
+                .build();
+    }
+
+    /**
+     * 进行本地版本判断[防止服务端出错,本来是不需要更新,但是服务端返回是需要更新]
+     *
+     * @param appVersionInfo
+     * @return
+     */
+    private AppVersionInfo doLocalCompare(AppVersionInfo appVersionInfo) {
+        if (appVersionInfo.getUpdateStatus() != CheckVersionResult.NO_NEW_VERSION) { //服务端返回需要更新
+            int lastVersionCode = appVersionInfo.getVersionCode();
+            if (lastVersionCode <= UpdateUtils.getVersionCode(XUpdate.getContext())) { //最新版本小于等于现在的版本,不需要更新
+                appVersionInfo.setUpdateStatus(CheckVersionResult.NO_NEW_VERSION);
+            }
+        }
+        return appVersionInfo;
+    }
+
+    public static String getVersionCheckUrl() {
+        String baseUrl = SettingSPUtils.get().getServiceURL();
+        if (baseUrl.endsWith("/")) {
+            return baseUrl + "update/checkVersion";
+        } else {
+            return baseUrl + "/update/checkVersion";
+        }
+    }
+
+    public static String getDownLoadUrl(String url) {
+        String baseUrl = SettingSPUtils.get().getServiceURL();
+        if (baseUrl.endsWith("/")) {
+            return baseUrl + "update/apk/" + url;
+        } else {
+            return baseUrl + "/update/apk/" + url;
+        }
+    }
+
+    public static IUpdateHttpService getUpdateHttpService() {
+        return new XHttpUpdateHttpService(SettingSPUtils.get().getServiceURL());
+    }
+}

+ 117 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/ISysteHelper.java

@@ -0,0 +1,117 @@
+package com.yijia.handpos.device;
+
+import android.content.Context;
+
+import com.yijia.handpos.device.print.PrintBean;
+import com.yijia.handpos.device.print.PrintCallBack;
+import com.yijia.handpos.device.scan.ScanResultListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
+
+import androidx.annotation.IntDef;
+
+/**
+ * 系统工具方法
+ */
+public interface ISysteHelper {
+
+    /**
+     * 需要的  Application 里面做的动作
+     *
+     * @param context 上下文
+     * @return
+     */
+    void initOnApplication(Context context);
+
+    /**
+     * 使用TDK对数据进行CBC加密
+     *
+     * @param data 加密数据
+     * @return
+     */
+    String unionPayStandardCBC(byte[] data);
+
+    /**
+     * 使用TDK对数据进行ECB加密
+     *
+     * @param data 加密数据
+     * @return
+     */
+    byte[] unionPayStandardECB(byte[] data);
+
+
+    /**
+     * 下载主密钥明文
+     *
+     * @param planKey
+     * @return
+     */
+    int loadPlantKey(String planKey);
+
+
+    /**
+     * 下载工作密钥
+     *
+     * @param type       密钥类型
+     * @param workKey    密钥密文
+     * @param checkValue
+     * @return
+     */
+    int loadWorkKey(@KeyType int type, String workKey, String checkValue);
+
+
+    /**
+     * 获取设备信息
+     *
+     * @param cxt
+     * @return
+     */
+    Map<String, String> getDevice(Context cxt);
+
+    /**
+     * 打印小票
+     *
+     * @param context
+     * @param beans
+     * @param callback
+     */
+    void print(Context context, List<PrintBean> beans, PrintCallBack callback);
+
+    /**
+     * 开启扫码
+     */
+    void scan(ScanResultListener scanResultListener);
+
+    /**
+     * 更新系统时间
+     *
+     * @param time
+     * @return
+     */
+    boolean updateTime(Context context, long time);
+
+    /**
+     * 密钥类型
+     * PLANTTEXT    主密钥
+     * PINKEY    PIN密钥
+     * TDKEY    磁道密钥
+     * MACKET    mac密钥
+     */
+    @IntDef({KeyType.PLANTTEXT, KeyType.PINKEY, KeyType.TDKEY, KeyType.MACKET})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface KeyType {
+        // 主密钥
+        int PLANTTEXT = 1;
+        // PIN密码
+        int PINKEY = 2;
+        // 磁道密钥
+        int TDKEY = 3;
+        // mac密钥
+        int MACKET = 4;
+    }
+
+
+}

+ 377 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/SysteHelper.java

@@ -0,0 +1,377 @@
+package com.yijia.handpos.device;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.google.zxing.BarcodeFormat;
+import com.pax.dal.IDAL;
+import com.pax.dal.IPed;
+import com.pax.dal.IPrinter;
+import com.pax.dal.IScanner;
+import com.pax.dal.entity.ECheckMode;
+import com.pax.dal.entity.EPedDesMode;
+import com.pax.dal.entity.EPedKeyType;
+import com.pax.dal.entity.EPedType;
+import com.pax.dal.entity.EScannerType;
+import com.pax.dal.entity.ETermInfoKey;
+import com.pax.dal.exceptions.PedDevException;
+import com.pax.dal.exceptions.PrinterDevException;
+import com.pax.eemv.EmvImpl;
+import com.pax.eemv.IEmv;
+import com.pax.gl.IGL;
+import com.pax.gl.convert.IConvert;
+import com.pax.gl.imgprocessing.IImgProcessing;
+import com.pax.gl.imgprocessing.IImgProcessing.IPage.EAlign;
+import com.pax.gl.impl.GLProxy;
+import com.pax.neptunelite.api.NeptuneLiteUser;
+import com.yijia.handpos.device.constants.PrintDefine;
+import com.yijia.handpos.device.print.PrintBean;
+import com.yijia.handpos.device.print.PrintCallBack;
+import com.yijia.handpos.device.scan.ScanResultListener;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 设备系统帮助类
+ */
+public class SysteHelper implements ISysteHelper {
+
+    private static String LOG_TAG = "SysteHelper";
+
+    // 主密钥索引
+    private final static byte TMK_INDEX = 11;
+    // 磁道密钥索引
+    public final static byte TDK_INDEX = 1;
+    // PIN密码索引
+    public final static byte PIK_INDEX = 3;
+    // MAC密钥索引
+    public final static byte MAK_INDEX = 5;
+
+    // 获取IPPI常用接口
+    public static IDAL dal;
+    public static IGL gl;
+    public static IEmv emv;
+    public static IConvert convert;
+
+    private SysteHelper() {
+    }
+
+    private static final SysteHelper instance = new SysteHelper();
+
+    public static SysteHelper getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void initOnApplication(Context context) {
+        Log.d(LOG_TAG, "SysteHelper.initOnApplication >>>");
+        if (dal != null)
+            return;
+        try {
+            dal = NeptuneLiteUser.getInstance().getDal(context);
+            emv = EmvImpl.getInstance(context);
+            gl = new GLProxy(context).getGL();
+            convert = gl.getConvert();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public String unionPayStandardCBC(byte[] data) {
+        Log.d(LOG_TAG, "SysteHelper.unionPayStandardCBC >>> track:" + SysteHelper.convert.bcdToStr(data));
+        byte[] enData = calcDesCBC(TDK_INDEX, data, null);
+        return SysteHelper.convert.bcdToStr(enData);
+    }
+
+    @Override
+    public byte[] unionPayStandardECB(byte[] data) {
+        Log.d(LOG_TAG, "SysteHelper.unionPayStandardECB >>> data:" + SysteHelper.convert.bcdToStr(data));
+        byte[] enData = calcDesECB(TDK_INDEX, data, (byte) 0x00);
+        return enData;
+    }
+
+    @Override
+    public int loadPlantKey(String planKey) {
+        Log.d(LOG_TAG, "SysteHelper.loadPlantKey >>>");
+        try {
+            getPed().writeKey(EPedKeyType.TMK, (byte) 0, EPedKeyType.TMK, TMK_INDEX,
+                    SysteHelper.convert.strToBcd(planKey, IConvert.EPaddingPosition.PADDING_LEFT),
+                    ECheckMode.KCV_NONE, null);
+            return 0;
+        } catch (PedDevException e) {
+            e.printStackTrace();
+            return e.getErrCode();
+        }
+    }
+
+    @Override
+    public int loadWorkKey(int type, String workKey, String checkValue) {
+        Log.d(LOG_TAG, "SysteHelper.loadWorkKey >>> type:" + type
+                + " workKey:" + workKey + " checkValue:" + checkValue);
+        byte keyIndex = 0;
+        EPedKeyType pedKeyType = EPedKeyType.TDK;
+        switch (type) {
+            case KeyType.MACKET:
+                keyIndex = MAK_INDEX;
+                pedKeyType = EPedKeyType.TAK;
+                break;
+            case KeyType.PINKEY:
+                keyIndex = PIK_INDEX;
+                pedKeyType = EPedKeyType.TPK;
+                break;
+            case KeyType.TDKEY:
+                keyIndex = TDK_INDEX;
+                pedKeyType = EPedKeyType.TDK;
+                break;
+            default:
+                break;
+        }
+        try {
+            getPed().writeKey(EPedKeyType.TMK, TMK_INDEX, pedKeyType, keyIndex,
+                    SysteHelper.convert.strToBcd(workKey, IConvert.EPaddingPosition.PADDING_LEFT),
+                    ECheckMode.KCV_ENCRYPT_0, SysteHelper.convert.strToBcd(checkValue.substring(0, 8),
+                            IConvert.EPaddingPosition.PADDING_LEFT));
+            return 0;
+        } catch (PedDevException e) {
+            e.printStackTrace();
+            return e.getErrCode();
+        }
+    }
+
+    @Override
+    public Map<String, String> getDevice(Context cxt) {
+        Log.d(LOG_TAG, "SysteHelper.getDevice >>>");
+        String sn = "";
+        Map<String, String> deviceInfo = new HashMap<String, String>();
+        try {
+            sn = SysteHelper.dal.getSys().getTermInfo().get(ETermInfoKey.SN);
+            String tusn = SysteHelper.dal.getSys().readTUSN();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        deviceInfo.put("SN", sn);
+        return deviceInfo;
+    }
+
+    private static final int FONT_BIG = 30;// 大字体
+    private static final int FONT_NORMAL = 24;// 普通字体
+    private static final int FONT_SMALL = 20;// 小字体
+
+    private static final String FONT_PATH_A920_D800 = "/system/fonts/";
+    private static final String FONT_PATH_A920C = "/data/resource/font/";
+    private static final String FONT = "Fangsong.ttf";
+
+    @Override
+    public void print(Context context, List<PrintBean> beans, PrintCallBack callback) {
+        Log.d(LOG_TAG, "SysteHelper.print >>>");
+        IImgProcessing.IPage page = SysteHelper.gl.getImgProcessing().createPage();
+        // 把打印机默认字库设置成仿宋
+        if (new File(FONT_PATH_A920_D800 + FONT).exists()) {
+            page.setTypeFace(FONT_PATH_A920_D800 + FONT);
+        } else if (new File(FONT_PATH_A920C + FONT).exists()) {
+            page.setTypeFace(FONT_PATH_A920C + FONT);
+        }
+        for (int i = 0; i < beans.size(); i++) {
+            PrintBean printBean = beans.get(i);
+            Log.d(LOG_TAG, "Type:" + printBean.getType() + " Text:" + printBean.getTxt()
+                    + " Align:" + printBean.getAlign() + " FontSize:" + printBean.getFontSize());
+
+            switch (printBean.getType()) {
+                case PrintDefine.Type.BITMAP:// 图片
+                    page.addLine().addUnit(printBean.getDrawBitmap(), EAlign.CENTER);
+                    break;
+                case PrintDefine.Type.BAR_CODE:// 条码
+                    page.addLine().addUnit(SysteHelper.gl.getImgProcessing().generateBarCode(printBean.getTxt(), 400,
+                            80, BarcodeFormat.CODE_128), EAlign.CENTER);
+                    break;
+                case PrintDefine.Type.QR_CODE:// 二维码
+                    page.addLine().addUnit(SysteHelper.gl.getImgProcessing().generateBarCode(printBean.getTxt(), 230,
+                            230, BarcodeFormat.QR_CODE), EAlign.CENTER);
+                    break;
+                case PrintDefine.Type.BLANK_LINE:// 空行
+                    page.addLine().addUnit("\n", FONT_NORMAL);
+                    break;
+                case PrintDefine.Type.END:// 走纸
+                    page.addLine().addUnit("\n\n\n", FONT_NORMAL);
+                    break;
+                case PrintDefine.Type.LINE:// 行
+                    page.addLine().addUnit("--------------------------------", FONT_NORMAL);
+                    break;
+                default:
+                    page.addLine().addUnit(printBean.getTxt(), setFrontSize(printBean.getFontSize()), EAlign.LEFT);
+                    break;
+            }
+        }
+
+        IImgProcessing imgProcessing = SysteHelper.gl.getImgProcessing();
+        Bitmap bitmap = imgProcessing.pageToBitmap(page, 384);// 打印宽度
+        IPrinter printer = SysteHelper.dal.getPrinter();
+        try {
+            printer.init();
+            printer.setGray(240);// 字体灰度 1-500
+            printer.printBitmap(bitmap);
+            printer.spaceSet((byte) 0x20, (byte) 0x00);
+            int ret = printer.start();
+            if (ret == 0) {
+                // 打印完毕
+                callback.result("0", "打印完成");
+            } else {
+                callback.result(String.valueOf(ret), getPrintErrorMsg(ret));
+            }
+        } catch (PrinterDevException e) {
+            e.printStackTrace();
+            callback.result(String.valueOf(e.getErrCode()), e.getMessage());
+        }
+    }
+
+    // 扫到的内容
+    private String qrCode = null;
+
+    @Override
+    public void scan(final ScanResultListener scanResult) {
+        qrCode = null;
+        EScannerType scannerType = EScannerType.valueOf("REAR");// REAR:后置  FRONT:前置
+        final IScanner scanner = SysteHelper.dal.getScanner(scannerType);
+        scanner.close(); // 系统扫码崩溃之后,再调用掉不起来
+        scanner.open();
+        scanner.start(new IScanner.IScanListener() {
+            @Override
+            public void onCancel() {
+            }
+
+            @Override
+            public void onFinish() {
+                scanner.close();
+                if (qrCode != null && qrCode.length() > 0) {
+                    scanResult.success(qrCode);
+                } else {
+                    scanResult.fail("扫码失败");
+                }
+            }
+
+            @Override
+            public void onRead(String content) {
+                qrCode = content;
+            }
+        });
+    }
+
+    // 获取打印状态
+    private String getPrintErrorMsg(int status) {
+        String result;
+        switch (status) {
+            case 2:// 缺纸
+                result = "缺纸";
+                break;
+            case 8:// 打印机过热
+                result = "打印机过热";
+                break;
+            case 9:// 电量低
+                result = "电量低";
+                break;
+            default:
+                result = "其它错误";
+        }
+        Log.d(LOG_TAG, "SysteHelper.getPrintErrorMsg >>> result:" + result);
+        return result;
+    }
+
+    @SuppressLint("SimpleDateFormat")
+    @Override
+    public boolean updateTime(Context context, long time) {
+        Log.d(LOG_TAG, "SysteHelper.upDateTime >>>");
+        SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
+        String curTime = sdf.format(new Date(time));
+        try {
+            SysteHelper.dal.getSys().setDate(curTime);// 设置系统时间
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    private int setFrontSize(String font) {
+        int fontSize = FONT_NORMAL;
+        if (font.equals(PrintDefine.Font.SMA)) {
+            fontSize = FONT_SMALL;
+        } else if (font.equals(PrintDefine.Font.MED)) {
+            fontSize = FONT_NORMAL;
+        } else if (font.equals(PrintDefine.Font.BIG)) {
+            fontSize = FONT_BIG;
+        }
+        return fontSize;
+    }
+
+    private EAlign setALign(String align) {
+        EAlign eAlign = EAlign.LEFT;
+        if (align.equals(PrintDefine.Align.CENTER)) {
+            eAlign = EAlign.CENTER;
+        } else if (align.equals(PrintDefine.Align.RIGHT)) {
+            eAlign = EAlign.CENTER;
+        }
+        return eAlign;
+    }
+
+    private byte[] calcDesECB(byte keyIdx, byte[] data, byte fill) {
+        byte[] ecb = new byte[((data.length + 7) / 8) * 8];
+        Arrays.fill(ecb, fill);
+        System.arraycopy(data, 0, ecb, 0, data.length);
+        byte[] desdata = null;
+        try {
+            desdata = getPed().calcDes(keyIdx, ecb, EPedDesMode.ENCRYPT);
+        } catch (PedDevException e) {
+            e.printStackTrace();
+        }
+
+        return desdata;
+    }
+
+    private byte[] calcDesCBC(byte keyIdx, byte data[], byte[] ivdata) {
+        byte[] iv = new byte[8];
+        if (ivdata == null || ivdata.length == 0) {
+            Arrays.fill(iv, (byte) 0x00);
+        } else {
+            System.arraycopy(ivdata, 0, iv, 0, Math.min(ivdata.length, 8));
+        }
+        ByteArrayOutputStream ons = new ByteArrayOutputStream();
+        byte[] calcdata = new byte[((data.length + 7) / 8) * 8];
+        Arrays.fill(calcdata, (byte) 0x00);
+        System.arraycopy(data, 0, calcdata, 0, data.length);
+        for (int i = 0; i < calcdata.length; ) {
+            byte[] xordata = new byte[8];
+            for (int j = 0; j < 8; j++)
+                xordata[j] = (byte) (calcdata[i + j] ^ iv[j]);
+            try {
+                iv = getPed().calcDes(keyIdx, xordata, EPedDesMode.ENCRYPT);
+            } catch (PedDevException e) {
+                e.printStackTrace();
+                return null;
+            }
+            try {
+                ons.write(iv);
+            } catch (IOException e) {
+                e.printStackTrace();
+                return null;
+            }
+            i += 8;
+        }
+        return ons.toByteArray();
+    }
+
+    public static IPed getPed() {
+        return SysteHelper.dal.getPed(EPedType.INTERNAL);
+    }
+
+}

+ 106 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/constants/FieldDefinition.java

@@ -0,0 +1,106 @@
+package com.yijia.handpos.device.constants;
+
+/**
+ * 交易字段定义
+ */
+public interface FieldDefinition {
+    /**
+     * 消费
+     */
+    String CONSUME = "1";
+    /**
+     * 消费撤消
+     */
+    String CONSUME_CANCEL = "2";
+    /**
+     * 预授权正向
+     */
+    String CONSUME_PRE_AUTH = "3";
+    /**
+     * 预授权撤销
+     */
+    String CONSUME_PRE_AUTH_CANCEL = "4";
+    /**
+     * 预授权完成
+     */
+    String CONSUME_PRE_AUTH_COMPLETE = "5";
+    /**
+     * 预授权完成撤销
+     */
+    String CONSUME_PRE_AUTH_COMPLETE_CANCEL = "6";
+
+
+    /**
+     * 刷卡 刷卡定义码
+     * SWIPE_SWIPTE 刷卡
+     * SWIPE_INSERT 插卡
+     * SWIPE_NON_CONTACTED 非接
+     * SWIPE_NOT 预授权类不进行卡交互
+     */
+    String SWIPE_SWIPE = "02";
+    String SWIPE_INSERT = "05";
+    String SWIPE_NON_CONTACTED = "07";
+    String SWIPE_NOT = "01";
+
+    /**
+     * 刷卡方式
+     */
+    String SWIPE_MODE = "swipe_mode";
+
+
+    /**
+     * 交易金额
+     */
+    String MONEY = "money";
+
+    /**
+     * 卡号
+     */
+    String CARD_NO = "card_no";
+    /**
+     * 磁道1
+     */
+    String TRACK1 = "track1";
+
+
+    /**
+     * 磁道2
+     */
+    String TRACK2 = "track2";
+
+    /**
+     * 磁道2 标准加密
+     */
+    String TRACK2_ENC = "TRACK2_ENC";
+    /**
+     * 磁道3
+     */
+    String TRACK3 = "track3";
+    /**
+     * 磁道3 标准加密
+     */
+    String TRACK3_ENC = "TRACK3_ENC";
+    /**
+     * 有效期
+     */
+    String EXPIRT_DATE = "expirt_date";
+    /**
+     * 序列号
+     */
+    String SERIAL = "serial";
+    /**
+     * f55数据
+     */
+    String ICDATA = "iccarddata";
+    /**
+     * 密码
+     */
+    String PASSWORD = "password";
+
+    /**
+     * 备注信息
+     */
+    String REMARK = "remark";
+
+
+}

+ 46 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/constants/PrintDefine.java

@@ -0,0 +1,46 @@
+package com.yijia.handpos.device.constants;
+
+/**
+ * 打印机定义
+ */
+public class PrintDefine {
+    //类型
+    public static class Type {
+        //文字内容(自动换行)
+        public static final String TEXT = "text";
+        //打印默认分割线 (---------------------------------)
+        public static final String LINE = "line";
+        //结尾
+        public static final String END = "end";
+        //空行内容
+        public static final String BLANK_LINE = "blank_line";
+        //条码
+        public static final String BAR_CODE = "bar_code";
+        //二维码
+        public static final String QR_CODE = "qr_code";
+        //绘制图片
+        public static final String BITMAP = "bitmap";
+    }
+
+    //字体大小
+    public static class Font {
+        //小
+        public static final String SMA = "1";
+        //中
+        public static final String MED = "2";
+        //大
+        public static final String BIG = "3";
+    }
+
+    //打印位置
+    public static class Align {
+        //打印位置-右
+        public static final String RIGHT = "0";
+        //打印位置-中
+        public static final String CENTER = "1";
+        //打印位置-左
+        public static final String LEFT = "2";
+    }
+
+
+}

+ 202 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvAid.java

@@ -0,0 +1,202 @@
+package com.yijia.handpos.device.emv;
+
+import com.pax.cocoa.tools.Convert;
+import com.pax.cocoa.tools.Tlv;
+import com.pax.cocoa.tools.entity.TlvCell;
+import com.pax.eemv.entity.AidParam;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class EmvAid {
+
+    public List<AidParam> loadAllAid() {
+        String[] aidArray = new String[]{
+                "9F0607A0000000031020DF0101009F0802008CDF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000012000",
+                "9F0607A0000000031010DF0101009F0802008CDF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000012000",
+                "9F0608A000000003101001DF0101009F0802008CDF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000012000",
+                "9F0608A000000003101002DF0101009F0802008CDF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000012000",
+                "9F0607A0000000032010DF0101009F0802008CDF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000012000",
+                "9F0607A0000000044010DF0101009F08020002DF1105FC50ACA000DF1205F850ACF800DF130504000000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000030000",
+                "9F0607A0000000041010DF0101009F08020002DF1105FC50ACA000DF1205F850ACF800DF130504000000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000030000",
+                "9F0607A0000000043060DF0101009F08020002DF1105FC50ACA000DF1205F850ACF800DF130504000000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000030000",
+                "9F0607A0000000651010DF0101009F08020200DF1105FC6024A800DF1205FC60ACF800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100",
+                "9F0606A00000002501DF0101009F08020001DF1105DC50FC9800DF1205DE00FC9800DF130500100000009F1B0400000000DF150400000000DF160100DF170100DF14039F3704DF180100DF1906000000000000DF2006999999999999DF2106000000030000",
+                "9F0608A000000333010101DF0101009F08020020DF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160199DF170199DF14039F3704DF1801019F7B06000000100000DF1906000000100000DF2006999999999999DF2106000000100000",
+                "9F0608A000000333010102DF0101009F08020020DF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160199DF170199DF14039F3704DF1801019F7B06000000100000DF1906000000100000DF2006999999999999DF2106000000100000",
+                "9F0608A000000333010103DF0101009F08020020DF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160199DF170199DF14039F3704DF1801019F7B06000000100000DF1906000000100000DF2006999999999999DF2106000000100000",
+                "9F0608A000000333010106DF0101009F08020020DF1105D84000A800DF1205D84004F800DF130500100000009F1B0400000000DF150400000000DF160199DF170199DF14039F3704DF1801019F7B06000000100000DF1906000000100000DF2006999999999999DF2106000000100000",};
+
+        List<AidParam> aidParams = new ArrayList<>();
+        for (int i = 0; i < aidArray.length; i++) {
+            AidParam aidParam = tlvToAidParam(aidArray[i]);
+            if (aidParam != null) {
+                aidParams.add(aidParam);
+            }
+        }
+        return aidParams;
+    }
+
+
+    private AidParam tlvToAidParam(String tlvAid) {
+        if (tlvAid == null || tlvAid.length() == 0) {
+            return null;
+        }
+        byte[] value = null;
+
+        Map<Integer, TlvCell> aidTlvList = Tlv.unpack(Convert.strToBcdBytes(tlvAid, true));
+        AidParam aidParam = new AidParam();
+        TlvCell tlvCell = null;
+
+        // 9f06 AID
+        tlvCell = aidTlvList.get(0x9f06);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setAid(value);
+            }
+        }
+
+        // DF01
+        tlvCell = aidTlvList.get(0xDF01);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setSelFlag(value[0]);
+            }
+        }
+
+        // 9F08
+        tlvCell = aidTlvList.get(0x9f08);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setVersion(value);
+            }
+        }
+
+        // DF11
+        tlvCell = aidTlvList.get(0xDF11);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setTacDefault(value);
+            }
+        }
+
+        // DF12
+        tlvCell = aidTlvList.get(0xDF12);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setTacOnline(value);
+            }
+        }
+
+        // DF13
+        tlvCell = aidTlvList.get(0xDF13);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setTacDenial(value);
+            }
+        }
+
+        // 9F1B
+        tlvCell = aidTlvList.get(0x9F1B);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setFloorLimit(Long.parseLong(Convert.bcdBytesToStr(value), 16));
+                aidParam.setFloorLimitCheck((byte) 1);
+            }
+        }
+
+        // DF15
+        tlvCell = aidTlvList.get(0xDF15);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setThreshold(Long.parseLong(Convert.bcdBytesToStr(value), 16));
+            }
+        }
+
+        // DF16
+        tlvCell = aidTlvList.get(0xDF16);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setMaxTargetPer(value[0]);
+            }
+        }
+
+        // DF17
+        tlvCell = aidTlvList.get(0xDF17);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setTargetPer(value[0]);
+            }
+        }
+
+        // DF14
+        tlvCell = aidTlvList.get(0xDF14);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setdDol(value);
+            }
+        }
+
+        // DF18
+        tlvCell = aidTlvList.get(0xDF18);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setOnlinePin((byte) (value[0] & 0x01));
+            }
+        }
+
+        // 9F7B
+        tlvCell = aidTlvList.get(0x9F7B);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setEcTTLVal(Long.parseLong(Convert.bcdBytesToStr(value)));
+                aidParam.setEcTTLFlag((byte) 1);
+            }
+        }
+
+        // DF19
+        tlvCell = aidTlvList.get(0xDF19);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setRdClssFLmt(Long.parseLong(Convert.bcdBytesToStr(value)));
+                aidParam.setRdClssFLmtFlag((byte) 1);
+            }
+        }
+
+        // DF20
+        tlvCell = aidTlvList.get(0xDF20);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setRdClssTxnLmt(Long.parseLong(Convert.bcdBytesToStr(value)));
+                aidParam.setRdClssTxnLmtFlag((byte) 1);
+            }
+        }
+
+        // DF21
+        tlvCell = aidTlvList.get(0xDF21);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                aidParam.setRdCVMLmt(Long.parseLong(Convert.bcdBytesToStr(value)));
+                aidParam.setRdCVMLmtFlag((byte) 1);
+            }
+        }
+        return aidParam;
+    }
+}

+ 136 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvCapk.java

@@ -0,0 +1,136 @@
+package com.yijia.handpos.device.emv;
+
+import com.pax.cocoa.tools.Convert;
+import com.pax.cocoa.tools.Tlv;
+import com.pax.cocoa.tools.entity.TlvCell;
+import com.pax.eemv.entity.Capk;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class EmvCapk {
+
+    public List<Capk> loadAllCapk() {
+        String[] capkArray = new String[]{
+                "9F0605A0000000039F220108DF050420241231DF060100DF070100DF0281B0D9FD6ED75D51D0E30664BD157023EAA1FFA871E4DA65672B863D255E81E137A51DE4F72BCC9E44ACE12127F87E263D3AF9DD9CF35CA4A7B01E907000BA85D24954C2FCA3074825DDD4C0C8F186CB020F683E02F2DEAD3969133F06F7845166ACEB57CA0FC2603445469811D293BFEFBAFAB57631B3DD91E796BF850A25012F1AE38F05AA5C4D6D03B1DC2E568612785938BBC9B3CD3A910C1DA55A5A9218ACE0F7A21287752682F15832A678D6E1ED0BDF040103DF031420D213126955DE205ADC2FD2822BD22DE21CF9A8",
+                "9F0605A0000000039F220109DF050420271231DF060100DF070100DF0281F89D912248DE0A4E39C1A7DDE3F6D2588992C1A4095AFBD1824D1BA74847F2BC4926D2EFD904B4B54954CD189A54C5D1179654F8F9B0D2AB5F0357EB642FEDA95D3912C6576945FAB897E7062CAA44A4AA06B8FE6E3DBA18AF6AE3738E30429EE9BE03427C9D64F695FA8CAB4BFE376853EA34AD1D76BFCAD15908C077FFE6DC5521ECEF5D278A96E26F57359FFAEDA19434B937F1AD999DC5C41EB11935B44C18100E857F431A4A5A6BB65114F174C2D7B59FDF237D6BB1DD0916E644D709DED56481477C75D95CDD68254615F7740EC07F330AC5D67BCD75BF23D28A140826C026DBDE971A37CD3EF9B8DF644AC385010501EFC6509D7A41DF040103DF03141FF80A40173F52D7D27E0F26A146A1C8CCB29046",
+                "9F0605A0000000659F220114DF050420261231DF060101DF070101DF0281F8AEED55B9EE00E1ECEB045F61D2DA9A66AB637B43FB5CDBDB22A2FBB25BE061E937E38244EE5132F530144A3F268907D8FD648863F5A96FED7E42089E93457ADC0E1BC89C58A0DB72675FBC47FEE9FF33C16ADE6D341936B06B6A6F5EF6F66A4EDD981DF75DA8399C3053F430ECA342437C23AF423A211AC9F58EAF09B0F837DE9D86C7109DB1646561AA5AF0289AF5514AC64BC2D9D36A179BB8A7971E2BFA03A9E4B847FD3D63524D43A0E8003547B94A8A75E519DF3177D0A60BC0B4BAB1EA59A2CBB4D2D62354E926E9C7D3BE4181E81BA60F8285A896D17DA8C3242481B6C405769A39D547C74ED9FF95A70A796046B5EFF36682DC29DF040103DF0314C0D15F6CD957E491DB56DCDD1CA87A03EBE06B7B",
+                "9F0605A0000000259F22010FDF050420241231DF060101DF070101DF0281B0C8D5AC27A5E1FB89978C7C6479AF993AB3800EB243996FBB2AE26B67B23AC482C4B746005A51AFA7D2D83E894F591A2357B30F85B85627FF15DA12290F70F05766552BA11AD34B7109FA49DE29DCB0109670875A17EA95549E92347B948AA1F045756DE56B707E3863E59A6CBE99C1272EF65FB66CBB4CFF070F36029DD76218B21242645B51CA752AF37E70BE1A84FF31079DC0048E928883EC4FADD497A719385C2BBBEBC5A66AA5E5655D18034EC5DF040103DF0314A73472B3AB557493A9BC2179CC8014053B12BAB4",
+                "9F0605A0000000259F220110DF050420261231DF060101DF070101DF0281F8CF98DFEDB3D3727965EE7797723355E0751C81D2D3DF4D18EBAB9FB9D49F38C8C4A826B99DC9DEA3F01043D4BF22AC3550E2962A59639B1332156422F788B9C16D40135EFD1BA94147750575E636B6EBC618734C91C1D1BF3EDC2A46A43901668E0FFC136774080E888044F6A1E65DC9AAA8928DACBEB0DB55EA3514686C6A732CEF55EE27CF877F110652694A0E3484C855D882AE191674E25C296205BBB599455176FDD7BBC549F27BA5FE35336F7E29E68D783973199436633C67EE5A680F05160ED12D1665EC83D1997F10FD05BBDBF9433E8F797AEE3E9F02A34228ACE927ABE62B8B9281AD08D3DF5C7379685045D7BA5FCDE58637DF040103DF0314C729CF2FD262394ABC4CC173506502446AA9B9FD",
+                "9F0605A0000003339F220102DF050420211231DF060101DF070101DF028190A3767ABD1B6AA69D7F3FBF28C092DE9ED1E658BA5F0909AF7A1CCD907373B7210FDEB16287BA8E78E1529F443976FD27F991EC67D95E5F4E96B127CAB2396A94D6E45CDA44CA4C4867570D6B07542F8D4BF9FF97975DB9891515E66F525D2B3CBEB6D662BFB6C3F338E93B02142BFC44173A3764C56AADD202075B26DC2F9F7D7AE74BD7D00FD05EE430032663D27A57DF040103DF031403BB335A8549A03B87AB089D006F60852E4B8060",
+                "9F0605A0000003339F220103DF050420241231DF060101DF070101DF0281B0B0627DEE87864F9C18C13B9A1F025448BF13C58380C91F4CEBA9F9BCB214FF8414E9B59D6ABA10F941C7331768F47B2127907D857FA39AAF8CE02045DD01619D689EE731C551159BE7EB2D51A372FF56B556E5CB2FDE36E23073A44CA215D6C26CA68847B388E39520E0026E62294B557D6470440CA0AEFC9438C923AEC9B2098D6D3A1AF5E8B1DE36F4B53040109D89B77CAFAF70C26C601ABDF59EEC0FDC8A99089140CD2E817E335175B03B7AA33DDF040103DF031487F0CD7C0E86F38F89A66F8C47071A8B88586F26",
+                "9F0605A0000003339F220104DF050420281231DF060101DF070101DF0281F8BC853E6B5365E89E7EE9317C94B02D0ABB0DBD91C05A224A2554AA29ED9FCB9D86EB9CCBB322A57811F86188AAC7351C72BD9EF196C5A01ACEF7A4EB0D2AD63D9E6AC2E7836547CB1595C68BCBAFD0F6728760F3A7CA7B97301B7E0220184EFC4F653008D93CE098C0D93B45201096D1ADFF4CF1F9FC02AF759DA27CD6DFD6D789B099F16F378B6100334E63F3D35F3251A5EC78693731F5233519CDB380F5AB8C0F02728E91D469ABD0EAE0D93B1CC66CE127B29C7D77441A49D09FCA5D6D9762FC74C31BB506C8BAE3C79AD6C2578775B95956B5370D1D0519E37906B384736233251E8F09AD79DFBE2C6ABFADAC8E4D8624318C27DAF1DF040103DF0314F527081CF371DD7E1FD4FA414A665036E0F5E6E5",
+                "9F0605A0000000659F220112DF050420241231DF060101DF070101DF0281B0ADF05CD4C5B490B087C3467B0F3043750438848461288BFEFD6198DD576DC3AD7A7CFA07DBA128C247A8EAB30DC3A30B02FCD7F1C8167965463626FEFF8AB1AA61A4B9AEF09EE12B009842A1ABA01ADB4A2B170668781EC92B60F605FD12B2B2A6F1FE734BE510F60DC5D189E401451B62B4E06851EC20EBFF4522AACC2E9CDC89BC5D8CDE5D633CFD77220FF6BBD4A9B441473CC3C6FEFC8D13E57C3DE97E1269FA19F655215B23563ED1D1860D8681DF040103DF0314874B379B7F607DC1CAF87A19E400B6A9E25163E8",
+                "9F0605A0000000049F220105DF050420241231DF060101DF070101DF0281B0B8048ABC30C90D976336543E3FD7091C8FE4800DF820ED55E7E94813ED00555B573FECA3D84AF6131A651D66CFF4284FB13B635EDD0EE40176D8BF04B7FD1C7BACF9AC7327DFAA8AA72D10DB3B8E70B2DDD811CB4196525EA386ACC33C0D9D4575916469C4E4F53E8E1C912CC618CB22DDE7C3568E90022E6BBA770202E4522A2DD623D180E215BD1D1507FE3DC90CA310D27B3EFCCD8F83DE3052CAD1E48938C68D095AAC91B5F37E28BB49EC7ED597DF040103DF0314EBFA0D5D06D8CE702DA3EAE890701D45E274C845",
+                "9F0605A0000000049F220106DF050420261231DF060101DF070101DF0281F8CB26FC830B43785B2BCE37C81ED334622F9622F4C89AAE641046B2353433883F307FB7C974162DA72F7A4EC75D9D657336865B8D3023D3D645667625C9A07A6B7A137CF0C64198AE38FC238006FB2603F41F4F3BB9DA1347270F2F5D8C606E420958C5F7D50A71DE30142F70DE468889B5E3A08695B938A50FC980393A9CBCE44AD2D64F630BB33AD3F5F5FD495D31F37818C1D94071342E07F1BEC2194F6035BA5DED3936500EB82DFDA6E8AFB655B1EF3D0D7EBF86B66DD9F29F6B1D324FE8B26CE38AB2013DD13F611E7A594D675C4432350EA244CC34F3873CBA06592987A1D7E852ADC22EF5A2EE28132031E48F74037E3B34AB747FDF040103DF0314F910A1504D5FFB793D94F3B500765E1ABCAD72D9",
+                "9F0605A0000003339F220111DF050420301225DF060107DF070104DF0240B177895550EC39108C9AAB1F79968E1F75E5CF81C7ABEC0D88A78FA11875B2B8D57C6D208C4A1BED791925B08775A12524678DE2118A637E5CAFE34AC22F3D3EDF040111DF03208304D2DDB0C3A3E9576A1E3AAE0CC88029F0575616389EE1B8ED6B999B28415B",
+                "9F0605A0000003339F220118DF05083230333031323331DF060107DF070104DF024037710FEB7CC3617767874E85509C268E8F931D68773E93A89F39A4247DFE2D280FC5BC838353885B6DAD447C8F90116BD9D314047591989F67F319544D42A48BDF040111DF03201FBFAEB3C284E93057A69F7F39097630CABED89D5B1E1958D78C7C17F514A9FA",
+                "9F0605A0000000659F220110DF050420161231DF060101DF070101DF02819099B63464EE0B4957E4FD23BF923D12B61469B8FFF8814346B2ED6A780F8988EA9CF0433BC1E655F05EFA66D0C98098F25B659D7A25B8478A36E489760D071F54CDF7416948ED733D816349DA2AADDA227EE45936203CBF628CD033AABA5E5A6E4AE37FBACB4611B4113ED427529C636F6C3304F8ABDD6D9AD660516AE87F7F2DDF1D2FA44C164727E56BBC9BA23C0285DF040103DF0314C75E5210CBE6E8F0594A0F1911B07418CADB5BAB",
+                "9F0605A0000000659F220112DF050420191231DF060101DF070101DF0281B0ADF05CD4C5B490B087C3467B0F3043750438848461288BFEFD6198DD576DC3AD7A7CFA07DBA128C247A8EAB30DC3A30B02FCD7F1C8167965463626FEFF8AB1AA61A4B9AEF09EE12B009842A1ABA01ADB4A2B170668781EC92B60F605FD12B2B2A6F1FE734BE510F60DC5D189E401451B62B4E06851EC20EBFF4522AACC2E9CDC89BC5D8CDE5D633CFD77220FF6BBD4A9B441473CC3C6FEFC8D13E57C3DE97E1269FA19F655215B23563ED1D1860D8681DF040103DF0314874B379B7F607DC1CAF87A19E400B6A9E25163E8",
+                "9F0605A0000000049F220104DF050420171231DF060101DF070101DF028190A6DA428387A502D7DDFB7A74D3F412BE762627197B25435B7A81716A700157DDD06F7CC99D6CA28C2470527E2C03616B9C59217357C2674F583B3BA5C7DCF2838692D023E3562420B4615C439CA97C44DC9A249CFCE7B3BFB22F68228C3AF13329AA4A613CF8DD853502373D62E49AB256D2BC17120E54AEDCED6D96A4287ACC5C04677D4A5A320DB8BEE2F775E5FEC5DF040103DF0314381A035DA58B482EE2AF75F4C3F2CA469BA4AA6C",
+                "9F0605A0000000049F220105DF050420211231DF060101DF070101DF0281B0B8048ABC30C90D976336543E3FD7091C8FE4800DF820ED55E7E94813ED00555B573FECA3D84AF6131A651D66CFF4284FB13B635EDD0EE40176D8BF04B7FD1C7BACF9AC7327DFAA8AA72D10DB3B8E70B2DDD811CB4196525EA386ACC33C0D9D4575916469C4E4F53E8E1C912CC618CB22DDE7C3568E90022E6BBA770202E4522A2DD623D180E215BD1D1507FE3DC90CA310D27B3EFCCD8F83DE3052CAD1E48938C68D095AAC91B5F37E28BB49EC7ED597DF040103DF0314EBFA0D5D06D8CE702DA3EAE890701D45E274C845",
+                "9F0605A0000000049F220106DF050420211231DF060101DF070101DF0281F8CB26FC830B43785B2BCE37C81ED334622F9622F4C89AAE641046B2353433883F307FB7C974162DA72F7A4EC75D9D657336865B8D3023D3D645667625C9A07A6B7A137CF0C64198AE38FC238006FB2603F41F4F3BB9DA1347270F2F5D8C606E420958C5F7D50A71DE30142F70DE468889B5E3A08695B938A50FC980393A9CBCE44AD2D64F630BB33AD3F5F5FD495D31F37818C1D94071342E07F1BEC2194F6035BA5DED3936500EB82DFDA6E8AFB655B1EF3D0D7EBF86B66DD9F29F6B1D324FE8B26CE38AB2013DD13F611E7A594D675C4432350EA244CC34F3873CBA06592987A1D7E852ADC22EF5A2EE28132031E48F74037E3B34AB747FDF040103DF0314F910A1504D5FFB793D94F3B500765E1ABCAD72D9",
+                "9F0605A0000003339F220118DF050420261231DF060107DF070104DF024037710FEB7CC3617767874E85509C268E8F931D68773E93A89F39A4247DFE2D280FC5BC838353885B6DAD447C8F90116BD9D314047591989F67F319544D42A48BDF040111DF03201FBFAEB3C284E93057A69F7F39097630CABED89D5B1E1958D78C7C17F514A9FA",};
+
+        List<Capk> capkParams = new ArrayList<>();
+        for (int i = 0; i < capkArray.length; i++) {
+            Capk capk = tlvToCapk(capkArray[i]);
+            if (capkParams != null) {
+                capkParams.add(capk);
+            }
+        }
+        return capkParams;
+    }
+
+
+    private Capk tlvToCapk(String capk) {
+        if (capk == null || capk.length() == 0) {
+            return null;
+        }
+
+        byte[] value = null;
+        Map<Integer, TlvCell> capkTlvList = Tlv.unpack(Convert.strToBcdBytes(capk, true));
+        Capk emvCapk = new Capk();
+        TlvCell tlvCell = null;
+
+        // 9f06 RID
+        tlvCell = capkTlvList.get(0x9f06);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setRid(value);
+            }
+        }
+
+        // 9F22 01
+        tlvCell = capkTlvList.get(0x9f22);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setKeyID(value[0]);
+            }
+        }
+
+        // DF02
+        tlvCell = capkTlvList.get(0xDF02);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setModul(value);
+            }
+        }
+
+        // DF03
+        tlvCell = capkTlvList.get(0xDF03);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setCheckSum(value);
+            }
+        }
+
+        // DF06
+        tlvCell = capkTlvList.get(0xDF06);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setHashInd(value[0]);
+            }
+        }
+
+        // DF04
+        tlvCell = capkTlvList.get(0xDF04);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setExponent(value);
+            }
+        }
+
+        // DF05
+        tlvCell = capkTlvList.get(0xDF05);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                String expDate = "";
+                if (value.length == 4) {
+                    expDate = Convert.bcdBytesToStr(value).substring(2, 8);
+                } else {
+                    expDate = Convert.bcdBytesToStr(value);
+                }
+                emvCapk.setExpDate(Convert.strToBcdBytes(expDate, true));
+            }
+        }
+
+        // DF07
+        tlvCell = capkTlvList.get(0xDF07);
+        if (tlvCell != null) {
+            value = tlvCell.getValue();
+            if (value != null && value.length > 0) {
+                emvCapk.setArithInd(value[0]);
+            }
+        }
+        return emvCapk;
+    }
+}

+ 237 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvContactlessListenerImpl.java

@@ -0,0 +1,237 @@
+package com.yijia.handpos.device.emv;
+
+import com.pax.eemv.entity.ClssProgramIDInfo;
+import com.pax.eemv.entity.TlvElementMC;
+import com.pax.eemv.enums.ESourceType;
+import com.pax.eemv.impl.EEmvContactlessListenerImpl;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+public class EmvContactlessListenerImpl extends EEmvContactlessListenerImpl {
+
+    @Override
+    public List<TlvElementMC> onAddProprietaryDataMC() {
+        List<TlvElementMC> tlvElementMCs = new ArrayList<TlvElementMC>();
+        TlvElementMC tlv1 = null;
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x1D }).intValue());
+        tlv1.setData(new byte[] { 0x24, (byte) 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        /**
+         * FinancialApplication.emv.setKernelTLV(EKernelType.MC, new BigInteger(new byte[]{(byte) 0xDF,(byte)
+         * 0x81,0x2C}).intValue(), new byte[]{0x00});
+         */
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x5F, 0x57 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x16 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x4E }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x33 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x1c }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, 0x60 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, 0x62 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, 0x63 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x40 }).intValue());
+        tlv1.setData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x7E }).intValue());
+        tlv1.setData(new byte[] { 0x01 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x6D }).intValue());
+        tlv1.setData(new byte[] { 0x00, 0x01 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x33 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x5F, 0x2A }).intValue());
+        tlv1.setData(new byte[] { 0x01, 0x56 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x35 }).intValue());
+        tlv1.setData(new byte[] { 0x22 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x1A }).intValue());
+        tlv1.setData(new byte[] { 0x01, 0x56 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x08 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x09 }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x0A }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x0D }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x0C }).intValue());
+        tlv1.setData(new byte[] { 0x02 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x17 }).intValue());
+        tlv1.setData(new byte[] { (byte) 0xe0 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x18 }).intValue());
+        tlv1.setData(new byte[] { (byte) 0xe1 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x19 }).intValue());
+        tlv1.setData(new byte[] { 0x08 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1A }).intValue());
+        tlv1.setData(new byte[] { (byte) 0x9F, 0x6A, 0x04 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1B }).intValue());
+        tlv1.setData(new byte[] { 0x20 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1C }).intValue());
+        tlv1.setData(new byte[] { 0x00, 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1D }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1E }).intValue());
+        tlv1.setData(new byte[] { 0x10 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x1F }).intValue());
+        tlv1.setData(new byte[] { 0x48 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0xDF, (byte) 0x81, 0x2C }).intValue());
+        tlv1.setData(new byte[] { 0x00 });
+        tlvElementMCs.add(tlv1);
+
+        tlv1 = new TlvElementMC();
+        tlv1.setSource(ESourceType.TM);
+        tlv1.setTag(new BigInteger(new byte[] { (byte) 0x9F, 0x5C }).intValue());
+        tlv1.setData(new byte[] { 0x12, 0x34, 0x56, 0x78, 0x78, 0x56, 0x34, 0x12 });
+        tlvElementMCs.add(tlv1);
+
+        return tlvElementMCs;
+    }
+
+    @Override
+    public ClssProgramIDInfo onMatchProgramID(byte[] abyte0) {
+
+        ClssProgramIDInfo info = new ClssProgramIDInfo();
+
+        info.setRdClssTxnLmt(1000000000);
+        info.setRdCVMLmt(5000);
+        info.setRdClssFLmt(5000);
+        info.setTermFLmt(5000);
+        info.setRdClssFLmtFlg((byte) 1);
+        info.setRdClssTxnLmtFlg((byte) 0);
+        info.setRdCVMLmtFlg((byte) 0);
+        info.setTermFLmtFlg((byte) 0);
+        info.setStatusCheckFlg((byte) 0);
+        info.setAmtZeroNoAllowed((byte) 0);
+        info.setProgramID(abyte0);
+
+        return info;
+    }
+
+}

+ 31 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvDeviceListenerImpl.java

@@ -0,0 +1,31 @@
+package com.yijia.handpos.device.emv;
+
+import android.util.Log;
+
+import com.pax.eemv.entity.APDUResp;
+import com.pax.eemv.entity.APDUSend;
+import com.pax.eemv.entity.RSAPinKey;
+import com.pax.eemv.impl.EEmvDeviceListenerImpl;
+
+public class EmvDeviceListenerImpl extends EEmvDeviceListenerImpl {
+
+    private static final String LOG_TAG = "EmvDeviceListenerImpl";
+
+    @Override
+    public int onVerifyCipherPin(RSAPinKey rsaPinKey, int leftTimes, byte[] iccResponse) {
+        Log.d(LOG_TAG, "onVerifyCipherPin:");
+        return super.onVerifyCipherPin(rsaPinKey, leftTimes, iccResponse);
+    }
+
+    @Override
+    public int onVerifyPlainPin(int leftTimes, byte[] iccResponse) {
+        Log.d(LOG_TAG, "onVerifyPlainPin:");
+        return super.onVerifyPlainPin(leftTimes, iccResponse);
+    }
+
+    @Override
+    public int onDeivcePiccIsoCommand(int slot, APDUSend sendApdu, APDUResp recvApdu) {
+        Log.d(LOG_TAG, "onDeivcePiccIsoCommand:");
+        return super.onDeivcePiccIsoCommand(slot, sendApdu, recvApdu);
+    }
+}

+ 33 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvListenerImpl.java

@@ -0,0 +1,33 @@
+package com.yijia.handpos.device.emv;
+
+import android.util.Log;
+
+import com.pax.eemv.entity.Amounts;
+import com.pax.eemv.enums.EOnlineResult;
+import com.pax.eemv.impl.EEmvListenerImpl;
+
+public class EmvListenerImpl extends EEmvListenerImpl {
+
+    private static final String LOG_TAG = "EmvListenerImpl";
+
+    @Override
+    public int onCardHolderPwd(boolean bOnlinePin, int leftTimes, byte[] pinData) {
+        Log.d(LOG_TAG, "onCardHolderPwd:");
+        return super.onCardHolderPwd(bOnlinePin, leftTimes, pinData);
+    }
+
+    @Override
+    public Amounts onGetAmounts() {
+        Log.d(LOG_TAG, "onGetAmounts:");
+        Amounts a = new Amounts();
+        a.setTransAmount("15");
+        return a;
+    }
+
+    @Override
+    public EOnlineResult onOnlineProc() {
+        Log.d(LOG_TAG, "onOnlineProc:");
+        return EOnlineResult.APPROVE;
+    }
+
+}

+ 86 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/emv/EmvTags.java

@@ -0,0 +1,86 @@
+package com.yijia.handpos.device.emv;
+
+import android.annotation.SuppressLint;
+import android.util.Log;
+
+import com.pax.cocoa.tools.Convert;
+import com.pax.eemv.IEmv;
+import com.pax.gl.packer.ITlv;
+import com.pax.gl.packer.TlvException;
+import com.yijia.handpos.device.SysteHelper;
+
+public class EmvTags {
+    private final static String TAG = "EmvTags";
+
+    public static String getTransResultTLV() {
+        Log.d(TAG, "getTransResultTLV");
+        final int[] TAGS = {0x95, 0x9b, 0x9f36, 0x9f26, 0x91, 0x9f10, 0xdf31, 0x9f79, 0x9f27, 0x84, 0x9f06, 0x50,
+                0x9f12, 0x9f33, 0x9f37, 0x9f34, 0x82, 0x9f5d};
+        return Convert.bcdBytesToStr(getTLVbyTags(TAGS));
+    }
+
+    public static String getScriptTLV() {
+        Log.d(TAG, "getScriptTLV");
+        final int[] TAGS = {0x9f33, 0x95, 0x9f37, 0x9f1e, 0x9f10, 0x9f26, 0x9f36, 0x82, 0xdf31, 0x9f1a, 0x9a};
+        return Convert.bcdBytesToStr(getTLVbyTags(TAGS));
+    }
+
+    public static String getOfflineTLV() {
+        Log.d(TAG, "getOfflinTLV");
+        // 添加 ,"5a","5f34","9b","ff21"
+        final int[] TAGS = {0x9f26, 0x9f27, 0x9f10, 0x9f37, 0x9f36, 0x95, 0x9a, 0x9c, 0x9f02, 0x5f2a, 0x82, 0x9f1a,
+                0x9f03, 0x9f33, 0x9f1e, 0x84, 0x9f09, 0x9f41, 0x9f34, 0x9f35, 0x8a, 0x9f12, 0x50, 0x9f74, 0x5a, 0x5f34,
+                0x9b, 0xff21};
+        return Convert.bcdBytesToStr(getTLVbyTags(TAGS));
+    }
+
+    public static String getARQCTLV() {
+        Log.d(TAG, "getARQCTLV");
+        final int[] TAGS = {0x9F26, 0x9F27, 0x9F10, 0x9F37, 0x9F36, 0x95, 0x9A, 0x9C, 0x9F02, 0x5F2A, 0x82, 0x9F1A,
+                0x9F03, 0x9F33, 0x9F34, 0x9F35, 0x9F1E, 0x84, 0x9F09, 0x9F41, 0x9F63};
+        return Convert.bcdBytesToStr(getTLVbyTags(TAGS));
+    }
+
+    public static String getReversalTLV() {
+        Log.d(TAG, "getReversalTLV");
+        final int[] TAGS = {0x95, 0x9f1e, 0x9f10, 0x9f36, 0xdf31};
+        return Convert.bcdBytesToStr(getTLVbyTags(TAGS));
+    }
+
+    @SuppressLint("UseSparseArrays")
+    public static byte[] getTLVbyTags(int[] tags) {
+        if (tags == null || tags.length == 0) {
+            return null;
+        }
+        IEmv emv = SysteHelper.emv;
+        ITlv tlv = SysteHelper.gl.getPacker().getTlv();
+        ITlv.ITlvDataObjList tlvList = tlv.createTlvDataObjectList();
+        for (int tag : tags) {
+            try {
+                byte[] value = emv.getTlv(tag);
+                if (value == null || value.length == 0) {
+                    if (tag == 0x9f03) {
+                        value = new byte[6];
+                    } else {
+                        continue;
+                    }
+                }
+                ITlv.ITlvDataObj obj = tlv.createTlvDataObject();
+                obj.setTag(tag);
+                obj.setValue(value);
+                tlvList.addDataObj(obj);
+            } catch (Exception e) {
+                e.printStackTrace();
+                continue;
+            }
+        }
+
+        try {
+            return tlv.pack(tlvList);
+        } catch (TlvException e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+}

+ 133 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/m1card/M1CardReadHelper.java

@@ -0,0 +1,133 @@
+package com.yijia.handpos.device.m1card;
+
+import android.util.Log;
+
+import com.pax.dal.IPicc;
+import com.pax.dal.entity.EDetectMode;
+import com.pax.dal.entity.EM1KeyType;
+import com.pax.dal.entity.EPiccRemoveMode;
+import com.pax.dal.entity.EPiccType;
+import com.pax.dal.entity.PiccCardInfo;
+import com.pax.dal.exceptions.PiccDevException;
+import com.yijia.handpos.device.SysteHelper;
+
+/**
+ * M1读取帮助类
+ */
+public class M1CardReadHelper {
+
+    private static final M1CardReadHelper instance = new M1CardReadHelper();
+    private IPicc picc;
+
+    private M1CardReadHelper() {
+        picc = SysteHelper.dal.getPicc(EPiccType.INTERNAL);
+    }
+
+    public static M1CardReadHelper getInstance() {
+        return instance;
+    }
+
+    /**
+     * 打开读卡器
+     */
+    public void open() {
+        try {
+            picc.open();
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 检卡
+     *
+     * @return
+     */
+    public PiccCardInfo detect() {
+        PiccCardInfo cardInfo = null;
+        try {
+            cardInfo = picc.detect(EDetectMode.ONLY_M);
+            Log.d("M1 detect", "CardType: " + cardInfo.getCardType());
+            byte[] serialInfo = cardInfo.getSerialInfo();
+            if (serialInfo != null) {
+                Log.d("M1 detect", "serialInfo: " + SysteHelper.convert.bcdToStr(serialInfo));
+            }
+            Log.d("M1 detect", "CID: " + cardInfo.getCID());
+            byte[] other = cardInfo.getOther();
+            if (other != null) {
+                Log.d("M1 detect", "Other: " + SysteHelper.convert.bcdToStr(other));
+            }
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+        return cardInfo;
+    }
+
+    /**
+     * m1卡授权
+     *
+     * @param type       卡类型
+     * @param blkNo      块号,从0起
+     * @param pwd        密钥,6字节长
+     * @param serialInfo SerialInfo
+     */
+    public void m1Auth(EM1KeyType type, byte blkNo, byte[] pwd, byte[] serialInfo) {
+        try {
+            picc.m1Auth(type, blkNo, pwd, serialInfo);
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 读取指定块号的内容
+     *
+     * @param blkNo 块号
+     */
+    public byte[] m1Read(byte blkNo) {
+        try {
+            return picc.m1Read(blkNo);
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * 写入指定块号的内容
+     *
+     * @param blkNo
+     * @param data
+     */
+    public void m1Write(byte blkNo, byte[] data) {
+        try {
+            picc.m1Write(blkNo, data);
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 移除读卡
+     */
+    public void remove() {
+        try {
+            picc.remove(EPiccRemoveMode.REMOVE, (byte) 0);
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 关闭读卡器
+     */
+    public void close() {
+        try {
+            picc.close();
+        } catch (PiccDevException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+}

+ 200 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/print/PrintBean.java

@@ -0,0 +1,200 @@
+package com.yijia.handpos.device.print;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.yijia.handpos.device.constants.PrintDefine;
+
+/**
+ * 对打印内容进行定制
+ */
+public class PrintBean implements Parcelable {
+
+    //内容
+    private String txt;
+    //打印类型
+    private String type;
+    //对齐方式
+    private String align;
+    //如果是text类型, text 尺寸大小
+    private String fontSize;
+    //是否换行
+    private boolean isNext;
+    //是否加粗
+    private boolean isBlod;
+
+    private Bitmap drawBitmap;
+
+    protected PrintBean(Parcel in) {
+        txt = in.readString();
+        type = in.readString();
+        align = in.readString();
+        fontSize = in.readString();
+        isNext = in.readByte() != 0;
+        isBlod = in.readByte() != 0;
+        drawBitmap = in.readParcelable(Bitmap.class.getClassLoader());
+    }
+
+    public static final Creator<PrintBean> CREATOR = new Creator<PrintBean>() {
+        @Override
+        public PrintBean createFromParcel(Parcel in) {
+            return new PrintBean(in);
+        }
+
+        @Override
+        public PrintBean[] newArray(int size) {
+            return new PrintBean[size];
+        }
+    };
+
+    public Bitmap getDrawBitmap() {
+        return drawBitmap;
+    }
+
+    public PrintBean setDrawBitmap(Bitmap drawBitmap) {
+        this.drawBitmap = drawBitmap;
+        return this;
+    }
+
+    public String getTxt() {
+        return txt;
+    }
+
+    public void setTxt(String txt) {
+        this.txt = txt;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getAlign() {
+        return align;
+    }
+
+    public void setAlign(String align) {
+        this.align = align;
+    }
+
+    public String getFontSize() {
+        return fontSize;
+    }
+
+    public void setFontSize(String fontSize) {
+        this.fontSize = fontSize;
+    }
+
+    public boolean isNext() {
+        return isNext;
+    }
+
+    public void setNext(boolean next) {
+        isNext = next;
+    }
+
+    public boolean isBlod() {
+        return isBlod;
+    }
+
+    public void setBlod(boolean blod) {
+        isBlod = blod;
+    }
+
+
+    public PrintBean(String type) {
+        this(type, "");
+    }
+
+    public PrintBean(String type, String content) {
+        this.type = type;
+        this.txt = content;
+        align = PrintDefine.Align.LEFT;
+        fontSize = PrintDefine.Font.MED;
+        if (type.equals(PrintDefine.Type.LINE)) {
+            fontSize = PrintDefine.Font.MED;
+        }
+        isBlod = false;
+        isNext = false;
+    }
+
+    public PrintBean changeTxt(String content) {
+        txt = content;
+        return this;
+    }
+
+
+    /**
+     * 内容
+     * {@link PrintDefine.Type}
+     *
+     * @param type
+     * @return
+     */
+    public PrintBean changeType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    /**
+     * 对其方式
+     * {@link PrintDefine.Align}
+     *
+     * @param align
+     * @return
+     */
+    public PrintBean changeAlgin(String align) {
+        this.align = align;
+        return this;
+    }
+
+    /**
+     * 字体大小
+     * {@link PrintDefine.Font}
+     *
+     * @param size
+     * @return
+     */
+    public PrintBean changeFontSize(String size) {
+        fontSize = size;
+        return this;
+    }
+
+    /**
+     * {@link PrintDefine.Font}
+     *
+     * @param blod
+     * @return
+     */
+    public PrintBean changeBold(boolean blod) {
+        isBlod = blod;
+        fontSize = PrintDefine.Font.BIG;
+        return this;
+    }
+
+    public PrintBean isNextLine(boolean isNext) {
+        this.isNext = isNext;
+        return this;
+    }
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(txt);
+        dest.writeString(type);
+        dest.writeString(align);
+        dest.writeString(fontSize);
+        dest.writeByte((byte) (isNext ? 1 : 0));
+        dest.writeByte((byte) (isBlod ? 1 : 0));
+        dest.writeParcelable(drawBitmap, flags);
+    }
+}

+ 10 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/print/PrintCallBack.java

@@ -0,0 +1,10 @@
+package com.yijia.handpos.device.print;
+
+public interface PrintCallBack {
+    /**
+     *
+     * @param code 00   打印成功  其他打印失败
+     * @param msg  回调原因
+     */
+    void result(String code, String msg);
+}

+ 22 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/scan/ScanResultListener.java

@@ -0,0 +1,22 @@
+package com.yijia.handpos.device.scan;
+
+/**
+ * 扫码结果接口
+ */
+public interface ScanResultListener {
+
+    /**
+     * 扫码成功
+     *
+     * @param code
+     */
+    void success(String code);
+
+    /**
+     * 扫码失败
+     *
+     * @param msg
+     */
+    void fail(String msg);
+
+}

+ 749 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/trade/DeviceController.java

@@ -0,0 +1,749 @@
+package com.yijia.handpos.device.trade;
+
+import android.annotation.SuppressLint;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.pax.cocoa.tools.Convert;
+import com.pax.dal.ICardReaderHelper;
+import com.pax.dal.IPed;
+import com.pax.dal.entity.EPedMacMode;
+import com.pax.dal.entity.EPinBlockMode;
+import com.pax.dal.entity.EReaderType;
+import com.pax.dal.entity.PollingResult;
+import com.pax.dal.entity.PollingResult.EOperationType;
+import com.pax.dal.exceptions.PedDevException;
+import com.pax.eemv.entity.Config;
+import com.pax.eemv.entity.InputPBOCParam;
+import com.pax.eemv.entity.InputParam;
+import com.pax.eemv.entity.InputPayPassParam;
+import com.pax.eemv.entity.InputPayWaveParam;
+import com.pax.eemv.entity.ReaderParam;
+import com.pax.eemv.entity.TagPresent;
+import com.pax.eemv.enums.EChannelType;
+import com.pax.eemv.enums.EFlowType;
+import com.pax.eemv.enums.EKernelType;
+import com.pax.eemv.enums.ETransResult;
+import com.pax.eemv.exception.EmvException;
+import com.yijia.handpos.device.SysteHelper;
+import com.yijia.handpos.device.constants.FieldDefinition;
+import com.yijia.handpos.device.emv.EmvAid;
+import com.yijia.handpos.device.emv.EmvCapk;
+import com.yijia.handpos.device.emv.EmvContactlessListenerImpl;
+import com.yijia.handpos.device.emv.EmvDeviceListenerImpl;
+import com.yijia.handpos.device.emv.EmvListenerImpl;
+import com.yijia.handpos.device.emv.EmvTags;
+import com.yijia.handpos.device.utils.PanUtils;
+import com.yijia.handpos.device.utils.TrackUtils;
+
+import java.math.BigInteger;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * DeviceController
+ * 调用系统读卡,输密,计算MAC等
+ */
+public class DeviceController extends IDeviceController {
+
+    private final String LOG_TAG = "DeviceController";
+
+    // 刷卡
+    private static final int TYPE_MAG = 0;
+    // 插卡
+    private static final int TYPE_ICC = 1;
+    // 非接
+    private static final int TYPE_PICC = 2;
+
+
+    private TradeListener listener = null;
+    private EChannelType channelType = null;
+
+    // 金额
+    private String amount = "";
+
+    // 交易类型
+    private int transType = 0;
+    // 卡号
+    private String pan = "";
+
+    private final Handler handler = new Handler(Looper.getMainLooper());
+
+    /**
+     * 消费
+     ***/
+    private final int CONSUME = 0x00;
+    /**
+     * 预授权交易
+     **/
+    private final int PRE_AUTH = 0x03;
+    /**
+     * 消费撤销或退货
+     **/
+    private final int CONSUME_CANCEL = 0x20;
+
+    private DeviceController() {
+    }
+
+    public static DeviceController getInstance() {
+        return Holder.INSTANCE;
+    }
+
+    static class Holder {
+        static DeviceController INSTANCE = new DeviceController();
+    }
+
+    @Override
+    public String setDeviceType() {
+        Log.d(LOG_TAG, "DeviceController.setDeviceType");
+        String model = android.os.Build.MODEL;
+        return model;
+    }
+
+    @Override
+    public void tradeInit(String transactionType, TradeListener listener) {
+        Log.d(LOG_TAG, "DeviceController.tradeInit >>> transactionType:" + transactionType);
+        this.listener = listener;
+        if (transactionType.equals(FieldDefinition.CONSUME)) {
+            this.transType = CONSUME;
+        } else if (transactionType.equals(FieldDefinition.CONSUME_CANCEL)) {
+            this.transType = CONSUME_CANCEL;
+        } else {
+            this.transType = PRE_AUTH;
+        }
+    }
+
+    @Override
+    public void startTrade(String money, String time, String transNo) {
+        Log.d(LOG_TAG, "DeviceController.startTrade >>> money:"
+                + money + " time:" + time + " transNo:" + transNo);
+        this.amount = money;
+        listener.onStartSearchCard();
+        // 开始寻卡
+        new SearchCardThread().start();
+    }
+
+    @Override
+    public void stopTrade() {
+        Log.d(LOG_TAG, "DeviceController.stopTrade");
+        ICardReaderHelper cardReaderHelper = SysteHelper.dal.getCardReaderHelper();
+        cardReaderHelper.stopPolling();
+    }
+
+    @Override
+    public void getPinInput() {
+        Log.d(LOG_TAG, "DeviceController.getPinInput");
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    IPed ped = SysteHelper.getPed();
+                    ped.showInputBox(true, "请输入密码");
+                    ped.setIntervalTime(1, 1);
+                    String cardNo = pan;
+                    if (TextUtils.isEmpty(pan)) {
+                        cardNo = "0000000000000000";
+                    }
+
+                    byte[] pindata = getPinBlock(PanUtils.getPanBlock(cardNo, PanUtils.EPanMode.X9_8_WITH_PAN), false);
+                    if (pindata != null)
+                        listener.onReturnPin(SysteHelper.convert.bcdToStr(pindata));
+                    else {
+                        listener.onReturnPin("");
+                    }
+                } catch (PedDevException e) {
+                    e.printStackTrace();
+                    listener.onError(e.getErrCode(), e.getErrMsg());
+                }
+            }
+        }.start();
+    }
+
+    @Override
+    public void getPinInput(final String cardNo) {
+        Log.d(LOG_TAG, "DeviceController.getPinInput >>> cardNo:" + cardNo);
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    IPed ped = SysteHelper.getPed();
+                    ped.showInputBox(true, "请输入密码");
+                    ped.setIntervalTime(1, 1);
+
+                    byte[] pindata = getPinBlock(PanUtils.getPanBlock(cardNo, PanUtils.EPanMode.X9_8_WITH_PAN), false);
+                    if (pindata != null)
+                        listener.onReturnPin(SysteHelper.convert.bcdToStr(pindata));
+                    else {
+                        listener.onReturnPin("");
+                    }
+                } catch (PedDevException e) {
+                    e.printStackTrace();
+                    listener.onError(e.getErrCode(), e.getErrMsg());
+                }
+            }
+        }.start();
+    }
+
+    @Override
+    public byte[] calcMac(byte[] data, int start, int end) {
+        Log.d(LOG_TAG, "DeviceController.calcMac >>> data:"
+                + SysteHelper.convert.bcdToStr(data) + " start:" + start + " end:" + end);
+        byte[] calData = new byte[end - start + 1];
+        System.arraycopy(calData, 0, data, start, calData.length);
+        return getCUPMac(calData);
+    }
+
+    // 寻卡线程
+    class SearchCardThread extends Thread {
+        @Override
+        public void run() {
+            try {
+                ICardReaderHelper cardReaderHelper = SysteHelper.dal.getCardReaderHelper();
+                PollingResult pollingResult = cardReaderHelper.polling(EReaderType.MAG_ICC_PICC, 60 * 1000, false);
+                cardReaderHelper.stopPolling();
+
+                if (pollingResult.getOperationType() == EOperationType.TIMEOUT) {
+                    listener.onError(-1, "捡卡超时");
+                } else if (pollingResult.getOperationType() == EOperationType.CANCEL) {
+                    listener.onStopSearchCard();
+                } else if (pollingResult.getOperationType() == EOperationType.PAUSE) {
+                    //listener.onStopSearchCard();
+                } else {// 寻卡成功时调用
+                    Log.d(LOG_TAG, "DeviceController.startTrade >>> " + pollingResult.getReaderType());
+
+                    addCardInfoMap(FieldDefinition.MONEY, amount);
+                    if (pollingResult.getReaderType().equals(EReaderType.ICC)) {
+                        channelType = EChannelType.ICC;
+                        listener.onFoundCard(TYPE_ICC);
+                        addCardInfoMap(FieldDefinition.SWIPE_MODE, FieldDefinition.SWIPE_INSERT);
+                        closeReadCard();
+                        processPBOC();
+                    } else if (pollingResult.getReaderType().equals(EReaderType.PICC)) {
+                        channelType = EChannelType.PICC;
+                        listener.onFoundCard(TYPE_PICC);
+                        addCardInfoMap(FieldDefinition.SWIPE_MODE, FieldDefinition.SWIPE_NON_CONTACTED);
+                        closeReadCard();
+                        processPBOC();
+                    } else {
+                        listener.onFoundCard(TYPE_MAG);
+                        String track2 = pollingResult.getTrack2();
+                        String track3 = pollingResult.getTrack3();
+
+                        // 有时刷卡成功,但没有磁道II,做一下防护
+                        if (track2 == null || track2.length() == 0) {
+                            handler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    new SearchCardThread().start();
+                                }
+                            });
+                            return;
+                        }
+
+                        pan = TrackUtils.getPan(track2);
+                        addCardInfoMap(FieldDefinition.TRACK1, pollingResult.getTrack1());
+                        addCardInfoMap(FieldDefinition.TRACK2, track2);
+                        addCardInfoMap(FieldDefinition.TRACK3, track3);
+                        addCardInfoMap(FieldDefinition.CARD_NO, pan);
+                        addCardInfoMap(FieldDefinition.SWIPE_MODE, FieldDefinition.SWIPE_SWIPE);
+                        addCardInfoMap(FieldDefinition.EXPIRT_DATE, TrackUtils.getExpDate(track2));
+                        closeReadCard();
+
+                        handler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                listener.onReturnCardInfo(cardInfoMap);
+                            }
+                        });
+                    }
+                }
+
+            } catch (Exception e) {
+                // 读卡失败,重试
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        new SearchCardThread().start();
+                    }
+                });
+            }
+        }
+    }
+
+    private void closeReadCard() {
+        SysteHelper.dal.getCardReaderHelper().setIsPause(true);
+        SysteHelper.dal.getCardReaderHelper().stopPolling();
+    }
+
+    private void processPBOC() {
+        Log.d(LOG_TAG, "DeviceController2.processPBOC >>> ");
+
+        try {
+            Log.d(LOG_TAG, "DeviceController.processPBOC >>> emvInit and set Emv Config");
+            SysteHelper.emv.emvInit();
+            setEmvConfig();
+
+            // paypass ,paywave设置参数
+            setPbocParam();
+            setPassParam();
+            setWaveParam();
+            setAEParam();
+
+        } catch (EmvException e) {
+            e.printStackTrace();
+        }
+
+        EmvAid emvAid = new EmvAid();
+        SysteHelper.emv.setAidParamList(emvAid.loadAllAid());
+        EmvCapk emvCapk = new EmvCapk();
+        SysteHelper.emv.setCapkList(emvCapk.loadAllCapk());
+        InputParam inputParam = toInputParam();
+
+        EmvListenerImpl emvListener = new EmvListenerImpl();
+        SysteHelper.emv.setListener(emvListener);
+
+        EmvContactlessListenerImpl contactlessListenerImpl = new EmvContactlessListenerImpl();
+        SysteHelper.emv.setContactlessListener(contactlessListenerImpl);
+
+        EmvDeviceListenerImpl deviceListenerImpl = new EmvDeviceListenerImpl();
+        SysteHelper.emv.setDeviceListener(deviceListenerImpl);
+
+        switch (transType) {
+            case CONSUME:
+                Log.w(LOG_TAG, "PbocStub.processPBOC >>> 消费");
+                if (channelType.equals(EChannelType.ICC)) {
+                    inputParam.setFlowType(EFlowType.COMPLETE);
+                } else {
+                    inputParam.setFlowType(EFlowType.QPBOC);
+                }
+                transProcess(inputParam);
+                break;
+            case PRE_AUTH:
+                Log.d(LOG_TAG, "PbocStub.processPBOC >>> 预授权交易");
+                inputParam.setIsForceOnline(true);
+                if (channelType.equals(EChannelType.ICC)) {
+                    inputParam.setFlowType(EFlowType.COMPLETE);
+                } else {
+                    inputParam.setFlowType(EFlowType.QPBOC);
+                }
+                transProcess(inputParam);
+                break;
+            case CONSUME_CANCEL:
+                Log.d(LOG_TAG, "PbocStub.processPBOC >>> 消费撤销或退货");
+                if (channelType.equals(EChannelType.ICC)) {
+                    inputParam.setFlowType(EFlowType.COMPLETE);
+                } else {
+                    inputParam.setFlowType(EFlowType.QPBOC);
+                }
+                transProcess(inputParam);
+                break;
+            default:
+                Log.e(LOG_TAG, "DeviceController2.processPBOC >>> 交易类型错误");
+                break;
+        }
+    }
+
+    private static byte[] getPinBlock(String panBlock, boolean supportBypass) throws PedDevException {
+        byte[] pinBlock;
+        String pinLen = "0,4,5,6";
+        IPed ped = SysteHelper.getPed();
+        while (true) {
+            pinBlock = ped.getPinBlock(SysteHelper.PIK_INDEX, pinLen, panBlock.getBytes(),
+                    EPinBlockMode.ISO9564_0, 50 * 1000);
+            if (!supportBypass) { // 主要处理外置密码键盘凭密的时候,不输入密码直接点确认键;
+                if (pinBlock != null && pinBlock.length == 0) {
+                    continue;
+                }
+            }
+            break;
+        }
+
+        return pinBlock;
+    }
+
+    private static byte[] getCUPMac(byte[] data) {
+        byte[] tmpbuf = new byte[8];
+        int len;
+        byte[] dataIn = new byte[data.length + tmpbuf.length];
+        len = data.length / tmpbuf.length + 1;
+        System.arraycopy(data, 0, dataIn, 0, data.length);
+        for (int i = 0; i < len; i++) {
+            for (int k = 0; k < tmpbuf.length; k++) {
+                tmpbuf[k] ^= dataIn[i * tmpbuf.length + k];
+            }
+        }
+        String beforeCalcMacData = SysteHelper.convert.bcdToStr(tmpbuf);
+        try {
+            byte[] mac = calcMac(beforeCalcMacData.getBytes());
+            return SysteHelper.convert.bcdToStr(mac).substring(0, 8).getBytes();
+        } catch (PedDevException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private static byte[] calcMac(byte[] data) throws PedDevException {
+        IPed ped = SysteHelper.getPed();
+        return ped.getMac(SysteHelper.MAK_INDEX, data, EPedMacMode.MODE_00);
+    }
+
+    private void transProcess(InputParam inputParam) {
+        ETransResult result = null;
+        Log.e(LOG_TAG, "DeviceController.transProcess >>> transProcess: ");
+        try {
+            result = SysteHelper.emv.emvProcess(inputParam);
+        } catch (EmvException e) {
+            e.printStackTrace();
+            Log.e(LOG_TAG, "DeviceController.transProcess >>> PaxEmvException: code" + e.getErrCode());
+            Log.e(LOG_TAG, "DeviceController.transProcess >>> PaxEmvException: getErrMsg" + e.getErrMsg());
+            listener.onError(e.getErrCode(), e.getErrMsg());
+            return;
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "DeviceController2.transProcess >>> Exception");
+        }
+        Log.e(LOG_TAG, "DeviceController2.transProcess >>> emv end-------");
+        Log.d(LOG_TAG, "DeviceController2.transProcess >>> result:" + result);
+
+
+        // 卡号
+        String pan = "";
+        String track = "";
+        byte[] track2 = SysteHelper.emv.getTlv(0x57);
+        if (track2 != null && track2.length > 0) {
+            track = Convert.bcdBytesToStr(track2);
+            track = track.split("F")[0];
+            pan = TrackUtils.getPan(track);
+            this.pan = pan;
+        }
+        addCardInfoMap(FieldDefinition.CARD_NO, pan);
+
+        // 有效期
+        byte[] expDate = SysteHelper.emv.getTlv(0x5f24);
+        if (expDate != null && expDate.length > 0) {
+            String temp = SysteHelper.convert.bcdToStr(expDate);
+            addCardInfoMap(FieldDefinition.EXPIRT_DATE, temp.substring(0, 4));
+        }
+        // 获取卡片序列号
+        byte[] cardSeq = SysteHelper.emv.getTlv(0x5f34);
+        if (cardSeq != null && cardSeq.length > 0) {
+            addCardInfoMap(FieldDefinition.SERIAL, Convert.bcdBytesToStr(cardSeq));
+        } else {
+            addCardInfoMap(FieldDefinition.SERIAL, "00");
+        }
+        addCardInfoMap(FieldDefinition.TRACK2, track);
+        addCardInfoMap(FieldDefinition.ICDATA, EmvTags.getARQCTLV());
+
+        saveTvrTsi();
+
+        switch (result) {
+            case ONLINE_APPROVED:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_ONLINE_APPROVED");
+                listener.onReturnCardInfo(cardInfoMap);
+                break;
+            case OFFLINE_APPROVED:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_OFFLINE_APPROVED");
+                listener.onReturnCardInfo(cardInfoMap);
+                break;
+            case ARQC:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_ARQC");
+                listener.onReturnCardInfo(cardInfoMap);
+                break;
+            case SIMPLE_FLOW_END:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_SIMPLE_FLOW_END");
+                listener.onReturnCardInfo(cardInfoMap);
+                break;
+            case ONLINE_DENIED:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_ONLINE_DENIED");
+                listener.onError(ETransResult.ONLINE_DENIED.getTransResult(), "联机拒绝");
+                break;
+
+            case OFFLINE_DENIED:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_OFFLINE_DENIED");
+                listener.onError(ETransResult.OFFLINE_DENIED.getTransResult(), "脱机拒绝");
+                break;
+            case ONLINE_CARD_DENIED:// 卡片的事
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_ONLINE_CARD_DENIED");
+                //listener.onError(ETransResult.ONLINE_CARD_DENIED.getTransResult(), "联机允许,卡片拒绝");
+                listener.onReturnCardInfo(cardInfoMap);
+                break;
+            case ABORT_TERMINATED:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_ABORT_TERMINATED");
+                listener.onError(ETransResult.ABORT_TERMINATED.getTransResult(), "终端终止");
+                break;
+            default:
+                Log.e(LOG_TAG, "DeviceController2.transProcess >>> emvPorcess result:PAX_EMV_default");
+                listener.onError(ETransResult.ABORT_TERMINATED.getTransResult(), "终端终止");
+        }
+    }
+
+    private void saveTvrTsi() {
+        // ARQC
+        byte[] tc = SysteHelper.emv.getTlv(0x9f26);
+        if (tc != null && tc.length > 0) {
+            Log.e("TAG", "ARQC:" + SysteHelper.convert.bcdToStr(tc));
+        }
+
+        // AID
+        byte[] aid = SysteHelper.emv.getTlv(0x4f);
+        if (aid != null && aid.length > 0) {
+            Log.e("TAG", "aid:" + SysteHelper.convert.bcdToStr(aid));
+        }
+
+        // CVM
+        byte[] CVM = SysteHelper.emv.getTlv(0x9F34);
+        if (CVM != null && CVM.length > 0) {
+            Log.e("TAG", "CVM:" + SysteHelper.convert.bcdToStr(CVM));
+        }
+
+        // ATC
+        byte[] atc = SysteHelper.emv.getTlv(0x9f36);
+        if (atc != null && atc.length > 0) {
+            Log.e("TAG", "atc:" + SysteHelper.convert.bcdToStr(atc));
+        }
+
+        // UNPR_NO
+        byte[] UNPR_NO = SysteHelper.emv.getTlv(0x9F37);
+        if (UNPR_NO != null && UNPR_NO.length > 0) {
+            Log.e("TAG", "UNPR_NO:" + SysteHelper.convert.bcdToStr(UNPR_NO));
+        }
+
+        // AIP
+        byte[] AIP = SysteHelper.emv.getTlv(0x82);
+        if (AIP != null && AIP.length > 0) {
+            Log.e("TAG", "AIP:" + SysteHelper.convert.bcdToStr(AIP));
+        }
+
+        // termCap
+        byte[] termCap = SysteHelper.emv.getTlv(0x9F33);
+        if (termCap != null && termCap.length > 0) {
+            Log.e("TAG", "termCap:" + SysteHelper.convert.bcdToStr(termCap));
+        }
+
+        // IAD
+        byte[] IAD = SysteHelper.emv.getTlv(0x9F10);
+        if (IAD != null && IAD.length > 0) {
+            Log.e("TAG", "IAD:" + SysteHelper.convert.bcdToStr(IAD));
+        }
+
+
+        // TVR
+        byte[] tvr = SysteHelper.emv.getTlv(0x95);
+        if (tvr != null && tvr.length > 0) {
+            Log.e("TAG", "TVR:" + SysteHelper.convert.bcdToStr(tvr));
+        }
+
+        // TSI
+        byte[] tsi = SysteHelper.emv.getTlv(0x9b);
+        if (tsi != null && tsi.length > 0) {
+            Log.e("TAG", "tsi:" + SysteHelper.convert.bcdToStr(tsi));
+        }
+
+    }
+
+    @SuppressLint({"SimpleDateFormat", "DefaultLocale"})
+    private InputParam toInputParam() {
+        Log.d(LOG_TAG, "DeviceController2.toInputParam >>>  ");
+        InputParam inputParam = new InputParam();
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+        Date date = new Date(System.currentTimeMillis());
+        String time = dateFormat.format(date);
+        Log.d(LOG_TAG, "DeviceController2.toInputParam >>> time=" + time);
+        if (time != null) {
+            inputParam.setTransDate(time.substring(0, 8));
+            inputParam.setTransTime(time.substring(8, 14));
+        }
+        inputParam.setChannelType(channelType);
+        inputParam.setIsNeedInputAmounts(true);
+        inputParam.setAmount(String.format("%012d", Long.parseLong(amount)));
+        inputParam.setCashBackAmount("0");
+        inputParam.setTag9CValue((byte) transType);// Tag9c的值
+        inputParam.setIsSupportSM(true);// 是否支持国密算法
+        inputParam.setIsCardAuth(true);// 是否执行脱机数据认证 0:不执行 ,1执行
+        inputParam.setIsForceOnline(true);// 是否强制联机
+        inputParam.setIsSupportEC(false);// 是否支持电子现金交易,需要根据实际情况设置,默认false
+        inputParam.setIsSupportCvm(true);// 是否支持CVM 非指定账户圈存、电子现金圈存 不执行
+        inputParam.setTransTraceNo(String.format("%06d", 0));
+
+        return inputParam;
+    }
+
+    private void setEmvConfig() throws EmvException {
+        Config cfg = SysteHelper.emv.getConfig();
+        cfg.setCapability(new byte[]{(byte) 0xE0, (byte) 0xF8, (byte) 0xC8});
+        cfg.setCountryCode(new byte[]{0x01, 0x56});
+        cfg.setExCapability(new byte[]{(byte) 0xe0, 0x00, (byte) 0xF0, (byte) 0xA0, 0x01});
+        cfg.setForceOnline((byte) 0);
+        cfg.setGetDataPIN((byte) 1);
+        cfg.setMerchCateCode(new byte[]{0x00, 0x00});
+        cfg.setReferCurrCode(new byte[]{0x01, 0x56});
+        cfg.setReferCurrCon(1000);
+        cfg.setReferCurrExp((byte) 0x02);
+        cfg.setSurportPSESel((byte) 1);
+        cfg.setTermType((byte) 0x22);
+        cfg.setTransCurrCode(new byte[]{0x01, 0x56});
+        cfg.setTransCurrExp((byte) 0x02);
+        cfg.setTransType((byte) 0x02);
+        cfg.setTermId("00000000");
+        cfg.setMerchId("000000000000000");
+        cfg.setMerchName("");// 读取商户名称
+        cfg.setTermAIP(new byte[]{0x7c, 0x00});
+        cfg.setBypassPin((byte) 1); // 输密码支持bypass
+        SysteHelper.emv.setConfig(cfg);
+    }
+
+    private int setPbocParam() {
+
+        ReaderParam readerParam = SysteHelper.emv.getReaderParam(EKernelType.PBOC);
+        if (readerParam != null) {
+
+            readerParam.setMerchCatCode(new byte[]{0x00, 0x00});
+            readerParam.setTermCap(new byte[]{(byte) 0xE0, (byte) 0xe1, (byte) 0xc8});
+            readerParam.setTermCapAd(new byte[]{(byte) 0xe0, 0x00, (byte) 0xf0, (byte) 0xa0, 0x01});
+            readerParam.setTermType((byte) 0x22);
+            readerParam.setTermCountryCode(new byte[]{0x01, 0x56});
+            readerParam.setTermRefCurCode(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCur(new byte[]{0x01, 0x56});
+
+            SysteHelper.emv.setReaderParam(EKernelType.PBOC, readerParam);
+        }
+
+        InputPBOCParam pbocParam = new InputPBOCParam();
+
+        pbocParam.setTermFlmt(100000);
+        pbocParam.setTTQ(new byte[]{0x36, 0x00, 0x00, 0x00});
+
+        SysteHelper.emv.contactlessParameterSetPBOC(pbocParam);
+        return 0;
+    }
+
+    private int setPassParam() {
+
+        ReaderParam readerParam = SysteHelper.emv.getReaderParam(EKernelType.MC);
+        if (readerParam != null) {
+
+            readerParam.setReferCurrCon(1000);
+            readerParam.setMerchNameLoc("PAX EMV LIBRARY".getBytes());
+            readerParam.setMerchCatCode(new byte[]{0x00, 0x01});
+            readerParam.setAcquierId(new byte[]{0x00, 0x00, 0x00, 0x12, 0x34, 0x56});
+            readerParam.setTermType((byte) 0x22);
+            readerParam.setTermCap(new byte[]{(byte) 0xE8, (byte) 0x20, (byte) 0xc8});
+            readerParam.setTermCapAd(new byte[]{(byte) 0xFF, 0x00, (byte) 0x10, (byte) 0x30, 0x01});
+            readerParam.setTermCountryCode(new byte[]{0x01, 0x56});
+            readerParam.setTermRefCurCode(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCur(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCurExp((byte) 0x02);
+
+            SysteHelper.emv.setReaderParam(EKernelType.MC, readerParam);
+        }
+
+        InputPayPassParam passParam = new InputPayPassParam();
+
+        List<TagPresent> tpList = new ArrayList<TagPresent>();
+        TagPresent tp = null;
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x04}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x05}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x2d}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0x9F, 0x5C}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x10}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x12}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x02}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x03}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x27}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        tp = new TagPresent();
+        tp.setTag(new BigInteger(new byte[]{(byte) 0xdf, (byte) 0x81, 0x30}).intValue());
+        tp.setPresent((byte) 0);
+        tpList.add(tp);
+
+        passParam.setTagPresents(tpList);
+        SysteHelper.emv.contactlessParameterSetPass(passParam);
+        return 0;
+    }
+
+    private int setWaveParam() {
+
+        ReaderParam readerParam = SysteHelper.emv.getReaderParam(EKernelType.VIS);
+        if (readerParam != null) {
+
+            readerParam.setReferCurrCon(1000);
+            readerParam.setMerchNameLoc("PAX EMV LIBRARY".getBytes());
+            readerParam.setMerchCatCode(new byte[]{0x00, 0x01});
+            readerParam.setAcquierId(new byte[]{0x00, 0x00, 0x00, 0x12, 0x34, 0x56});
+            readerParam.setTermType((byte) 0x22);
+            readerParam.setTermCap(new byte[]{(byte) 0xE8, (byte) 0x20, (byte) 0xc8});
+            readerParam.setTermCapAd(new byte[]{(byte) 0xFF, 0x00, (byte) 0x10, (byte) 0x30, 0x01});
+            readerParam.setTermCountryCode(new byte[]{0x01, 0x56});
+            readerParam.setTermRefCurCode(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCur(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCurExp((byte) 0x02);
+
+            SysteHelper.emv.setReaderParam(EKernelType.VIS, readerParam);
+        }
+
+        InputPayWaveParam waveParam = new InputPayWaveParam();
+        waveParam.setTermFlmt(100000);
+        waveParam.setDomesticOnly((byte) 0x00);
+        waveParam.setEnDDAVerNo((byte) 0x00);
+        waveParam.setTTQ(new byte[]{0x26, 0x00, 0x00, 0x00});
+        waveParam.setCvmReq(new byte[]{0x00});
+        SysteHelper.emv.contactlessParameterSetWave(waveParam);
+
+        return 0;
+    }
+
+    private int setAEParam() {
+        ReaderParam readerParam = SysteHelper.emv.getReaderParam(EKernelType.PBOC);
+        if (readerParam != null) {
+            readerParam.setMerchCatCode(new byte[]{0x00, 0x00});
+            readerParam.setTermCap(new byte[]{(byte) 0xE0, (byte) 0xe1, (byte) 0xc8});
+            readerParam.setTermCapAd(new byte[]{(byte) 0xe0, 0x00, (byte) 0xf0, (byte) 0xa0, 0x01});
+            readerParam.setTermType((byte) 0x22);
+            readerParam.setTermCountryCode(new byte[]{0x01, 0x56});
+            readerParam.setTermRefCurCode(new byte[]{0x01, 0x56});
+            readerParam.setTermTransCur(new byte[]{0x01, 0x56});
+            SysteHelper.emv.setReaderParam(EKernelType.AE, readerParam);
+        }
+        return 0;
+    }
+}

+ 109 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/trade/IDeviceController.java

@@ -0,0 +1,109 @@
+package com.yijia.handpos.device.trade;
+
+import android.content.Context;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 设备读卡/输密/计算MAC 接口
+ */
+public abstract class IDeviceController {
+
+    // 读卡数据
+    protected Map<String, String> cardInfoMap = new HashMap<>();
+
+    public void addCardInfoMap(String key, String value) {
+        cardInfoMap.put(key, value);
+    }
+
+    /**
+     * 上下文
+     */
+    protected Context mContext;
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+    /**
+     * 个性化数据,不同的设备传不一样的值
+     */
+    protected Map<String, String> personalizedMap;
+
+    public void addPersonalizedMap(String key, String value) {
+        personalizedMap.put(key, value);
+    }
+
+    /**
+     * 初始化
+     *
+     * @param ctx
+     */
+    public void init(Context ctx) {
+        mContext = ctx.getApplicationContext();
+        personalizedMap = new HashMap<>();
+        deviceType = setDeviceType();
+    }
+
+    /**
+     * 获取设备类型
+     *
+     * @return 设备类型
+     */
+    public abstract String setDeviceType();
+
+    /**
+     * 初始化读卡流程
+     *
+     * @param transactionType 交易类型
+     *                        交易{@link com.pax.device.constants.FieldDefinition#CONSUME}
+     *                        撤消 {@link com.pax.device.constants.FieldDefinition#CONSUME_CANCEL}
+     * @param listener        读卡回调
+     */
+    public abstract void tradeInit(String transactionType, TradeListener listener);
+
+
+    /**
+     * 开始读卡 刷卡/插卡/非接
+     *
+     * @param money   金额 (分)
+     * @param time    时间
+     * @param transNo 流水号
+     */
+    public abstract void startTrade(String money, String time, String transNo);
+
+    /**
+     * 停止刷卡
+     */
+    public abstract void stopTrade();
+
+    /**
+     * 输入密码,
+     */
+    public abstract void getPinInput();
+
+    /**
+     * 带卡号输入密码
+     */
+    public abstract void getPinInput(String cardNo);
+
+    /**
+     * 计算mac 地址
+     *
+     * @param data
+     * @param start
+     * @param end
+     */
+    public abstract byte[] calcMac(byte[] data, int start, int end);
+
+    /**
+     * 获取设备类型
+     *
+     * @return 设备类型
+     */
+    public String getDeviceType() {
+        return deviceType;
+    }
+
+
+}

+ 56 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/trade/TradeListener.java

@@ -0,0 +1,56 @@
+package com.yijia.handpos.device.trade;
+
+import java.util.Map;
+
+/**
+ * 交易监听
+ */
+public interface TradeListener {
+
+    /**
+     * 开始寻卡
+     */
+    void onStartSearchCard();
+
+    /**
+     * 停止寻卡(主动停止时回调)
+     */
+    void onStopSearchCard();
+
+    /**
+     * 搜索到卡
+     *
+     * @param type 卡类型
+     */
+    void onFoundCard(int type);
+
+    /**
+     * 返回卡数据
+     * card_no 卡号
+     * track1 磁道1数据
+     * track2 磁道2数据
+     * track3 磁道3数据
+     * expirt_date 卡有效期
+     * serial 卡序列号
+     * iccarddata 55域数据
+     *
+     * @param cardInfo 卡数据
+     */
+    void onReturnCardInfo(Map<String, String> cardInfo);
+
+    /**
+     * 返回密码数据
+     *
+     * @param password 密文密码
+     */
+    void onReturnPin(String password);
+
+    /**
+     * 返回错误
+     *
+     * @param code 0x9876 操作超时
+     * @param msg
+     */
+    void onError(int code, String msg);
+
+}

+ 81 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/utils/PanUtils.java

@@ -0,0 +1,81 @@
+package com.yijia.handpos.device.utils;
+
+public class PanUtils {
+    public static enum EPanMode {
+        X9_8_WITH_PAN,
+        X9_8_NO_PAN,
+    }
+
+    /**
+     * 按各个银行要求处理卡号
+     * 
+     * @param pan
+     * @param mode
+     * @return
+     */
+    public static String getPanBlock(String pan, EPanMode mode) {
+        String panBlock = null;
+        if (pan == null || pan.length() < 13 || pan.length() > 19) {
+            return null;
+        }
+        switch (mode) {
+            case X9_8_WITH_PAN:
+                panBlock = "0000" + pan.substring(pan.length() - 13, pan.length() - 1);
+                break;
+            case X9_8_NO_PAN:
+                panBlock = "0000000000000000";
+                break;
+
+            default:
+                break;
+        }
+
+        return panBlock;
+    }
+
+    /**
+     * 空格分隔卡号
+     * 
+     * @param cardNo
+     * @return
+     */
+    public static String separateWithSpace(String cardNo) {
+        if (cardNo == null)
+            return null;
+
+        String temp = "";
+        int total = cardNo.length() / 4;
+        for (int i = 0; i < total; i++) {
+            temp += cardNo.substring(i * 4, i * 4 + 4);
+            if (i != (total - 1)) {
+                temp += " ";
+            }
+        }
+        if (total * 4 < cardNo.length()) {
+            temp += " " + cardNo.substring(total * 4, cardNo.length());
+        }
+
+        return temp;
+    }
+
+    /**
+     * 前6后4, 其他显示“*”
+     * 
+     * @param cardNo
+     * @return
+     */
+    public static String maskedCardNo(String cardNo) {
+        char[] tempNum = cardNo.toCharArray();
+        int cardLength = tempNum.length;
+        // 验证:16-20位数字
+        if (cardLength < 13)
+            return null;
+
+        for (int i = 0; i < cardLength; i++) {
+            if ((i + 1 > 6) && (i < cardLength - 4)) {
+                tempNum[i] = '*';
+            }
+        }
+        return new String(tempNum);
+    }
+}

+ 81 - 0
HandPos/app/src/main/java/com/yijia/handpos/device/utils/TrackUtils.java

@@ -0,0 +1,81 @@
+package com.yijia.handpos.device.utils;
+
+public class TrackUtils {
+
+    /**
+     * 从磁道2数据中获取主帐号
+     * 
+     * @param track
+     * @return
+     * @date 2015年5月22日下午3:28:14
+     * @example
+     */
+    public static String getPan(String track) {
+        if (track == null)
+            return null;
+
+        int len = track.indexOf('=');
+        if (len < 0) {
+            len = track.indexOf('D');
+            if (len < 0)
+                return null;
+        }
+
+        if ((len < 13) || (len > 19))
+            return null;
+        return track.substring(0, len);
+    }
+
+    public static String getServiceCode(String track) {
+        // FIXME
+        return null;
+    }
+
+    /**
+     * 判定是否为IC卡
+     * 
+     * @param track
+     * @return
+     */
+    public static boolean isIcCard(String track) {
+        if (track == null)
+            return false;
+
+        int index = track.indexOf('=');
+        if (index < 0) {
+            index = track.indexOf('D');
+            if (index < 0)
+                return false;
+        }
+
+        if (index + 6 > track.length())
+            return false;
+
+        if ("2".equals(track.substring(index + 5, index + 6)) || "6".equals(track.substring(index + 5, index + 6))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 获取有效期
+     * 
+     * @param track
+     * @return
+     */
+    public static String getExpDate(String track) {
+        if (track == null)
+            return null;
+
+        int index = track.indexOf('=');
+        if (index < 0) {
+            index = track.indexOf('D');
+            if (index < 0)
+                return null;
+        }
+
+        if (index + 5 > track.length())
+            return null;
+        return track.substring(index + 1, index + 5);
+    }
+}

+ 85 - 0
HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/ConstellationAdapter.java

@@ -0,0 +1,85 @@
+package com.yijia.handpos.dropdownmenu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+
+import com.yijia.handpos.R;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+
+public class ConstellationAdapter extends BaseAdapter {
+
+    private Context context;
+    private List<String> list;
+    private int checkItemPosition = 0;
+
+    public void setCheckItem(int position) {
+        checkItemPosition = position;
+        notifyDataSetChanged();
+    }
+
+    public ConstellationAdapter(Context context, List<String> list) {
+        this.context = context;
+        this.list = list;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder;
+        if (convertView != null) {
+            viewHolder = (ViewHolder) convertView.getTag();
+        } else {
+            convertView = LayoutInflater.from(context).inflate(R.layout.item_constellation_layout, null);
+            viewHolder = new ViewHolder(convertView);
+            convertView.setTag(viewHolder);
+        }
+        fillValue(position, viewHolder);
+        return convertView;
+    }
+
+    private void fillValue(int position, ViewHolder viewHolder) {
+        viewHolder.mText.setText(list.get(position));
+        if (checkItemPosition != -1) {
+            if (checkItemPosition == position) {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_selected));
+                viewHolder.mText.setBackgroundResource(R.drawable.check_bg);
+            } else {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_unselected));
+                viewHolder.mText.setBackgroundResource(R.drawable.uncheck_bg);
+            }
+        }
+    }
+
+    static class ViewHolder {
+        @BindView(R.id.text)
+        TextView mText;
+
+        ViewHolder(View view) {
+            ButterKnife.bind(this, view);
+        }
+    }
+}

+ 49 - 0
HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/DeviceUtils.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me>
+ * 
+ * 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
+ * 
+ *   http://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.
+ */
+
+package com.yijia.handpos.dropdownmenu;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * 设备工具箱,提供与设备硬件相关的工具方法
+ */
+public class DeviceUtils {
+
+	/**
+	 * 获取屏幕尺寸
+	 */
+	@SuppressWarnings("deprecation")
+	@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+	public static Point getScreenSize(Context context){
+		WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+		Display display = windowManager.getDefaultDisplay();
+		if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR2){
+			return new Point(display.getWidth(), display.getHeight());
+		}else{
+			Point point = new Point();
+			display.getSize(point);
+			return point;
+		}
+	}
+
+
+}

+ 265 - 0
HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/DropDownMenu.java

@@ -0,0 +1,265 @@
+package com.yijia.handpos.dropdownmenu;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.yijia.handpos.R;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+
+/**
+ * Created by dongjunkun on 2015/6/17.
+ */
+public class DropDownMenu extends LinearLayout {
+
+    //顶部菜单布局
+    private LinearLayout tabMenuView;
+    //底部容器,包含popupMenuViews,maskView
+    private FrameLayout containerView;
+    //弹出菜单父布局
+    private FrameLayout popupMenuViews;
+    //遮罩半透明View,点击可关闭DropDownMenu
+    private View maskView;
+    //tabMenuView里面选中的tab位置,-1表示未选中
+    private int current_tab_position = -1;
+
+    //分割线颜色
+    private int dividerColor = 0xffcccccc;
+    //tab选中颜色
+    private int textSelectedColor = 0xff890c85;
+    //tab未选中颜色
+    private int textUnselectedColor = 0xff111111;
+    //遮罩颜色
+    private int maskColor = 0x88888888;
+    //tab字体大小
+    private int menuTextSize = 14;
+
+    //tab选中图标
+    private int menuSelectedIcon;
+    //tab未选中图标
+    private int menuUnselectedIcon;
+
+    private float menuHeighPercent = 0.5f;
+
+
+    public DropDownMenu(Context context) {
+        super(context, null);
+    }
+
+    public DropDownMenu(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DropDownMenu(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        setOrientation(VERTICAL);
+
+        //为DropDownMenu添加自定义属性
+        int menuBackgroundColor = 0xffffffff;
+        int underlineColor = 0xffcccccc;
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DropDownMenu);
+        underlineColor = a.getColor(R.styleable.DropDownMenu_ddunderlineColor, underlineColor);
+        dividerColor = a.getColor(R.styleable.DropDownMenu_dddividerColor, dividerColor);
+        textSelectedColor = a.getColor(R.styleable.DropDownMenu_ddtextSelectedColor, textSelectedColor);
+        textUnselectedColor = a.getColor(R.styleable.DropDownMenu_ddtextUnselectedColor, textUnselectedColor);
+        menuBackgroundColor = a.getColor(R.styleable.DropDownMenu_ddmenuBackgroundColor, menuBackgroundColor);
+        maskColor = a.getColor(R.styleable.DropDownMenu_ddmaskColor, maskColor);
+        menuTextSize = a.getDimensionPixelSize(R.styleable.DropDownMenu_ddmenuTextSize, menuTextSize);
+        menuSelectedIcon = a.getResourceId(R.styleable.DropDownMenu_ddmenuSelectedIcon, menuSelectedIcon);
+        menuUnselectedIcon = a.getResourceId(R.styleable.DropDownMenu_ddmenuUnselectedIcon, menuUnselectedIcon);
+        menuHeighPercent = a.getFloat(R.styleable.DropDownMenu_ddmenuMenuHeightPercent,menuHeighPercent);
+        a.recycle();
+
+        //初始化tabMenuView并添加到tabMenuView
+        tabMenuView = new LinearLayout(context);
+        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        tabMenuView.setOrientation(HORIZONTAL);
+        tabMenuView.setBackgroundColor(menuBackgroundColor);
+        tabMenuView.setLayoutParams(params);
+        addView(tabMenuView, 0);
+
+        //为tabMenuView添加下划线
+        View underLine = new View(getContext());
+        underLine.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpTpPx(1.0f)));
+        underLine.setBackgroundColor(underlineColor);
+        addView(underLine, 1);
+
+        //初始化containerView并将其添加到DropDownMenu
+        containerView = new FrameLayout(context);
+        containerView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+        addView(containerView, 2);
+
+    }
+
+    /**
+     * 初始化DropDownMenu
+     *
+     * @param tabTexts
+     * @param popupViews
+     * @param contentView
+     */
+    public void setDropDownMenu(@NonNull List<String> tabTexts, @NonNull List<View> popupViews, @NonNull View contentView) {
+        if (tabTexts.size() != popupViews.size()) {
+            throw new IllegalArgumentException("params not match, tabTexts.size() should be equal popupViews.size()");
+        }
+
+        for (int i = 0; i < tabTexts.size(); i++) {
+            addTab(tabTexts, i);
+        }
+
+        containerView.addView(contentView, 0);
+
+        maskView = new View(getContext());
+        maskView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT));
+        maskView.setBackgroundColor(maskColor);
+        maskView.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                closeMenu();
+            }
+        });
+        containerView.addView(maskView, 1);
+        maskView.setVisibility(GONE);
+        if (containerView.getChildAt(2) != null){
+            containerView.removeViewAt(2);
+        }
+
+        popupMenuViews = new FrameLayout(getContext());
+        popupMenuViews.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (DeviceUtils.getScreenSize(getContext()).y*menuHeighPercent)));
+        popupMenuViews.setVisibility(GONE);
+        containerView.addView(popupMenuViews, 2);
+
+        for (int i = 0; i < popupViews.size(); i++) {
+            popupViews.get(i).setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+            popupMenuViews.addView(popupViews.get(i), i);
+        }
+
+    }
+
+    private void addTab(@NonNull List<String> tabTexts, int i) {
+        final TextView tab = new TextView(getContext());
+        tab.setSingleLine();
+        tab.setEllipsize(TextUtils.TruncateAt.END);
+        tab.setGravity(Gravity.CENTER);
+        tab.setTextSize(TypedValue.COMPLEX_UNIT_PX,menuTextSize);
+        tab.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f));
+        tab.setTextColor(textUnselectedColor);
+        tab.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(menuUnselectedIcon), null);
+        tab.setText(tabTexts.get(i));
+        tab.setPadding(dpTpPx(5), dpTpPx(12), dpTpPx(5), dpTpPx(12));
+        //添加点击事件
+        tab.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                switchMenu(tab);
+            }
+        });
+        tabMenuView.addView(tab);
+        //添加分割线
+        if (i < tabTexts.size() - 1) {
+            View view = new View(getContext());
+            view.setLayoutParams(new LayoutParams(dpTpPx(0.5f), ViewGroup.LayoutParams.MATCH_PARENT));
+            view.setBackgroundColor(dividerColor);
+            tabMenuView.addView(view);
+        }
+    }
+
+    /**
+     * 改变tab文字
+     *
+     * @param text
+     */
+    public void setTabText(String text) {
+        if (current_tab_position != -1) {
+            ((TextView) tabMenuView.getChildAt(current_tab_position)).setText(text);
+        }
+    }
+
+    public void setTabClickable(boolean clickable) {
+        for (int i = 0; i < tabMenuView.getChildCount(); i = i + 2) {
+            tabMenuView.getChildAt(i).setClickable(clickable);
+        }
+    }
+
+    /**
+     * 关闭菜单
+     */
+    public void closeMenu() {
+        if (current_tab_position != -1) {
+            ((TextView) tabMenuView.getChildAt(current_tab_position)).setTextColor(textUnselectedColor);
+            ((TextView) tabMenuView.getChildAt(current_tab_position)).setCompoundDrawablesWithIntrinsicBounds(null, null,
+                    getResources().getDrawable(menuUnselectedIcon), null);
+            popupMenuViews.setVisibility(View.GONE);
+            popupMenuViews.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_menu_out));
+            maskView.setVisibility(GONE);
+            maskView.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_mask_out));
+            current_tab_position = -1;
+        }
+
+    }
+
+    /**
+     * DropDownMenu是否处于可见状态
+     *
+     * @return
+     */
+    public boolean isShowing() {
+        return current_tab_position != -1;
+    }
+
+    /**
+     * 切换菜单
+     *
+     * @param target
+     */
+    private void switchMenu(View target) {
+        System.out.println(current_tab_position);
+        for (int i = 0; i < tabMenuView.getChildCount(); i = i + 2) {
+            if (target == tabMenuView.getChildAt(i)) {
+                if (current_tab_position == i) {
+                    closeMenu();
+                } else {
+                    if (current_tab_position == -1) {
+                        popupMenuViews.setVisibility(View.VISIBLE);
+                        popupMenuViews.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_menu_in));
+                        maskView.setVisibility(VISIBLE);
+                        maskView.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_mask_in));
+                        popupMenuViews.getChildAt(i / 2).setVisibility(View.VISIBLE);
+                    } else {
+                        popupMenuViews.getChildAt(i / 2).setVisibility(View.VISIBLE);
+                    }
+                    current_tab_position = i;
+                    ((TextView) tabMenuView.getChildAt(i)).setTextColor(textSelectedColor);
+                    ((TextView) tabMenuView.getChildAt(i)).setCompoundDrawablesWithIntrinsicBounds(null, null,
+                            getResources().getDrawable(menuSelectedIcon), null);
+                }
+            } else {
+                ((TextView) tabMenuView.getChildAt(i)).setTextColor(textUnselectedColor);
+                ((TextView) tabMenuView.getChildAt(i)).setCompoundDrawablesWithIntrinsicBounds(null, null,
+                        getResources().getDrawable(menuUnselectedIcon), null);
+                popupMenuViews.getChildAt(i / 2).setVisibility(View.GONE);
+            }
+        }
+    }
+
+    public int dpTpPx(float value) {
+        DisplayMetrics dm = getResources().getDisplayMetrics();
+        return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm) + 0.5);
+    }
+}

+ 85 - 0
HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/GirdDropDownAdapter.java

@@ -0,0 +1,85 @@
+package com.yijia.handpos.dropdownmenu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+
+import com.yijia.handpos.R;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+
+public class GirdDropDownAdapter extends BaseAdapter {
+
+    private Context context;
+    private List<String> list;
+    private int checkItemPosition = 0;
+
+    public void setCheckItem(int position) {
+        checkItemPosition = position;
+        notifyDataSetChanged();
+    }
+
+    public GirdDropDownAdapter(Context context, List<String> list) {
+        this.context = context;
+        this.list = list;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder;
+        if (convertView != null) {
+            viewHolder = (ViewHolder) convertView.getTag();
+        } else {
+            convertView = LayoutInflater.from(context).inflate(R.layout.item_list_drop_down, null);
+            viewHolder = new ViewHolder(convertView);
+            convertView.setTag(viewHolder);
+        }
+        fillValue(position, viewHolder);
+        return convertView;
+    }
+
+    private void fillValue(int position, ViewHolder viewHolder) {
+        viewHolder.mText.setText(list.get(position));
+        if (checkItemPosition != -1) {
+            if (checkItemPosition == position) {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_selected));
+                viewHolder.mText.setCompoundDrawablesWithIntrinsicBounds(null, null, context.getResources().getDrawable(R.mipmap.drop_down_checked), null);
+            } else {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_unselected));
+                viewHolder.mText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+            }
+        }
+    }
+
+    static class ViewHolder {
+        @BindView(R.id.text)
+        TextView mText;
+
+        ViewHolder(View view) {
+            ButterKnife.bind(this, view);
+        }
+    }
+}

+ 84 - 0
HandPos/app/src/main/java/com/yijia/handpos/dropdownmenu/ListDropDownAdapter.java

@@ -0,0 +1,84 @@
+package com.yijia.handpos.dropdownmenu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+
+import com.yijia.handpos.R;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+
+public class ListDropDownAdapter extends BaseAdapter {
+
+    private Context context;
+    private List<String> list;
+    private int checkItemPosition = 0;
+
+    public void setCheckItem(int position) {
+        checkItemPosition = position;
+        notifyDataSetChanged();
+    }
+
+    public ListDropDownAdapter(Context context, List<String> list) {
+        this.context = context;
+        this.list = list;
+    }
+
+    @Override
+    public int getCount() {
+        return list.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return null;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ViewHolder viewHolder;
+        if (convertView != null) {
+            viewHolder = (ViewHolder) convertView.getTag();
+        } else {
+            convertView = LayoutInflater.from(context).inflate(R.layout.item_default_drop_down, null);
+            viewHolder = new ViewHolder(convertView);
+            convertView.setTag(viewHolder);
+        }
+        fillValue(position, viewHolder);
+        return convertView;
+    }
+
+    private void fillValue(int position, ViewHolder viewHolder) {
+        viewHolder.mText.setText(list.get(position));
+        if (checkItemPosition != -1) {
+            if (checkItemPosition == position) {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_selected));
+                viewHolder.mText.setBackgroundResource(R.color.check_bg);
+            } else {
+                viewHolder.mText.setTextColor(context.getResources().getColor(R.color.drop_down_unselected));
+                viewHolder.mText.setBackgroundResource(R.color.white);
+            }
+        }
+    }
+
+    static class ViewHolder {
+        @BindView(R.id.text)
+        TextView mText;
+        ViewHolder(View view) {
+            ButterKnife.bind(this, view);
+        }
+    }
+}

+ 74 - 0
HandPos/app/src/main/java/com/yijia/handpos/entity/ApiResult.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * 提供的默认的标注返回api
+ *
+ * @author xuexiang
+ * @since 2018/5/22 下午4:22
+ */
+public class ApiResult<T> {
+    public final static String CODE = "Code";
+    public final static String MSG = "Msg";
+    public final static String DATA = "Data";
+
+    @SerializedName(value = CODE, alternate = {"code"})
+    private int Code;
+    @SerializedName(value = MSG, alternate = {"msg"})
+    private String Msg;
+    @SerializedName(value = DATA, alternate = {"data"})
+    private T Data;
+
+    public int getCode() {
+        return Code;
+    }
+
+    public ApiResult setCode(int code) {
+        Code = code;
+        return this;
+    }
+
+    public String getMsg() {
+        return Msg;
+    }
+
+    public ApiResult setMsg(String msg) {
+        Msg = msg;
+        return this;
+    }
+
+    public T getData() {
+        return Data;
+    }
+
+    public ApiResult setData(T data) {
+        Data = data;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "ApiResult{" +
+                "Code='" + Code + '\'' +
+                ", Msg='" + Msg + '\'' +
+                ", Data=" + Data +
+                '}';
+    }
+}

+ 187 - 0
HandPos/app/src/main/java/com/yijia/handpos/entity/AppVersionInfo.java

@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.entity;
+
+/**
+ * XUpdateService返回的api
+ *
+ * @author xuexiang
+ * @since 2018/7/30 上午11:36
+ */
+public class AppVersionInfo {
+    
+    private Integer versionId;
+
+    private Integer updateStatus;
+
+    private Integer versionCode;
+
+    private String versionName;
+
+    private String uploadTime;
+
+    private Integer apkSize;
+
+    private String appKey;
+
+    private String modifyContent;
+
+    private String downloadUrl;
+
+    private String apkMd5;
+
+    /**
+     * @return version_id
+     */
+    public Integer getVersionId() {
+        return versionId;
+    }
+
+    /**
+     * @param versionId
+     */
+    public void setVersionId(Integer versionId) {
+        this.versionId = versionId;
+    }
+
+    /**
+     * @return update_status
+     */
+    public Integer getUpdateStatus() {
+        return updateStatus;
+    }
+
+    /**
+     * @param updateStatus
+     */
+    public AppVersionInfo setUpdateStatus(Integer updateStatus) {
+        this.updateStatus = updateStatus;
+        return this;
+    }
+
+    /**
+     * @return version_code
+     */
+    public Integer getVersionCode() {
+        return versionCode;
+    }
+
+    /**
+     * @param versionCode
+     */
+    public void setVersionCode(Integer versionCode) {
+        this.versionCode = versionCode;
+    }
+
+    /**
+     * @return version_name
+     */
+    public String getVersionName() {
+        return versionName;
+    }
+
+    /**
+     * @param versionName
+     */
+    public void setVersionName(String versionName) {
+        this.versionName = versionName;
+    }
+
+    /**
+     * @return upload_time
+     */
+    public String getUploadTime() {
+        return uploadTime;
+    }
+
+    /**
+     * @param uploadTime
+     */
+    public void setUploadTime(String uploadTime) {
+        this.uploadTime = uploadTime;
+    }
+
+    /**
+     * @return apk_size
+     */
+    public Integer getApkSize() {
+        return apkSize;
+    }
+
+    /**
+     * @param apkSize
+     */
+    public void setApkSize(Integer apkSize) {
+        this.apkSize = apkSize;
+    }
+
+    /**
+     * @return app_key
+     */
+    public String getAppKey() {
+        return appKey;
+    }
+
+    /**
+     * @param appKey
+     */
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    /**
+     * @return modify_content
+     */
+    public String getModifyContent() {
+        return modifyContent;
+    }
+
+    /**
+     * @param modifyContent
+     */
+    public void setModifyContent(String modifyContent) {
+        this.modifyContent = modifyContent;
+    }
+
+    /**
+     * @return download_url
+     */
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    /**
+     * @param downloadUrl
+     */
+    public void setDownloadUrl(String downloadUrl) {
+        this.downloadUrl = downloadUrl;
+    }
+
+    /**
+     * @return apk_md5
+     */
+    public String getApkMd5() {
+        return apkMd5;
+    }
+
+    /**
+     * @param apkMd5
+     */
+    public void setApkMd5(String apkMd5) {
+        this.apkMd5 = apkMd5;
+    }
+}

+ 43 - 0
HandPos/app/src/main/java/com/yijia/handpos/entity/CustomResult.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.entity;
+
+import java.io.Serializable;
+
+/**
+ * 自定义版本检查的结果
+ *
+ * @author xuexiang
+ * @since 2018/7/11 上午1:03
+ */
+public class CustomResult implements Serializable {
+
+    public boolean hasUpdate;
+
+    public boolean isIgnorable;
+
+    public int versionCode;
+
+    public String versionName;
+
+    public String updateLog;
+
+    public String apkUrl;
+
+    public long apkSize;
+
+}

+ 148 - 0
HandPos/app/src/main/java/com/yijia/handpos/http/OKHttpUpdateHttpService.java

@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.http;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xupdate.proxy.IUpdateHttpService;
+import com.xuexiang.xupdate.utils.UpdateUtils;
+import com.zhy.http.okhttp.OkHttpUtils;
+import com.zhy.http.okhttp.callback.FileCallBack;
+import com.zhy.http.okhttp.callback.StringCallback;
+import com.zhy.http.okhttp.request.RequestCall;
+
+import java.io.File;
+import java.util.Map;
+import java.util.TreeMap;
+
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.Request;
+
+/**
+ * 使用okhttp
+ *
+ * @author xuexiang
+ * @since 2018/7/10 下午4:04
+ */
+public class OKHttpUpdateHttpService implements IUpdateHttpService {
+
+    private boolean mIsPostJson;
+
+    public OKHttpUpdateHttpService() {
+        this(false);
+    }
+
+    public OKHttpUpdateHttpService(boolean isPostJson) {
+        mIsPostJson = isPostJson;
+    }
+
+
+    @Override
+    public void asyncGet(@NonNull String url, @NonNull Map<String, Object> params, final @NonNull Callback callBack) {
+        OkHttpUtils.get()
+                .url(url)
+                .params(transform(params))
+                .build()
+                .execute(new StringCallback() {
+                    @Override
+                    public void onError(Call call, Exception e, int id) {
+                        callBack.onError(e);
+                    }
+
+                    @Override
+                    public void onResponse(String response, int id) {
+                        callBack.onSuccess(response);
+                    }
+                });
+    }
+
+    @Override
+    public void asyncPost(@NonNull String url, @NonNull Map<String, Object> params, final @NonNull Callback callBack) {
+        //这里默认post的是Form格式,使用json格式的请修改 post -> postString
+        RequestCall requestCall;
+        if (mIsPostJson) {
+            requestCall = OkHttpUtils.postString()
+                    .url(url)
+                    .content(UpdateUtils.toJson(params))
+                    .mediaType(MediaType.parse("application/json; charset=utf-8"))
+                    .build();
+        } else {
+            requestCall = OkHttpUtils.post()
+                    .url(url)
+                    .params(transform(params))
+                    .build();
+        }
+        requestCall
+                .execute(new StringCallback() {
+                    @Override
+                    public void onError(Call call, Exception e, int id) {
+                        callBack.onError(e);
+                    }
+
+                    @Override
+                    public void onResponse(String response, int id) {
+                        callBack.onSuccess(response);
+                    }
+                });
+    }
+
+    @Override
+    public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, final @NonNull DownloadCallback callback) {
+        OkHttpUtils.get()
+                .url(url)
+                .tag(url)
+                .build()
+                .execute(new FileCallBack(path, fileName) {
+                    @Override
+                    public void inProgress(float progress, long total, int id) {
+                        callback.onProgress(progress, total);
+                    }
+
+                    @Override
+                    public void onError(Call call, Exception e, int id) {
+                        callback.onError(e);
+                    }
+
+                    @Override
+                    public void onResponse(File response, int id) {
+                        callback.onSuccess(response);
+                    }
+
+                    @Override
+                    public void onBefore(Request request, int id) {
+                        super.onBefore(request, id);
+                        callback.onStart();
+                    }
+                });
+    }
+
+    @Override
+    public void cancelDownload(@NonNull String url) {
+        OkHttpUtils.getInstance().cancelTag(url);
+    }
+
+    private Map<String, String> transform(Map<String, Object> params) {
+        Map<String, String> map = new TreeMap<>();
+        for (Map.Entry<String, Object> entry : params.entrySet()) {
+            map.put(entry.getKey(), entry.getValue().toString());
+        }
+        return map;
+    }
+
+
+}

+ 124 - 0
HandPos/app/src/main/java/com/yijia/handpos/http/XHttpUpdateHttpService.java

@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
+ *
+ * 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
+ *
+ *       http://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.
+ */
+
+package com.yijia.handpos.http;
+
+import androidx.annotation.NonNull;
+
+import com.xuexiang.xhttp2.XHttp;
+import com.xuexiang.xhttp2.XHttpSDK;
+import com.xuexiang.xhttp2.callback.DownloadProgressCallBack;
+import com.xuexiang.xhttp2.callback.SimpleCallBack;
+import com.xuexiang.xhttp2.exception.ApiException;
+import com.xuexiang.xupdate.proxy.IUpdateHttpService;
+import com.xuexiang.xutil.file.FileUtils;
+import com.xuexiang.xutil.tip.ToastUtils;
+
+import java.util.Map;
+
+import io.reactivex.disposables.Disposable;
+
+/**
+ * XHttp2实现的请求更新
+ *
+ * @author xuexiang
+ * @since 2018/8/12 上午11:46
+ */
+public class XHttpUpdateHttpService implements IUpdateHttpService {
+
+    private String mBaseUrl;
+
+    public XHttpUpdateHttpService(String baseUrl) {
+        mBaseUrl = baseUrl;
+    }
+
+    @Override
+    public void asyncGet(@NonNull String url, @NonNull Map<String, Object> params, @NonNull final Callback callBack) {
+        XHttp.get(url)
+                .baseUrl(mBaseUrl)
+                .params(params)
+                .keepJson(true)
+                .execute(new SimpleCallBack<String>() {
+                    @Override
+                    public void onSuccess(String response) throws Throwable {
+                        callBack.onSuccess(response);
+                    }
+
+                    @Override
+                    public void onError(ApiException e) {
+                        callBack.onError(e);
+                    }
+                });
+    }
+
+    @Override
+    public void asyncPost(@NonNull String url, @NonNull Map<String, Object> params, @NonNull final Callback callBack) {
+        //这里默认post的是Form格式,使用json格式的请修改为 params -> upJson
+        XHttp.post(url)
+                .baseUrl(mBaseUrl)
+                .params(params)
+                .keepJson(true)
+                .execute(new SimpleCallBack<String>() {
+                    @Override
+                    public void onSuccess(String response) throws Throwable {
+                        callBack.onSuccess(response);
+                    }
+
+                    @Override
+                    public void onError(ApiException e) {
+                        callBack.onError(e);
+                    }
+                });
+    }
+
+    @Override
+    public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull final DownloadCallback callback) {
+        Disposable disposable = XHttp.downLoad(url)
+                .savePath(path)
+                .saveName(fileName)
+                .isUseBaseUrl(false)
+                .baseUrl(mBaseUrl)
+                .execute(new DownloadProgressCallBack<String>() {
+                    @Override
+                    public void onStart() {
+                        callback.onStart();
+                    }
+
+                    @Override
+                    public void onError(ApiException e) {
+                        callback.onError(e);
+                    }
+
+                    @Override
+                    public void update(long downLoadSize, long totalSize, boolean done) {
+                        callback.onProgress(downLoadSize / (float) totalSize, totalSize);
+                    }
+
+                    @Override
+                    public void onComplete(String path) {
+                        callback.onSuccess(FileUtils.getFileByPath(path));
+                    }
+                });
+
+        XHttpSDK.addRequest(url, disposable);
+    }
+
+    @Override
+    public void cancelDownload(@NonNull String url) {
+        ToastUtils.toast("已取消更新!");
+        XHttpSDK.cancelRequest(url);
+    }
+}

+ 38 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/ContentData.java

@@ -0,0 +1,38 @@
+package com.yijia.handpos.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class ContentData {
+    //小票样式公用参数
+    private String orderType;       //订单类型: 1、油品订单; 2、非油品订单; 3、积分订单;
+    private String orderNo;         //订单号
+    private String createdDate;     //下单时间
+    private String payDate;         //付款时间
+    private String stationName;     //油站名称
+    private String stationId;       //油站ID
+    private String transactionId;   //交易流水号
+
+    //油品订单参数
+    private String oilGun;          //油枪
+    private String oilName;         //油品
+    private String oilPirce;        //油品价格(单价)
+    private String orderLiters;     //订单加油升数
+    private String receivableAmt;   //应收金额
+    private String amt;             //实收金额
+    private String discountAmt;     //优惠金额
+    private String payType;         //支付方式  例:微信、电子卡
+    private String mobilePhone;     //手机号   例:186****5051
+
+    //积分小票
+    private String waresName;                //兑换商品
+    private String exchangeNum;              //商品数量
+    private String orderSumIntegral;         //消耗积分
+    private String surplusPoints;            //剩余积分
+    private String customerName;             //客户昵称
+    private String integral;                 //获得的积分:订单完成后所得的积分
+}

+ 204 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/Data.java

@@ -0,0 +1,204 @@
+package com.yijia.handpos.pojo;
+
+import java.util.List;
+
+public class Data {
+    private int userId;
+    private int deptId;
+    private String userName;
+    private String nickName;
+    private String phonenumber;
+    private String sex;
+    private String avatar;
+    private List<TimeList> timeList;
+    private List<ListSum> listSum;
+    private List<ListSumGroupByPayType> listSumGroupByPayType;
+    private List<ListSumGroupByOilName> listSumGroupByOilName;
+    private OrderList orderList;
+
+    private int deviceId;
+    private String deviceNo;
+    private String deviceName;
+    private int stationId;
+    private String stationName;
+    private String deviceType;
+    private String gunNo;
+    private String deviceStatus;
+    private String deviceFactory;
+    private String posFanoutExchange;
+    private String posQueue;
+
+
+    public int getUserId() {
+        return userId;
+    }
+
+    public void setUserId(int userId) {
+        this.userId = userId;
+    }
+
+    public int getDeptId() {
+        return deptId;
+    }
+
+    public void setDeptId(int deptId) {
+        this.deptId = deptId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public String getPhonenumber() {
+        return phonenumber;
+    }
+
+    public void setPhonenumber(String phonenumber) {
+        this.phonenumber = phonenumber;
+    }
+
+    public String getSex() {
+        return sex;
+    }
+
+    public void setSex(String sex) {
+        this.sex = sex;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public List<TimeList> getTimeList() {
+        return timeList;
+    }
+
+    public void setTimeList(List<TimeList> timeList) {
+        this.timeList = timeList;
+    }
+
+    public List<ListSum> getListSum() {
+        return listSum;
+    }
+
+    public void setListSum(List<ListSum> listSum) {
+        this.listSum = listSum;
+    }
+
+    public List<ListSumGroupByPayType> getListSumGroupByPayType() {
+        return listSumGroupByPayType;
+    }
+
+    public void setListSumGroupByPayType(List<ListSumGroupByPayType> listSumGroupByPayType) {
+        this.listSumGroupByPayType = listSumGroupByPayType;
+    }
+
+    public List<ListSumGroupByOilName> getListSumGroupByOilName() {
+        return listSumGroupByOilName;
+    }
+
+    public void setListSumGroupByOilName(List<ListSumGroupByOilName> listSumGroupByOilName) {
+        this.listSumGroupByOilName = listSumGroupByOilName;
+    }
+
+    public OrderList getOrderList() {
+        return orderList;
+    }
+
+    public void setOrderList(OrderList orderList) {
+        this.orderList = orderList;
+    }
+
+    public void setDeviceId(int deviceId) {
+        this.deviceId = deviceId;
+    }
+    public int getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceNo(String deviceNo) {
+        this.deviceNo = deviceNo;
+    }
+    public String getDeviceNo() {
+        return deviceNo;
+    }
+
+    public void setDeviceName(String deviceName) {
+        this.deviceName = deviceName;
+    }
+    public String getDeviceName() {
+        return deviceName;
+    }
+
+    public void setStationId(int stationId) {
+        this.stationId = stationId;
+    }
+    public int getStationId() {
+        return stationId;
+    }
+
+    public void setStationName(String stationName) {
+        this.stationName = stationName;
+    }
+    public String getStationName() {
+        return stationName;
+    }
+
+    public void setDeviceType(String deviceType) {
+        this.deviceType = deviceType;
+    }
+    public String getDeviceType() {
+        return deviceType;
+    }
+
+    public void setGunNo(String gunNo) {
+        this.gunNo = gunNo;
+    }
+    public String getGunNo() {
+        return gunNo;
+    }
+
+    public void setDeviceStatus(String deviceStatus) {
+        this.deviceStatus = deviceStatus;
+    }
+    public String getDeviceStatus() {
+        return deviceStatus;
+    }
+
+    public void setDeviceFactory(String deviceFactory) {
+        this.deviceFactory = deviceFactory;
+    }
+    public String getDeviceFactory() {
+        return deviceFactory;
+    }
+
+    public void setPosFanoutExchange(String posFanoutExchange) {
+        this.posFanoutExchange = posFanoutExchange;
+    }
+    public String getPosFanoutExchange() {
+        return posFanoutExchange;
+    }
+
+    public void setPosQueue(String posQueue) {
+        this.posQueue = posQueue;
+    }
+    public String getPosQueue() {
+        return posQueue;
+    }
+}

+ 39 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/DealList.java

@@ -0,0 +1,39 @@
+package com.yijia.handpos.pojo;
+
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class DealList {
+    private int orderId;
+    private String orderNo;
+    private String oilGun;
+    private String oilName;
+    private int consumerId;
+    private String consumer;
+    private double amt;
+    private int stationId;
+    private String status;
+    private String orderLiters;
+    private String payType;
+    private String payWay;
+    private Date payDate;
+    private String oilPersonnel;
+    private Date createdDate;
+    private String orderType;
+    private String oilPirce;
+    private String stationName;
+    private double receivableAmt;
+    private double receivedAmt;
+    private double discountAmt;
+    private double wxAmt;
+    private int printCount;
+    private String oilType;
+    private String payTypeName;
+    private String mobilePhone;
+}

+ 28 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSum.java

@@ -0,0 +1,28 @@
+package com.yijia.handpos.pojo;
+
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class ListSum {
+    private double receivableAmtSum;
+    private double receivedAmtSum;
+    private int printCountSum;
+    private double orderLitersSum;
+    private double discountAmtSum;
+    private int orderSum;
+    private double amtSum;
+
+    private String classesMan;
+    private String classesNo;
+    private Date endDate;
+    private String stationName;
+    private Date startDate;
+    private int stationId;
+
+}

+ 17 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSumGroupByOilName.java

@@ -0,0 +1,17 @@
+package com.yijia.handpos.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class ListSumGroupByOilName {
+    private double receivableAmtSum;
+    private double orderLitersSum;
+    private double discountAmtSum;
+    private String oilName;
+    private int orderSum;
+    private double amtSum;
+}

+ 17 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/ListSumGroupByPayType.java

@@ -0,0 +1,17 @@
+package com.yijia.handpos.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class ListSumGroupByPayType {
+        private double receivableAmtSum;
+        private String payType;
+        private double orderLitersSum;
+        private double discountAmtSum;
+        private int orderSum;
+        private double amtSum;
+}

+ 14 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/MessageContent.java

@@ -0,0 +1,14 @@
+package com.yijia.handpos.pojo;
+
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class MessageContent {
+    private List<ContentData> contentData;
+}

+ 130 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/MyOrderBean.java

@@ -0,0 +1,130 @@
+package com.yijia.handpos.pojo;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.yijia.handpos.util.DateUtil;
+
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class MyOrderBean implements Parcelable {
+    private String payType;
+    private String payDate;
+    private String payAmount;
+    private String payState;
+
+    private int orderId;
+    private String orderNo;
+    private String oilGun;
+    private String oilName;
+    private int consumerId;
+    private String consumer;
+    private double amt;
+    private int stationId;
+    private String status;
+    private String orderLiters;
+    private String payWay;
+    private String oilPersonnel;
+    private String createdDate;
+    private String orderType;
+    private String oilPirce;
+    private String stationName;
+    private double receivableAmt;
+    private double receivedAmt;
+    private double discountAmt;
+    private double wxAmt;
+    private int printCount;
+    private String oilType;
+    private String payTypeName;
+    private String mobilePhone;
+
+    public MyOrderBean() {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(payType);
+        dest.writeString(payDate);
+        dest.writeString(payAmount);
+        dest.writeString(payState);
+
+        dest.writeInt(orderId);
+        dest.writeString(orderNo);
+        dest.writeString(oilGun);
+        dest.writeString(oilName);
+        dest.writeInt(consumerId);
+        dest.writeString(consumer);
+        dest.writeDouble(amt);
+        dest.writeInt(stationId);
+        dest.writeString(status);
+        dest.writeString(orderLiters);
+        dest.writeString(payWay);
+        dest.writeString(oilPersonnel);
+        dest.writeString(createdDate);
+        dest.writeString(orderType);
+        dest.writeString(oilPirce);
+        dest.writeString(stationName);
+        dest.writeDouble(receivableAmt);
+        dest.writeDouble(receivedAmt);
+        dest.writeDouble(discountAmt);
+        dest.writeDouble(wxAmt);
+        dest.writeInt(printCount);
+        dest.writeString(oilType);
+        dest.writeString(payTypeName);
+        dest.writeString(mobilePhone);
+    }
+
+    //提供一个常量CREATOR指定泛型MyOrderBean
+    public static final Parcelable.Creator<MyOrderBean> CREATOR = new Parcelable.Creator<MyOrderBean>() {
+        @Override
+        public MyOrderBean createFromParcel(Parcel source) {
+            MyOrderBean myOrderBean = new MyOrderBean();
+            myOrderBean.payType = source.readString();
+            myOrderBean.payDate = source.readString();
+            myOrderBean.payAmount = source.readString();
+            myOrderBean.payState = source.readString();
+            myOrderBean.orderId = source.readInt();
+            myOrderBean.orderNo = source.readString();
+            myOrderBean.oilGun = source.readString();
+            myOrderBean.oilName = source.readString();
+            myOrderBean.consumerId = source.readInt();
+            myOrderBean.consumer = source.readString();
+            myOrderBean.amt = source.readDouble();
+            myOrderBean.stationId = source.readInt();
+            myOrderBean.status = source.readString();
+            myOrderBean.orderLiters = source.readString();
+            myOrderBean.payWay = source.readString();
+            myOrderBean.oilPersonnel = source.readString();
+            myOrderBean.createdDate = source.readString();
+            myOrderBean.orderType = source.readString();
+            myOrderBean.oilPirce = source.readString();
+            myOrderBean.stationName = source.readString();
+            myOrderBean.receivableAmt = source.readDouble();
+            myOrderBean.receivedAmt = source.readDouble();
+            myOrderBean.discountAmt = source.readDouble();
+            myOrderBean.wxAmt = source.readDouble();
+            myOrderBean.printCount = source.readInt();
+            myOrderBean.oilType = source.readString();
+            myOrderBean.payTypeName = source.readString();
+            myOrderBean.mobilePhone=source.readString();
+            return myOrderBean;
+        }
+
+        @Override
+        public MyOrderBean[] newArray(int size) {
+            return new MyOrderBean[size];
+        }
+    };
+}

+ 18 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/OffdutedItemBean.java

@@ -0,0 +1,18 @@
+package com.yijia.handpos.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class OffdutedItemBean {
+    private String classesNo;
+    private String classMan;
+    private String endDate;
+
+    public OffdutedItemBean() {
+
+    }
+}

+ 33 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/OrderList.java

@@ -0,0 +1,33 @@
+package com.yijia.handpos.pojo;
+
+
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class OrderList {
+    private int pageNum;
+    private int pageSize;
+    private int size;
+    private int startRow;
+    private int endRow;
+    private int pages;
+    private int prePage;
+    private int nextPage;
+    private boolean isFirstPage;
+    private boolean isLastPage;
+    private boolean hasPreviousPage;
+    private boolean hasNextPage;
+    private int navigatePages;
+    private List<Integer> navigatepageNums;
+    private int navigateFirstPage;
+    private int navigateLastPage;
+    private int total;
+    private List<DealList> dealList;
+
+}

+ 15 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/PrintRootBean.java

@@ -0,0 +1,15 @@
+package com.yijia.handpos.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class PrintRootBean {
+    private String messageId;
+    private String messageTitle;
+    private MessageContent messageContent;
+    private String messageType;
+}

+ 31 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/ResponseBean.java

@@ -0,0 +1,31 @@
+package com.yijia.handpos.pojo;
+
+public class ResponseBean {
+    private String message;
+    private int retCode;
+    private Data data;
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int getRetCode() {
+        return retCode;
+    }
+
+    public void setRetCode(int retCode) {
+        this.retCode = retCode;
+    }
+
+    public Data getData() {
+        return data;
+    }
+
+    public void setData(Data data) {
+        this.data = data;
+    }
+}

+ 17 - 0
HandPos/app/src/main/java/com/yijia/handpos/pojo/TimeList.java

@@ -0,0 +1,17 @@
+package com.yijia.handpos.pojo;
+
+import java.util.Date;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@AllArgsConstructor
+@EqualsAndHashCode
+public class TimeList {
+        private Date beginTime;
+        private Date endTime;
+        private String classStructureNo;
+        private String classStructureMan;
+}

+ 37 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/account/AccountFragment.java

@@ -0,0 +1,37 @@
+package com.yijia.handpos.ui.account;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.yijia.handpos.R;
+
+public class AccountFragment extends Fragment {
+
+    private AccountViewModel accountViewModel;
+    private LinearLayout ll_version_update;
+
+    public View onCreateView(@NonNull LayoutInflater inflater,
+                             ViewGroup container, Bundle savedInstanceState) {
+        accountViewModel =
+                new ViewModelProvider(this).get(AccountViewModel.class);
+        View root = inflater.inflate(R.layout.fragment_account, container, false);
+        ll_version_update = root.findViewById(R.id.ll_version_update);
+        initClickLisener();
+        return root;
+    }
+
+    public void initClickLisener() {
+        ll_version_update.setOnClickListener(v -> {
+            Intent intent = new Intent(getActivity(), VersionActivity.class);
+            startActivity(intent);
+        });
+    }
+}

+ 19 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/account/AccountViewModel.java

@@ -0,0 +1,19 @@
+package com.yijia.handpos.ui.account;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class AccountViewModel extends ViewModel {
+
+    private MutableLiveData<String> mText;
+
+    public AccountViewModel() {
+        mText = new MutableLiveData<>();
+        mText.setValue("这是我的页面!");
+    }
+
+    public LiveData<String> getText() {
+        return mText;
+    }
+}

+ 91 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/account/VersionActivity.java

@@ -0,0 +1,91 @@
+package com.yijia.handpos.ui.account;
+
+import androidx.appcompat.app.AppCompatActivity;
+import dmax.dialog.SpotsDialog;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.Toast;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.JsonRequest;
+import com.android.volley.toolbox.Volley;
+import com.google.gson.Gson;
+import com.xuexiang.xupdate.XUpdate;
+import com.xuexiang.xupdate.utils.UpdateUtils;
+import com.yijia.handpos.MainActivity;
+import com.yijia.handpos.R;
+import com.yijia.handpos.custom.CustomUpdateParser;
+import com.yijia.handpos.entity.CustomResult;
+
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class VersionActivity extends AppCompatActivity {
+    private Button btn_check_update, btn_check_back;
+    private String mUpdateUrl = "http://www.huiyj.com:8888/json/update.json";
+    private RequestQueue requestQueue;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_version);
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().hide();
+        }
+        btn_check_update = findViewById(R.id.btn_check_update);
+        btn_check_back = findViewById(R.id.btn_check_back);
+        //获取到一个RequestQueue对象
+        requestQueue = Volley.newRequestQueue(this);
+        initBtnClickLisener();
+    }
+
+    public void initBtnClickLisener() {
+        btn_check_update.setOnClickListener(v -> {
+            JsonRequest<JSONObject> jsonRequest = new JsonObjectRequest(Request.Method.GET, mUpdateUrl, null,
+                    response -> {
+                        Gson gson = new Gson();
+                        CustomResult customResult = gson.fromJson(response.toString(), CustomResult.class);
+                        if (!customResult.equals(null) || !customResult.equals("")) {
+                            if (customResult.hasUpdate) {
+                                if (customResult.versionCode > UpdateUtils.getVersionCode(this)) {
+                                    XUpdate.newBuild(getApplicationContext())
+                                            .updateUrl(mUpdateUrl)
+                                            .updateParser(new CustomUpdateParser())
+                                            .update();
+                                } else {
+                                    Toast.makeText(this, "已是最新版本,无需更新!", Toast.LENGTH_SHORT).show();
+                                }
+                            } else {
+                                Toast.makeText(this, "已是最新版本,无需更新!", Toast.LENGTH_SHORT).show();
+                            }
+                        } else {
+                            Toast.makeText(this, "获取服务器版本更新失败!", Toast.LENGTH_SHORT).show();
+                        }
+                    }, volleyError -> {
+                Toast.makeText(getApplicationContext(), volleyError.toString(), Toast.LENGTH_SHORT).show();
+                Log.d("请求失败:", volleyError.toString());
+            }) {
+                @Override
+                public Map<String, String> getHeaders() {
+                    HashMap<String, String> headers = new HashMap<String, String>();
+                    headers.put("Accept", "application/json");
+                    headers.put("Content-Type", "application/json; charset=UTF-8");
+                    return headers;
+                }
+            };
+            // 3 将post请求添加到队列中
+            requestQueue.add(jsonRequest);
+        });
+        btn_check_back.setOnClickListener(v -> {
+            Intent intent = new Intent(VersionActivity.this, MainActivity.class);
+            intent.putExtra("fragment_flag", 3);
+            startActivity(intent);
+        });
+    }
+
+}

+ 175 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/component/CustomDialog.java

@@ -0,0 +1,175 @@
+package com.yijia.handpos.ui.component;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.yijia.handpos.R;
+
+public class CustomDialog extends Dialog {
+
+    public CustomDialog(Context context) {
+        super(context);
+    }
+
+    public CustomDialog(Context context, int theme) {
+        super(context, theme);
+    }
+
+
+    public static class Builder {
+        private Context context;
+        private String title;
+        private String message;
+        private String positiveButtonText;
+        private String negativeButtonText;
+        private View contentView;
+        private DialogInterface.OnClickListener positiveButtonClickListener;
+        private DialogInterface.OnClickListener negativeButtonClickListener;
+
+        public Builder(Context context) {
+            this.context = context;
+        }
+
+        public Builder setMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        /**
+         * Set the Dialog message from resource
+         *
+         * @param
+         * @return
+         */
+        public Builder setMessage(int message) {
+            this.message = (String) context.getText(message);
+            return this;
+        }
+
+        /**
+         * Set the Dialog title from resource
+         *
+         * @param title
+         * @return
+         */
+        public Builder setTitle(int title) {
+            this.title = (String) context.getText(title);
+            return this;
+        }
+
+        /**
+         * Set the Dialog title from String
+         *
+         * @param title
+         * @return
+         */
+
+        public Builder setTitle(String title) {
+            this.title = title;
+            return this;
+        }
+
+        public Builder setContentView(View v) {
+            this.contentView = v;
+            return this;
+        }
+
+        /**
+         * Set the positive button resource and it's listener
+         *
+         * @param positiveButtonText
+         * @return
+         */
+        public Builder setPositiveButton(int positiveButtonText,
+                                         DialogInterface.OnClickListener listener) {
+            this.positiveButtonText = (String) context
+                    .getText(positiveButtonText);
+            this.positiveButtonClickListener = listener;
+            return this;
+        }
+
+        public Builder setPositiveButton(String positiveButtonText,
+                                         DialogInterface.OnClickListener listener) {
+            this.positiveButtonText = positiveButtonText;
+            this.positiveButtonClickListener = listener;
+            return this;
+        }
+
+        public Builder setNegativeButton(int negativeButtonText,
+                                         DialogInterface.OnClickListener listener) {
+            this.negativeButtonText = (String) context
+                    .getText(negativeButtonText);
+            this.negativeButtonClickListener = listener;
+            return this;
+        }
+
+        public Builder setNegativeButton(String negativeButtonText,
+                                         DialogInterface.OnClickListener listener) {
+            this.negativeButtonText = negativeButtonText;
+            this.negativeButtonClickListener = listener;
+            return this;
+        }
+
+        public CustomDialog create() {
+            LayoutInflater inflater = (LayoutInflater) context
+                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            // instantiate the dialog with the custom Theme
+            final CustomDialog dialog = new CustomDialog(context, R.style.Dialog);
+            View layout = inflater.inflate(R.layout.dialog_layout, null);
+            dialog.addContentView(layout, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+            // set the dialog title
+            ((TextView) layout.findViewById(R.id.title)).setText(title);
+            // set the confirm button
+            if (positiveButtonText != null) {
+                ((TextView) layout.findViewById(R.id.positiveTextView))
+                        .setText(positiveButtonText);
+                if (positiveButtonClickListener != null) {
+                    ((TextView) layout.findViewById(R.id.positiveTextView))
+                            .setOnClickListener(v -> positiveButtonClickListener.onClick(dialog,
+                                    DialogInterface.BUTTON_POSITIVE));
+                }
+            } else {
+                // if no confirm button just set the visibility to GONE
+                layout.findViewById(R.id.positiveTextView).setVisibility(
+                        View.GONE);
+            }
+            // set the cancel button
+            if (negativeButtonText != null) {
+                ((TextView) layout.findViewById(R.id.negativeTextView))
+                        .setText(negativeButtonText);
+                if (negativeButtonClickListener != null) {
+                    layout.findViewById(R.id.negativeTextView)
+                            .setOnClickListener(v -> negativeButtonClickListener.onClick(dialog,
+                                    DialogInterface.BUTTON_NEGATIVE));
+                }
+            } else {
+                // if no confirm button just set the visibility to GONE
+                layout.findViewById(R.id.negativeTextView).setVisibility(
+                        View.GONE);
+            }
+            // set the content message
+            if (message != null) {
+                ((TextView) layout.findViewById(R.id.message)).setText(message);
+            } else if (contentView != null) {
+                // if no message set
+                // add the contentView to the dialog body
+                ((LinearLayout) layout.findViewById(R.id.content))
+                        .removeAllViews();
+                ((LinearLayout) layout.findViewById(R.id.content))
+                        .addView(contentView, new LinearLayout.LayoutParams(
+                                LinearLayout.LayoutParams.MATCH_PARENT,
+                                LinearLayout.LayoutParams.MATCH_PARENT));
+            }
+            dialog.setContentView(layout);
+            return dialog;
+        }
+    }
+
+
+}

+ 93 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/home/HomeFragment.java

@@ -0,0 +1,93 @@
+package com.yijia.handpos.ui.home;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.yijia.handpos.R;
+import com.yijia.handpos.ui.statistic.StatisticActivity;
+import com.yijia.handpos.ui.work.OffdutyActivity;
+import com.yijia.handpos.ui.work.OffdutyedActivity;
+
+public class HomeFragment extends Fragment {
+
+    private HomeViewModel homeViewModel;
+    private Button btn_banjie, btn_yibanjie, btn_tongji;
+    private Button btn_czkcz, btn_czkzf, btn_xjzf;
+    private Button btn_skewm, btn_jfdh, btn_syt, btn_hygl;
+
+    public View onCreateView(@NonNull LayoutInflater inflater,
+                             ViewGroup container, Bundle savedInstanceState) {
+        homeViewModel =
+                new ViewModelProvider(this).get(HomeViewModel.class);
+        View root = inflater.inflate(R.layout.fragment_home, container, false);
+        btn_banjie = root.findViewById(R.id.btn_banjie);
+        btn_yibanjie = root.findViewById(R.id.btn_yibanjie);
+        btn_tongji = root.findViewById(R.id.btn_tongji);
+
+        btn_czkcz = root.findViewById(R.id.btn_czkcz);
+        btn_czkzf = root.findViewById(R.id.btn_czkzf);
+        btn_xjzf = root.findViewById(R.id.btn_xjzf);
+
+        btn_skewm = root.findViewById(R.id.btn_skewm);
+        btn_jfdh = root.findViewById(R.id.btn_jfdh);
+        btn_syt = root.findViewById(R.id.btn_syt);
+        btn_hygl = root.findViewById(R.id.btn_hygl);
+
+        initClickLisener();
+        return root;
+    }
+
+
+
+    private void initClickLisener() {
+        btn_banjie.setOnClickListener(v -> {
+            Intent intent = new Intent(getActivity(), OffdutyActivity.class);
+            startActivity(intent);
+        });
+        btn_yibanjie.setOnClickListener(v -> {
+            Intent intent = new Intent(getActivity(), OffdutyedActivity.class);
+            startActivity(intent);
+        });
+        btn_tongji.setOnClickListener(v -> {
+//            Intent intent = new Intent(getActivity(), StatisticActivity.class);
+//            startActivity(intent);
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+
+        btn_czkcz.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+        btn_czkzf.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+        btn_xjzf.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+
+        btn_skewm.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+        btn_jfdh.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+        btn_syt.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+        btn_hygl.setOnClickListener(v -> {
+            Toast.makeText(getContext(), "暂未开放,敬请期待!", Toast.LENGTH_SHORT).show();
+        });
+    }
+}

+ 19 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/home/HomeViewModel.java

@@ -0,0 +1,19 @@
+package com.yijia.handpos.ui.home;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class HomeViewModel extends ViewModel {
+
+    private MutableLiveData<String> mText;
+
+    public HomeViewModel() {
+        mText = new MutableLiveData<>();
+        mText.setValue("这是首页页面!");
+    }
+
+    public LiveData<String> getText() {
+        return mText;
+    }
+}

+ 17 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoggedInUserView.java

@@ -0,0 +1,17 @@
+package com.yijia.handpos.ui.login;
+
+/**
+ * Class exposing authenticated user details to the UI.
+ */
+class LoggedInUserView {
+    private String displayName;
+    //... other data fields that may be accessible to the UI
+
+    LoggedInUserView(String displayName) {
+        this.displayName = displayName;
+    }
+
+    String getDisplayName() {
+        return displayName;
+    }
+}

+ 590 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginActivity.java

@@ -0,0 +1,590 @@
+package com.yijia.handpos.ui.login;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.JsonObjectRequest;
+import com.android.volley.toolbox.JsonRequest;
+import com.android.volley.toolbox.Volley;
+import com.google.gson.Gson;
+import com.iflytek.cloud.ErrorCode;
+import com.iflytek.cloud.InitListener;
+import com.iflytek.cloud.SpeechConstant;
+import com.iflytek.cloud.SpeechError;
+import com.iflytek.cloud.SpeechEvent;
+import com.iflytek.cloud.SpeechSynthesizer;
+import com.iflytek.cloud.SpeechUtility;
+import com.iflytek.cloud.SynthesizerListener;
+import com.iflytek.cloud.util.ResourceUtil;
+import com.orhanobut.logger.Logger;
+import com.pax.dal.IDAL;
+import com.pax.gl.IGL;
+import com.pax.gl.impl.GLProxy;
+import com.pax.neptunelite.api.NeptuneLiteUser;
+import com.yijia.handpos.MainActivity;
+import com.yijia.handpos.R;
+import com.yijia.handpos.pojo.ResponseBean;
+import com.yijia.handpos.util.ConsUtil;
+import com.yijia.handpos.util.DESUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProvider;
+import dmax.dialog.SpotsDialog;
+
+public class LoginActivity extends AppCompatActivity {
+    private static String TAG = LoginActivity.class.getSimpleName();
+    private LoginViewModel loginViewModel;
+    private RequestQueue requestQueue;
+    public static IDAL dal;
+    public static IGL gl;
+    public static String TUSN_NO = "";
+    // 语音合成对象
+    private SpeechSynthesizer mTts;
+    // 本地发音人列表
+    private String[] localVoicersEntries;
+    private String[] localVoicersValue;
+    // 默认云端发音人
+    public static String voicerCloud = "xiaoyan";
+    // 默认本地发音人
+    public static String voicerLocal = "xiaoyan";
+    public static String voicerXtts = "xiaoyan";
+    // 引擎类型
+    private String mEngineType = SpeechConstant.TYPE_LOCAL;
+    //打印机状态代码
+    private int printerStatusFlag=0;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        // 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
+        // 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
+        // 参数间使用“,”分隔。
+        // 设置你申请的应用appid
+        // 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
+        StringBuffer param = new StringBuffer();
+        param.append("appid="+getString(R.string.app_id));
+        param.append(",");
+        // 设置使用v5+
+        param.append(SpeechConstant.ENGINE_MODE+"="+SpeechConstant.MODE_MSC);
+        SpeechUtility.createUtility(LoginActivity.this, param.toString());
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_login);
+        if (getSupportActionBar() != null) {
+            getSupportActionBar().hide();
+        }
+        try {
+
+            dal = NeptuneLiteUser.getInstance().getDal(this);
+            gl = new GLProxy(this).getGL();
+            TUSN_NO = dal.getSys().readTUSN();
+            printerStatusFlag=dal.getPrinter().getStatus();
+            Logger.i("获取到的打印机状态码:"+dal.getPrinter().getStatus());
+            if (ConsUtil.tusnNo.equals("")) {
+                ConsUtil.tusnNo = TUSN_NO;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        // 初始化合成对象
+        mTts = SpeechSynthesizer.createSynthesizer(this, mTtsInitListener);
+        // 本地发音人名称列表
+        localVoicersEntries = getResources().getStringArray(R.array.voicer_local_entries);
+        localVoicersValue = getResources().getStringArray(R.array.voicer_local_values);
+        initView();
+    }
+
+
+
+    private void initView() {
+        //获取到一个RequestQueue对象
+        requestQueue = Volley.newRequestQueue(this);
+        AlertDialog alertDialog = new SpotsDialog.Builder().setContext(this).setMessage(R.string.custom_login_title).build();
+        loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory())
+                .get(LoginViewModel.class);
+        final EditText usernameEditText = findViewById(R.id.username);
+        final EditText passwordEditText = findViewById(R.id.password);
+        final Button loginButton = findViewById(R.id.login);
+        final ProgressBar loadingProgressBar = findViewById(R.id.loading);
+
+        loginViewModel.getLoginFormState().observe(this, loginFormState -> {
+            if (loginFormState == null) {
+                return;
+            }
+            loginButton.setEnabled(loginFormState.isDataValid());
+            if (loginFormState.getUsernameError() != null) {
+                usernameEditText.setError(getString(loginFormState.getUsernameError()));
+            }
+            if (loginFormState.getPasswordError() != null) {
+                passwordEditText.setError(getString(loginFormState.getPasswordError()));
+            }
+        });
+
+        loginViewModel.getLoginResult().observe(this, loginResult -> {
+            if (loginResult == null) {
+                return;
+            }
+            loadingProgressBar.setVisibility(View.GONE);
+            if (loginResult.getError() != null) {
+                showLoginFailed(loginResult.getError());
+            }
+            if (loginResult.getSuccess() != null) {
+                updateUiWithUser(loginResult.getSuccess());
+            }
+            setResult(Activity.RESULT_OK);
+
+            //Complete and destroy login activity once successful
+            finish();
+        });
+
+
+        TextWatcher afterTextChangedListener = new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // ignore
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // ignore
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
+                        passwordEditText.getText().toString());
+            }
+        };
+        usernameEditText.addTextChangedListener(afterTextChangedListener);
+        passwordEditText.addTextChangedListener(afterTextChangedListener);
+        //监听软键盘上的Done按钮
+        passwordEditText.setOnEditorActionListener((v, actionId, event) -> {
+            if (actionId == EditorInfo.IME_ACTION_DONE) {
+                alertDialog.show();
+                String url = ConsUtil.REQUEST_BASE_URL + ConsUtil.REQUEST_PREFIX + "/loginPosMachine";
+                Map<String, String> params = new HashMap<>();
+                params.put("userName", usernameEditText.getText().toString());
+                params.put("password", DESUtils.encrypt(passwordEditText.getText().toString(),ConsUtil.key));
+                JSONObject jsonObject = new JSONObject(params);
+                JsonRequest<JSONObject> jsonRequest = new JsonObjectRequest(Request.Method.POST, url, jsonObject,
+                        response -> {
+                            try {
+                                if (response.getInt("retCode") == 0) {
+                                    Log.d("请求成功:", response.toString());
+                                    alertDialog.hide();
+                                    Gson gson = new Gson();
+                                    ResponseBean responseBean = gson.fromJson(response.toString(), ResponseBean.class);
+                                    ConsUtil.stationId = responseBean.getData().getDeptId();
+                                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
+                                    startActivity(intent);
+                                } else {
+                                    Log.d("请求失败:", response.getString("message"));
+                                    alertDialog.hide();
+                                    Toast.makeText(getApplicationContext(),
+                                            response.getString("message"), Toast.LENGTH_SHORT).show();
+                                }
+                            } catch (JSONException e) {
+                                Log.d("转换失败:", e.toString());
+                                alertDialog.hide();
+                                Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
+
+                            }
+
+                        }, volleyError -> {
+                    loadingProgressBar.setVisibility(View.GONE);
+                    Toast.makeText(getApplicationContext(), volleyError.toString(), Toast.LENGTH_SHORT).show();
+                    Log.d("请求失败:", volleyError.toString());
+                }) {
+                    @Override
+                    public Map<String, String> getHeaders() {
+                        HashMap<String, String> headers = new HashMap<String, String>();
+                        headers.put("Accept", "application/json");
+                        headers.put("Content-Type", "application/json; charset=UTF-8");
+                        return headers;
+                    }
+                };
+
+                // 3 将post请求添加到队列中
+                requestQueue.add(jsonRequest);
+            }
+            return false;
+        });
+
+        //登录按钮监听事件
+        loginButton.setOnClickListener(v -> {
+            alertDialog.show();
+            String url = ConsUtil.REQUEST_BASE_URL + ConsUtil.REQUEST_PREFIX + "/loginPosMachine";
+            Map<String, String> params = new HashMap<>();
+            params.put("userName", usernameEditText.getText().toString());
+            params.put("password", DESUtils.encrypt(passwordEditText.getText().toString(),ConsUtil.key));
+            JSONObject jsonObject = new JSONObject(params);
+            JsonRequest<JSONObject> jsonRequest = new JsonObjectRequest(Request.Method.POST, url, jsonObject,
+                    response -> {
+                        try {
+                            if (response.getInt("retCode") == 0) {
+                                Log.d("请求成功:", response.toString());
+                                ConsUtil.userName = usernameEditText.getText().toString();
+                                alertDialog.hide();
+                                Gson gson = new Gson();
+                                ResponseBean responseBean = gson.fromJson(response.toString(), ResponseBean.class);
+                                ConsUtil.stationId = responseBean.getData().getDeptId();
+                                Intent intent = new Intent(LoginActivity.this, MainActivity.class);
+                                startActivity(intent);
+                            } else {
+                                Log.d("请求失败:", response.getString("message"));
+                                alertDialog.hide();
+                                Toast.makeText(getApplicationContext(),
+                                        response.getString("message"), Toast.LENGTH_SHORT).show();
+                            }
+                        } catch (JSONException e) {
+                            Log.d("转换失败:", e.toString());
+                            alertDialog.hide();
+                            Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
+                        }
+
+                    }, volleyError -> {
+                loadingProgressBar.setVisibility(View.GONE);
+                Toast.makeText(getApplicationContext(), volleyError.toString(), Toast.LENGTH_SHORT).show();
+                Log.d("请求失败:", volleyError.toString());
+            }) {
+                @Override
+                public Map<String, String> getHeaders() {
+                    HashMap<String, String> headers = new HashMap<String, String>();
+                    headers.put("Accept", "application/json");
+                    headers.put("Content-Type", "application/json; charset=UTF-8");
+                    return headers;
+                }
+            };
+
+            // 3 将post请求添加到队列中
+            requestQueue.add(jsonRequest);
+        });
+
+        String urlGet = ConsUtil.REQUEST_BASE_URL + ConsUtil.REQUEST_PREFIX + "/getStationDeviceManagerBytusn?tusn="
+                + TUSN_NO;
+        JsonRequest<JSONObject> jsonRequestGet = new JsonObjectRequest(Request.Method.GET, urlGet, null,
+                response -> {
+                    try {
+                        if (response.getInt("retCode") == 0) {
+                            Log.d("请求成功:", response.toString());
+                            Gson gson = new Gson();
+                            ResponseBean responseBean = gson.fromJson(response.toString(), ResponseBean.class);
+                            if (null != responseBean.getData() || "".equals(responseBean.getData())) {
+                                String[] guns = responseBean.getData().getGunNo().split(",");
+                                if (!Arrays.equals(guns, ConsUtil.gunNoArray)) {
+                                    ConsUtil.gunNoArray = guns;
+                                }
+                            } else {
+                                Toast.makeText(getApplicationContext(),
+                                        response.getString("该POS机未绑定油枪,请维护!否则无法打印小票"),
+                                        Toast.LENGTH_LONG).show();
+                            }
+                        } else {
+                            Log.d("请求失败:", response.getString("message"));
+                            Toast.makeText(getApplicationContext(),
+                                    response.getString("message"), Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (JSONException e) {
+                        Log.d("转换失败:", e.toString());
+                        Toast.makeText(getApplicationContext(), "该POS机未绑定油枪,请维护!否则无法打印小票!", Toast.LENGTH_LONG).show();
+                    }
+
+                }, volleyError -> {
+            Toast.makeText(getApplicationContext(), volleyError.toString(), Toast.LENGTH_SHORT).show();
+            Log.d("请求失败:", volleyError.toString());
+        }) {
+            @Override
+            public Map<String, String> getHeaders() {
+                HashMap<String, String> headers = new HashMap<String, String>();
+                headers.put("Accept", "application/json");
+                headers.put("Content-Type", "application/json; charset=UTF-8");
+                return headers;
+            }
+        };
+        // 3 将post请求添加到队列中
+        requestQueue.add(jsonRequestGet);
+    }
+
+    private void updateUiWithUser(LoggedInUserView model) {
+        String welcome = getString(R.string.welcome) + model.getDisplayName();
+        // TODO : initiate successful logged in experience
+        Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
+    }
+
+    private void showLoginFailed(@StringRes Integer errorString) {
+        Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
+    }
+
+    //监听返回键
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        //监控/拦截/屏蔽返回键
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            quitConfirmDialogBack();
+        }
+        //监控/拦截菜单键
+        else if (keyCode == KeyEvent.KEYCODE_MENU) {
+
+            quitConfirmDialogBack();
+        }
+        //由于Home键为系统键,此处不能捕获,需要重写onAttachedToWindow()
+        else if (keyCode == KeyEvent.KEYCODE_HOME) {
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+
+    @Override
+    public void onAttachedToWindow() {
+        this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+        super.onAttachedToWindow();
+    }
+
+    public void quitConfirmDialogBack() {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        // 自定义 title样式
+        TextView title = new TextView(this);
+        title.setText("提示");
+        title.setTextSize(18);
+        title.setGravity(Gravity.CENTER);
+        title.setPadding(0, 20, 0, 20);
+        title.setBackgroundColor(Color.rgb(255, 165, 0));
+        builder.setCustomTitle(title);
+        // 中间的信息以一个view的形式设置进去
+        TextView msg = new TextView(this);
+        msg.setText("确定要退出程序吗?");
+        msg.setTextSize(16);
+        msg.setGravity(Gravity.LEFT);
+        msg.setPadding(20, 40, 20, 40);
+        builder.setView(msg);
+        builder.setPositiveButton("确认", (dialogInterface, i) -> {
+            dialogInterface.dismiss();
+            finish();
+            System.exit(0);
+        }).setNegativeButton("取消", null);
+        // 调用 show()方法后得到 dialog对象
+        AlertDialog dialog = builder.show();
+        dialog.setCancelable(false);
+        final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+        final Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+        LinearLayout.LayoutParams positiveParams = (LinearLayout.LayoutParams) positiveButton.getLayoutParams();
+        positiveParams.gravity = Gravity.CENTER;
+        positiveParams.setMargins(10, 10, 10, 10);
+        positiveParams.width = 0;
+        // 安卓下面有三个位置的按钮,默认权重为 1,设置成 500或更大才能让两个按钮看起来均分
+        positiveParams.weight = 500;
+        LinearLayout.LayoutParams negativeParams = (LinearLayout.LayoutParams) negativeButton.getLayoutParams();
+        negativeParams.gravity = Gravity.CENTER;
+        negativeParams.setMargins(10, 10, 10, 10);
+        negativeParams.width = 0;
+        negativeParams.weight = 500;
+        positiveButton.setLayoutParams(positiveParams);
+        negativeButton.setLayoutParams(negativeParams);
+        positiveButton.setBackgroundColor(Color.parseColor("#018786"));
+        positiveButton.setTextColor(Color.parseColor("#FFFFFF"));
+        negativeButton.setBackgroundColor(Color.parseColor("#808080"));
+        positiveButton.setTextSize(16);
+        negativeButton.setTextSize(16);
+    }
+
+    /**
+     * 初始化监听。
+     */
+    private InitListener mTtsInitListener = code -> {
+        Log.d(TAG, "InitListener init() code = " + code);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG, "初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        } else {
+            // 初始化成功,之后可以调用startSpeaking方法
+            // 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
+            // 正确的做法是将onCreate中的startSpeaking调用移至这里
+            if (printerStatusFlag==0){
+                welcomeVoice();//播放欢迎语音
+            }else {
+                printerCheck();//检测打印机状态
+            }
+        }
+    };
+    public void welcomeVoice(){
+        if( null == mTts ){
+            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
+            Log.d(TAG, "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" );
+        }
+        String text="欢迎使用智慧易加APP";
+        // 设置参数
+        setParam();
+        int code = mTts.startSpeaking(text, mTtsListener);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG,"语音合成失败,错误码: " + code+",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        }
+    }
+
+    public void printerCheck(){
+        String result;
+        switch (printerStatusFlag){
+            case 1:
+                result="打印机忙";
+                break;
+            case 2:
+                result="打印机缺纸";
+                break;
+            case 8:
+                result="打印机过热";
+                break;
+            case 9:
+                result="电池电量低";
+                break;
+            default:
+                result="打印机为止错误";
+        }
+        if( null == mTts ){
+            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
+            Log.d(TAG, "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" );
+        }
+        String text=result;
+        // 设置参数
+        setParam();
+        int code = mTts.startSpeaking(text, mTtsListener);
+        if (code != ErrorCode.SUCCESS) {
+            Log.d(TAG,"语音合成失败,错误码: " + code+",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
+        }
+    }
+
+    /**
+     * 合成回调监听。
+     */
+    private SynthesizerListener mTtsListener = new SynthesizerListener() {
+
+        @Override
+        public void onSpeakBegin() {
+            Log.d(TAG,"开始播放:"+ System.currentTimeMillis());
+        }
+        @Override
+        public void onSpeakPaused() {
+            Log.d(TAG,"暂停播放");
+        }
+
+        @Override
+        public void onSpeakResumed() {
+            Log.d(TAG,"继续播放");
+        }
+
+        @Override
+        public void onBufferProgress(int percent, int beginPos, int endPos,
+                                     String info) {
+        }
+        @Override
+        public void onSpeakProgress(int percent, int beginPos, int endPos) {
+
+        }
+        @Override
+        public void onCompleted(SpeechError error) {
+            if (error == null) {
+                Log.d(TAG,"播放完成");
+            } else if (error != null) {
+                Log.d(TAG,error.getPlainDescription(true));
+            }
+        }
+
+        @Override
+        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
+            // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
+            // 若使用本地能力,会话id为null
+            if (SpeechEvent.EVENT_SESSION_ID == eventType) {
+                String sid = obj.getString(SpeechEvent.KEY_EVENT_AUDIO_URL);
+                Log.d(TAG, "session id =" + sid);
+            }
+        }
+    };
+
+    /**
+     * 参数设置
+     */
+    private void setParam() {
+        // 清空参数
+        mTts.setParameter(SpeechConstant.PARAMS, null);
+        //设置合成
+        if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
+            //设置使用云端引擎
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerCloud);
+
+        } else if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
+            //设置使用本地引擎
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
+            //设置发音人资源路径
+            mTts.setParameter(ResourceUtil.TTS_RES_PATH, getResourcePath());
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerLocal);
+        } else {
+            mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_XTTS);
+            //设置发音人资源路径
+            mTts.setParameter(ResourceUtil.TTS_RES_PATH, getResourcePath());
+            //设置发音人
+            mTts.setParameter(SpeechConstant.VOICE_NAME, voicerXtts);
+        }
+        //mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY,"1");//支持实时音频流抛出,仅在synthesizeToUri条件下支持
+        //设置合成语速
+        mTts.setParameter(SpeechConstant.SPEED, "50");
+        //设置合成音调
+        mTts.setParameter(SpeechConstant.PITCH, "50");
+        //设置合成音量
+        mTts.setParameter(SpeechConstant.VOLUME, "50");
+        //设置播放器音频流类型
+        mTts.setParameter(SpeechConstant.STREAM_TYPE, "3");
+        //	mTts.setParameter(SpeechConstant.STREAM_TYPE, AudioManager.STREAM_MUSIC+"");
+        // 设置播放合成音频打断音乐播放,默认为true
+        mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
+        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
+        mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
+        mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/tts.wav");
+    }
+
+    //获取发音人资源路径
+    private String getResourcePath() {
+        StringBuffer tempBuffer = new StringBuffer();
+        String type = "tts";
+        if (mEngineType.equals(SpeechConstant.TYPE_XTTS)) {
+            type = "xtts";
+        }
+        //合成通用资源
+        tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/common.jet"));
+        tempBuffer.append(";");
+        //发音人资源
+        if (mEngineType.equals(SpeechConstant.TYPE_XTTS)) {
+            tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/" + MainActivity.voicerXtts + ".jet"));
+        } else {
+            tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, type + "/" + MainActivity.voicerLocal + ".jet"));
+        }
+
+        return tempBuffer.toString();
+    }
+
+}

+ 40 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginFormState.java

@@ -0,0 +1,40 @@
+package com.yijia.handpos.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Data validation state of the login form.
+ */
+class LoginFormState {
+    @Nullable
+    private Integer usernameError;
+    @Nullable
+    private Integer passwordError;
+    private boolean isDataValid;
+
+    LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
+        this.usernameError = usernameError;
+        this.passwordError = passwordError;
+        this.isDataValid = false;
+    }
+
+    LoginFormState(boolean isDataValid) {
+        this.usernameError = null;
+        this.passwordError = null;
+        this.isDataValid = isDataValid;
+    }
+
+    @Nullable
+    Integer getUsernameError() {
+        return usernameError;
+    }
+
+    @Nullable
+    Integer getPasswordError() {
+        return passwordError;
+    }
+
+    boolean isDataValid() {
+        return isDataValid;
+    }
+}

+ 31 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginResult.java

@@ -0,0 +1,31 @@
+package com.yijia.handpos.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Authentication result : success (user details) or error message.
+ */
+class LoginResult {
+    @Nullable
+    private LoggedInUserView success;
+    @Nullable
+    private Integer error;
+
+    LoginResult(@Nullable Integer error) {
+        this.error = error;
+    }
+
+    LoginResult(@Nullable LoggedInUserView success) {
+        this.success = success;
+    }
+
+    @Nullable
+    LoggedInUserView getSuccess() {
+        return success;
+    }
+
+    @Nullable
+    Integer getError() {
+        return error;
+    }
+}

+ 70 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginViewModel.java

@@ -0,0 +1,70 @@
+package com.yijia.handpos.ui.login;
+
+import android.util.Patterns;
+
+import com.yijia.handpos.R;
+import com.yijia.handpos.ui.login.data.LoginRepository;
+import com.yijia.handpos.ui.login.data.Result;
+import com.yijia.handpos.ui.login.data.model.LoggedInUser;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class LoginViewModel extends ViewModel {
+
+    private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
+    private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
+    private LoginRepository loginRepository;
+
+    LoginViewModel(LoginRepository loginRepository) {
+        this.loginRepository = loginRepository;
+    }
+
+    LiveData<LoginFormState> getLoginFormState() {
+        return loginFormState;
+    }
+
+    LiveData<LoginResult> getLoginResult() {
+        return loginResult;
+    }
+
+    public void login(String username, String password) {
+        // can be launched in a separate asynchronous job
+        Result<LoggedInUser> result = loginRepository.login(username, password);
+
+        if (result instanceof Result.Success) {
+            LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
+            loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));
+        } else {
+            loginResult.setValue(new LoginResult(R.string.login_failed));
+        }
+    }
+
+    public void loginDataChanged(String username, String password) {
+        if (!isUserNameValid(username)) {
+            loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
+        } else if (!isPasswordValid(password)) {
+            loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
+        } else {
+            loginFormState.setValue(new LoginFormState(true));
+        }
+    }
+
+    // A placeholder username validation check
+    private boolean isUserNameValid(String username) {
+        if (username == null) {
+            return false;
+        }
+        if (username.contains("@")) {
+            return Patterns.EMAIL_ADDRESS.matcher(username).matches();
+        } else {
+            return !username.trim().isEmpty();
+        }
+    }
+
+    // A placeholder password validation check
+    private boolean isPasswordValid(String password) {
+        return password != null && password.trim().length() > 5;
+    }
+}

+ 26 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/LoginViewModelFactory.java

@@ -0,0 +1,26 @@
+package com.yijia.handpos.ui.login;
+
+import com.yijia.handpos.ui.login.data.LoginDataSource;
+import com.yijia.handpos.ui.login.data.LoginRepository;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * ViewModel provider factory to instantiate LoginViewModel.
+ * Required given LoginViewModel has a non-empty constructor
+ */
+public class LoginViewModelFactory implements ViewModelProvider.Factory {
+
+    @NonNull
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+        if (modelClass.isAssignableFrom(LoginViewModel.class)) {
+            return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
+        } else {
+            throw new IllegalArgumentException("Unknown ViewModel class");
+        }
+    }
+}

+ 29 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/LoginDataSource.java

@@ -0,0 +1,29 @@
+package com.yijia.handpos.ui.login.data;
+
+import com.yijia.handpos.ui.login.data.model.LoggedInUser;
+
+import java.io.IOException;
+
+/**
+ * Class that handles authentication w/ login credentials and retrieves user information.
+ */
+public class LoginDataSource {
+
+    public Result<LoggedInUser> login(String username, String password) {
+
+        try {
+            // TODO: handle loggedInUser authentication
+            LoggedInUser fakeUser =
+                    new LoggedInUser(
+                            java.util.UUID.randomUUID().toString(),
+                            "Jane Doe");
+            return new Result.Success<>(fakeUser);
+        } catch (Exception e) {
+            return new Result.Error(new IOException("Error logging in", e));
+        }
+    }
+
+    public void logout() {
+        // TODO: revoke authentication
+    }
+}

+ 54 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/LoginRepository.java

@@ -0,0 +1,54 @@
+package com.yijia.handpos.ui.login.data;
+
+import com.yijia.handpos.ui.login.data.model.LoggedInUser;
+
+/**
+ * Class that requests authentication and user information from the remote data source and
+ * maintains an in-memory cache of login status and user credentials information.
+ */
+public class LoginRepository {
+
+    private static volatile LoginRepository instance;
+
+    private LoginDataSource dataSource;
+
+    // If user credentials will be cached in local storage, it is recommended it be encrypted
+    // @see https://developer.android.com/training/articles/keystore
+    private LoggedInUser user = null;
+
+    // private constructor : singleton access
+    private LoginRepository(LoginDataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public static LoginRepository getInstance(LoginDataSource dataSource) {
+        if (instance == null) {
+            instance = new LoginRepository(dataSource);
+        }
+        return instance;
+    }
+
+    public boolean isLoggedIn() {
+        return user != null;
+    }
+
+    public void logout() {
+        user = null;
+        dataSource.logout();
+    }
+
+    private void setLoggedInUser(LoggedInUser user) {
+        this.user = user;
+        // If user credentials will be cached in local storage, it is recommended it be encrypted
+        // @see https://developer.android.com/training/articles/keystore
+    }
+
+    public Result<LoggedInUser> login(String username, String password) {
+        // handle login
+        Result<LoggedInUser> result = dataSource.login(username, password);
+        if (result instanceof Result.Success) {
+            setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
+        }
+        return result;
+    }
+}

+ 48 - 0
HandPos/app/src/main/java/com/yijia/handpos/ui/login/data/Result.java

@@ -0,0 +1,48 @@
+package com.yijia.handpos.ui.login.data;
+
+/**
+ * A generic class that holds a result success w/ data or an error exception.
+ */
+public class Result<T> {
+    // hide the private constructor to limit subclass types (Success, Error)
+    private Result() {
+    }
+
+    @Override
+    public String toString() {
+        if (this instanceof Result.Success) {
+            Result.Success success = (Result.Success) this;
+            return "Success[data=" + success.getData().toString() + "]";
+        } else if (this instanceof Result.Error) {
+            Result.Error error = (Result.Error) this;
+            return "Error[exception=" + error.getError().toString() + "]";
+        }
+        return "";
+    }
+
+    // Success sub-class
+    public final static class Success<T> extends Result {
+        private T data;
+
+        public Success(T data) {
+            this.data = data;
+        }
+
+        public T getData() {
+            return this.data;
+        }
+    }
+
+    // Error sub-class
+    public final static class Error extends Result {
+        private Exception error;
+
+        public Error(Exception error) {
+            this.error = error;
+        }
+
+        public Exception getError() {
+            return this.error;
+        }
+    }
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor