Commit 8a3cbba7 by pye52

push最新代码

parent 0b51e3c7
*.apk
*.iml
.gradle
/**/output.json
/local.properties
/.idea/misc.xml
/.idea/caches
/.idea/libraries
/.idea/modules.xml
......@@ -14,6 +11,9 @@
/build
/captures
.externalNativeBuild
.idea/codeStyles/Project.xml
.cxx
/.idea
/gradlew
/gradle
/app/src/main/assets/Daemon.apk
/~$食堂开发文档.docx
## Deprecated
原项目架构比较混乱,耦合严重,牵一发动全身,在后续需求迭代时改动非常困难,也没有文档说明,且由分支管理环境显得繁琐并容易出错,因此不再维护,现已重构完毕。
重构版本分支为:
new_master
new_for_phone(适配手机)
### 分支管理
分支名|描述
-|-
develop|开发分支作为源分支,所有新功能开发和bug修复都在develop派生分支进行
test|测试环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号
uat|uat环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号
production|生产环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号
| 分支名 | 描述 |
| ---------- | ------------------------------------------------------------ |
| develop | deprecated 开发分支作为源分支,所有新功能开发和bug修复都在develop派生分支进行 |
| test | deprecated 测试环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号 |
| uat | deprecated uat环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号 |
| production | deprecated 生产环境,仅用作打包使用,打包前先从develop分支合并过来并递增版本号 |
| new_master | 重构版本,通过Build Variants来切换到指定的环境,打包同理。已配置好混淆 |
| new_for_phone | 重构版本,供手机测试,通过Build Variants来切换到指定的环境,打包同理。已配置好混淆 |
### 特殊功能二维码
在线生成二维码:https://www.liantu.com
请参考同目录下"智慧食堂开发文档.docx"
功能|二维码内容
-|-
开启/关闭Log|{"action":"CONFIG_LOG"}
软件更新|{"action":"CONFIG_UPDATE","data":{"url":"apk网络地址"}}
清空离线支付记录|{"action":"CONFIG_CLEAR_OFFLINE_RECODE"}
### 已知问题
- 现场反馈终端有偶发性卡死的情况,具体表现为界面右上角时间不走,接入鼠标键盘均没有响应。终端系统日志显示是因为某个App占用过多资源导致系统频繁GC,目前没有排除到是否我们App导致还是终端里其它App导致,建议与甲方沟通把多余无关的App卸载掉,并且把蓝牙关闭。
### 重构目的
1. 去掉Kotlin,架构重新设计
2. 所有订单及操作都将在本地数据库记录
3. 优化各任务(在线支付、离线支付、指令执行)的设计
4. 提升稳定性
5. 优化日志输出,方便测试及后续迭代维护
### 注意事项
- 优卡特p60s受厂家二维码sdk限制,targetSdkVersion必须为22,使用高版本将导致App**无法在设备上运行!!!切记!!!**
~~优卡特p60s受厂家二维码sdk限制,targetSdkVersion必须为22,使用高版本将导致App**无法在设备上运行!!!切记!!!**~~
已确认天波厂家提供的sdk可以在targetSdkVersion 22以上使用,且优卡特设备同样可以使用该方案
### 机型历史
该项目到现在一共使用过3个机型,按时间顺序排列分别是:
1. ~~深圳小兵Q6~~
2. ~~天波580C~~
3. 优卡特p60s
2. 天波580C
3. 优卡特P60S
其中 **小兵Q6** **天波580C** 是早期评估机型,没有正式上线使用过,因此不再需要维护。
如后期有需要可以在TAG "**旧机型(小兵Q6、天波580C)**" 中找回相关代码
其中 **小兵Q6** 是早期评估机型,没有正式上线使用过,因此不再需要维护。
当前已实现天波580C和优卡特P60S的串口读取,并且会根据机型匹配对应的方案。
\ No newline at end of file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.bgycc.smartcanteen"
minSdkVersion 21
targetSdkVersion 22
minSdkVersion 22
targetSdkVersion 29
versionCode 13
versionName "1.3.3" // test分支
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionName "1.3.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
buildConfigField "int", "DaemonVersion", String.valueOf(rootProject.ext.daemon_verson_code)
}
buildTypes {
release {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
proguardFiles 'proguard-rules.pro'
}
zipAlignEnabled true
}
}
flavorDimensions "smart_canteen"
productFlavors {
dev {
dimension "smart_canteen"
buildConfigField "String", "MainWebSocketServerUrl", '"ws://10.187.43.221:9001/websocket/%s/V%s"'
buildConfigField "String", "MainHttpServerHost", '"http://diningbackdev.bgy.com.cn"'
}
tes {
dimension "smart_canteen"
buildConfigField "String", "MainWebSocketServerUrl", '"wss://diningservicetest.bgy.com.cn/websocket/%s/V%s"'
buildConfigField "String", "MainHttpServerHost", '"http://diningbacktest.bgy.com.cn:9000"'
}
uat {
dimension "smart_canteen"
buildConfigField "String", "MainWebSocketServerUrl", '"wss://diningserviceuat.bgy.com.cn/websocket/%s/V%s"'
buildConfigField "String", "MainHttpServerHost", '"https://diningbackuat.bgy.com.cn"'
}
pro {
dimension "smart_canteen"
buildConfigField "String", "MainWebSocketServerUrl", '"wss://diningservice.bgy.com.cn/websocket/%s/V%s"'
buildConfigField "String", "MainHttpServerHost", '"https://diningback.bgy.com.cn"'
}
}
signingConfigs {
......@@ -42,11 +75,9 @@ android {
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
// 命名生成的apk
applicationVariants.all { variant ->
......@@ -58,27 +89,32 @@ android {
.forEach { apkData -> apkData.outputFileName = "SmartCanteen" + "_v" + variant.versionName + ".apk" }
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.java-websocket:Java-WebSocket:1.4.0"
implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
implementation 'com.blankj:utilcode:1.26.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.10'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.10'
implementation 'com.blankj:utilcodex:1.26.0'
implementation 'com.squareup.okhttp3:okhttp:4.3.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.3.1'
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
implementation 'com.squareup.retrofit2:converter-scalars:2.7.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.code.gson:gson:2.8.6'
def room_version = "2.2.4"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.liulishuo.filedownloader:library:1.7.7'
}
......@@ -19,3 +19,166 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# -----------------------------基本 -----------------------------
#
# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-dontoptimize
# 不进行预校验,Android不需要,可加快混淆速度。
-dontpreverify
# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
-repackageclasses
# 混淆时记录日志(打印混淆的详细信息)
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
#
# ----------------------------- 默认保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些类不被混淆
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep class android.support.** {*;}## 保留support下的所有类及其内部类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
#----------------------------------------------------
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
#表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
#当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#表示不混淆任何一个View中的setXxx()和getXxx()方法,
#因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#表示不混淆Parcelable实现类中的CREATOR字段,
#毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留R下面的资源
#-keep class **.R$* {
# *;
#}
#不混淆资源类下static的
-keepclassmembers class **.R$* {
public static <fields>;
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#
# ----------------------------- 第三方 -----------------------------
#
-dontwarn com.google.gson.**
-keep class com.google.gson.**{*;}
-keep interface com.google.gson.**{*;}
#不混淆smartcanteen下的所有类
-keep class com.bgycc.smartcanteen.** { *; }
#不混淆tps下的所有类
-keep class com.telpo.tps550.api.** { *; }
#okhttp
-dontwarn okhttp3.**
-keep class okhttp3.**{*;}
-keep interface okhttp3.**{*;}
#okio
-dontwarn okio.**
-keep class okio.**{*;}
-keep interface okio.**{*;}
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
\ No newline at end of file
package com.bgycc.smartcanteen;
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.bgycc.smartcanteen", appContext.getPackageName());
}
}
package com.bgycc.smartcanteen
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.bgycc.smartcanteen", appContext.packageName)
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bgycc.smartcanteen">
package="com.bgycc.smartcanteen">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
......@@ -13,14 +14,16 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity" android:launchMode="singleTask">
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".RootApp"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity"
android:launchMode="singleInstance"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
......@@ -37,24 +40,16 @@
<data android:scheme="canteen"/>
</intent-filter>
</activity>
<!--<receiver android:name=".receiver.BootReceiver" >-->
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.BOOT_COMPLETED" />-->
<!--&lt;!&ndash;<category android:name="android.intent.category.LAUNCHER" />&ndash;&gt;-->
<!--<category android:name="android.intent.category.DEFAULT" />-->
<!--</intent-filter>-->
<!--</receiver>-->
<!--<receiver android:name=".receiver.UpdateReceiver"-->
<!--android:label="@string/app_name">-->
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.PACKAGE_ADDED" />-->
<!--<action android:name="android.intent.action.PACKAGE_REPLACED" />-->
<!--<data android:scheme="package" />-->
<!--</intent-filter>-->
<!--</receiver>-->
<receiver
android:name=".broadcast.BootBroadcast"
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter android:priority="1000">
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
\ No newline at end of file
package android_serialport_api;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Google官方代码
* 此类的作用为,JNI的调用,用来加载.so文件的
* 获取串口输入输出流
*/
public class SerialPort {
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
static {
System.loadLibrary("serial_port");
}
public SerialPort(String devicePath, int baudRate, int flags) throws SecurityException {
this(new File(devicePath), baudRate, flags);
}
private SerialPort(File device, int baudRate, int flags) throws SecurityException {
if (!device.canRead() || !device.canWrite()) {
try {
Process su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
su.getOutputStream().write(cmd.getBytes());
if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
throw new RuntimeException("没有串口权限");
}
} catch (Exception e) {
throw new RuntimeException("提权失败" + e.getMessage(), e);
}
}
mFd = open(device.getAbsolutePath(), baudRate, flags);
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}
public InputStream getInputStream() {
return mFileInputStream;
}
public OutputStream getOutputStream() {
return mFileOutputStream;
}
private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();
}
package android_serialport_api;
import android.util.Log;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Iterator;
import java.util.Vector;
/**
* Google官方代码
* 此类的作用为,寻找得到有效的串口的物理地址。
* 如果你本身就知道串口的地址如:ttyS1、ttyS2,那么这个类就可以不用了。
*
*/
public class SerialPortFinder {
public class Driver {
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}
private String mDriverName;
private String mDeviceRoot;
Vector<File> mDevices = null;
public Vector<File> getDevices() {
if (mDevices == null) {
mDevices = new Vector<>();
File dev = new File("/dev");
File[] files = dev.listFiles();
int i;
for (i=0; i<files.length; i++) {
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
Log.d(TAG, "Found new device: " + files[i]);
mDevices.add(files[i]);
}
}
}
return mDevices;
}
public String getName() {
return mDriverName;
}
}
private static final String TAG = "SerialPort";
private Vector<Driver> mDrivers = null;
Vector<Driver> getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector<>();
LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers"));
String l;
while((l = r.readLine()) != null) {
// Issue 3:
// Since driver name may contain spaces, we do not extract driver name with split()
String drivername = l.substring(0, 0x15).trim();
String[] w = l.split(" +");
if ((w.length >= 5) && (w[w.length-1].equals("serial"))) {
Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length-4]);
mDrivers.add(new Driver(drivername, w[w.length-4]));
}
}
r.close();
}
return mDrivers;
}
public String[] getAllDevices() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while(itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while(itdev.hasNext()) {
String device = itdev.next().getName();
String value = String.format("%s (%s)", device, driver.getName());
devices.add(value);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
public String[] getAllDevicesPath() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while(itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while(itdev.hasNext()) {
String device = itdev.next().getAbsolutePath();
devices.add(device);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
}
package com.bgycc.smartcanteen
import android.app.ActivityManager
import android.app.Application
import android.content.Context
import com.bgycc.smartcanteen.Storage.PayStorage
import com.bgycc.smartcanteen.action.UpdateAction
import com.bgycc.smartcanteen.helper.TTSHelper
import com.bgycc.smartcanteen.helper.TimerHelper
import com.bgycc.smartcanteen.helper.WifiHelpler
import com.bgycc.smartcanteen.manager.NetworkManager
import com.bgycc.smartcanteen.module.Device
import com.bgycc.smartcanteen.util.LogUtil
import com.blankj.utilcode.constant.PermissionConstants
import com.blankj.utilcode.util.CrashUtils
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.PermissionUtils
import java.io.File
import kotlin.system.exitProcess
class App : Application() {
companion object {
private lateinit var sDefault: App
private const val LOG_DIR = "log"
private val sInstances = HashMap<String, App>()
private var sVersionName = "0.0.0"
private var sVersionCode = 0
var testQRCode = ""
fun getDefault(): App {
return sDefault
}
fun getInstance(processName: String): App? {
return sInstances[processName]
}
fun getDeviceSN(): String {
return Device.getAndroidId()
}
fun getVersionName(): String {
return sVersionName
}
fun getVersionCode(): Int {
return sVersionCode
}
fun exit() {
TimerHelper.shutdown()
exitProcess(0)
}
}
override fun onCreate() {
super.onCreate()
if (isMainProcess()) {
sDefault = this
readVersion()
}
sInstances[getProcessNameCompat()] = this
initLog()
NetworkManager.initialize(this)
WifiHelpler.initialize(this)
PayStorage.initialize(this)
TTSHelper.initialize(this)
Device.initialize(this)
PermissionUtils.permission(PermissionConstants.STORAGE).request()
UpdateAction.getDefault().initialize()
}
fun isMainProcess(): Boolean {
return packageName == getProcessNameCompat()
}
fun getProcessNameCompat(): String {
if (android.os.Build.VERSION.SDK_INT >= 28) {
return getProcessName()
} else {
var processName = ""
val pid = android.os.Process.myPid()
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (process in manager.runningAppProcesses) {
if (process.pid == pid) {
processName = process.processName
}
}
return processName
}
}
private fun readVersion() {
try {
val pi = packageManager.getPackageInfo(packageName, 0)
sVersionName = pi.versionName.toUpperCase()
sVersionCode = pi.versionCode
} catch (e: Exception) {}
}
private fun initLog() {
LogUtil.setEnable(BuildConfig.DEBUG)
val logDir: String = applicationContext.filesDir.absolutePath + File.separator + LOG_DIR
CrashUtils.init(logDir)
LogUtils.getConfig()
.setDir(logDir)
.setLog2FileSwitch(true)
.setBorderSwitch(false)
.saveDays = 7
}
}
\ No newline at end of file
package com.bgycc.smartcanteen
object AppConfig {
enum class Server {
DEV,
TEST,
UAT,
PROD
}
val SERVER = Server.TEST // develop分支
fun getMainWebSocketServerHost(): String {
return when (SERVER) {
Server.DEV -> "10.187.43.221:9001"
Server.TEST -> "diningservicetest.bgy.com.cn"
Server.UAT -> "diningserviceuat.bgy.com.cn"
else -> "diningservice.bgy.com.cn"
}
}
fun getMainWebSocketServerUrl(id: String, version: String): String {
return when (SERVER) {
Server.DEV -> "ws://${getMainWebSocketServerHost()}/websocket/$id/V$version"
Server.TEST -> "wss://${getMainWebSocketServerHost()}/websocket/$id/V$version"
Server.UAT -> "wss://${getMainWebSocketServerHost()}/websocket/$id/V$version"
else -> "wss://${getMainWebSocketServerHost()}/websocket/$id/V$version"
}
}
fun getMainHttpServerHost(): String {
return when (SERVER) {
Server.DEV -> "http://diningbackdev.bgy.com.cn"
Server.TEST -> "http://diningbacktest.bgy.com.cn:9000"
Server.UAT -> "https://diningbackuat.bgy.com.cn"
else -> "https://diningback.bgy.com.cn"
}
}
}
\ No newline at end of file
package com.bgycc.smartcanteen;
import com.bgycc.smartcanteen.data.DatabaseManager;
import com.bgycc.smartcanteen.data.dao.CommandDao;
import com.bgycc.smartcanteen.data.dao.PayDataDao;
import com.bgycc.smartcanteen.data.dao.PayResponseDao;
import com.bgycc.smartcanteen.entity.PayRequest;
import com.bgycc.smartcanteen.entity.typeadapter.PayRequestTypeAdapter;
import com.bgycc.smartcanteen.repository.CommandRepository;
import com.bgycc.smartcanteen.repository.PayDataRepository;
import com.bgycc.smartcanteen.repository.PayResponseRepository;
import com.bgycc.smartcanteen.viewModel.ViewModelFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@SuppressWarnings("all")
public class Injection {
private static CommandRepository commandRepositorySingleton;
private static PayDataRepository payDataRepositorySingleton;
private static PayResponseRepository payResponseRepositorySingleton;
private static Gson globalGson = new GsonBuilder()
.registerTypeAdapter(PayRequest.class, new PayRequestTypeAdapter())
.create();
public static ViewModelFactory injectFactory(String deviceSN) {
CommandRepository commandRepository = provideCommandRepositorySingleton();
PayDataRepository payDataRepository = providePayDataRepositorySingleton();
PayResponseRepository payResponseRepository = providePayResponseRepositorySingleton();
return new ViewModelFactory(commandRepository,
payDataRepository,
payResponseRepository,
globalGson,
deviceSN);
}
public static Gson provideGson() {
return globalGson;
}
private static CommandRepository provideCommandRepository() {
CommandDao dao = DatabaseManager.getInstance().getCommandDao();
return new CommandRepository(dao);
}
private static PayDataRepository providePayDataRepository() {
PayDataDao dao = DatabaseManager.getInstance().getPayDataDao();
return new PayDataRepository(dao);
}
private static PayResponseRepository providePayResponseRepository() {
PayResponseDao dao = DatabaseManager.getInstance().getPayResponseDao();
return new PayResponseRepository(dao);
}
public static CommandRepository provideCommandRepositorySingleton() {
if (commandRepositorySingleton != null) return commandRepositorySingleton;
CommandDao dao = DatabaseManager.getInstance().getCommandDao();
commandRepositorySingleton = new CommandRepository(dao);
return commandRepositorySingleton;
}
public static PayDataRepository providePayDataRepositorySingleton() {
if (payDataRepositorySingleton != null) return payDataRepositorySingleton;
PayDataDao dao = DatabaseManager.getInstance().getPayDataDao();
payDataRepositorySingleton = new PayDataRepository(dao);
return payDataRepositorySingleton;
}
public static PayResponseRepository providePayResponseRepositorySingleton() {
if (payResponseRepositorySingleton != null) return payResponseRepositorySingleton;
PayResponseDao dao = DatabaseManager.getInstance().getPayResponseDao();
payResponseRepositorySingleton = new PayResponseRepository(dao);
return payResponseRepositorySingleton;
}
}
package com.bgycc.smartcanteen;
import android.app.Application;
import com.blankj.utilcode.util.CrashUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import java.io.File;
public class RootApp extends Application {
private static final String LOG_PREFIX = "app";
private static final String LOG_DIR = "log";
@Override
public void onCreate() {
super.onCreate();
Utils.init(getApplicationContext());
String logDir = PathUtils.getExternalStoragePath() + File.separator + LOG_DIR;
CrashUtils.init(logDir);
LogUtils.getConfig()
.setDir(logDir)
.setLog2FileSwitch(true)
.setBorderSwitch(false)
.setFilePrefix(LOG_PREFIX);
}
}
package com.bgycc.smartcanteen.Storage;
import android.content.Context;
public class AppStorage extends Storage {
protected AppStorage(String name, Context context) {
super(name, context);
}
}
package com.bgycc.smartcanteen.Storage;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PayStorage extends Storage {
static final String TAG = PayStorage.class.getSimpleName();
enum Key {
PAY_LIST
}
static PayStorage sInstance;
public static void initialize(Context context) {
sInstance = new PayStorage(context);
}
public static PayStorage getInstance() {
return sInstance;
}
JSONArray mPayList = new JSONArray();
PayStorage(Context context) {
super(TAG, context);
try {
String tmp = mSharedPreferences.getString(Key.PAY_LIST.name(), "[]");
mPayList = new JSONArray(tmp);
} catch (JSONException e) {
e.printStackTrace();
}
}
public synchronized void add(JSONObject payInfo) {
mPayList.put(payInfo);
}
public synchronized JSONArray getPayList() {
return mPayList;
}
public synchronized void savePayList() {
mSharedPreferences.edit()
.putString(Key.PAY_LIST.name(), mPayList.toString())
.apply();
}
public synchronized void clearPayList() {
mPayList = new JSONArray();
mSharedPreferences.edit()
.remove(Key.PAY_LIST.name())
.apply();
}
}
package com.bgycc.smartcanteen.Storage;
import android.content.Context;
import android.content.SharedPreferences;
public abstract class Storage {
SharedPreferences mSharedPreferences;
protected Storage(String name, Context context) {
mSharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE);
}
}
package com.bgycc.smartcanteen.action;
public abstract class Action {
public enum State {
RESPONSE_TIMEOUT,
RESPONSE_FAIL,
RESQUEST_TIMEOUT,
RESQUEST_FAIL,
TIMEOUT,
FAIL,
UNINIT,
INITED,
STARTED,
RESQUEST,
RESQUEST_SUCCESS,
RESPONSE_SUCCESS,
SUCCESS
}
private String mAction;
private State mState;
private ActionResult mActionResult;
protected Action(String action) {
mState = State.UNINIT;
mAction = action;
if (mAction != null && !mAction.isEmpty()) {
mState = State.INITED;
}
}
public String getAction() {
return mAction;
}
public State getState() {
return mState;
}
protected void setState(State state) {
mState = state;
}
protected void setActionResult(ActionResult result) {
mActionResult = result;
}
boolean isAction(String action) {
if (mAction == null || mAction.isEmpty()) return false;
return mAction.equals(action);
}
protected void success(String message) {
if (mActionResult != null) mActionResult.onSuccess(message);
}
protected void fail(String message) {
if (mActionResult != null) mActionResult.onFail(message);
}
}
package com.bgycc.smartcanteen.action;
public enum ActionEnum {
// 检查设备
CHECK_DEVICE,
AUTO_CHECK_DEVICE,
// 支付
PAY_,
PAY_ONLINE,
PAY_OFFLINE,
PAY_RESULT,
// Log
LOG_PULL,
// 配置
CONFIG_,
CONFIG_LOG,
CONFIG_POWER,
CONFIG_WIFI,
CONFIG_UPDATE,
CONFIG_SERVER,
CONFIG_PAYCODE_RULE,
CONFIG_CLEAR_OFFLINE_RECODE
}
package com.bgycc.smartcanteen.action;
public interface ActionResult {
void onSuccess(String message);
void onFail(String message);
}
package com.bgycc.smartcanteen.action
import com.bgycc.smartcanteen.App
import com.bgycc.smartcanteen.server.http.MainHttpClient
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.PathUtils
import com.blankj.utilcode.util.ZipUtils
import org.json.JSONObject
import java.io.File
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
object LogAction : Action(ActionEnum.LOG_PULL.name) {
val TAG = LogAction::class.java.simpleName
fun exec(data: JSONObject) {
if (state != State.INITED) return
try {
val formatSrc = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val type = data.getString("logType")
val startTime = formatSrc.parse(data.getString("startTime"))
startTime.hours = 0
startTime.minutes = 0
startTime.seconds = 0
val endTime = formatSrc.parse(data.getString("endTime"))
endTime.hours = 23
endTime.minutes = 59
endTime.seconds = 59
if (startTime > endTime) return
val formatDst = SimpleDateFormat("yyyy_MM_dd", Locale.getDefault())
val dateList = ArrayList<String>()
val c = Calendar.getInstance()
c.time = startTime
while (c.time <= endTime) {
dateList.add(formatDst.format(c.time))
c.add(Calendar.DATE, 1)
}
Thread {
try {
state = State.STARTED
val logZipFile = File(PathUtils.getExternalAppCachePath(), "upload/log.zip")
val logUploadDir = File(PathUtils.getExternalAppCachePath(), "upload/log")
FileUtils.delete(logZipFile)
FileUtils.delete(logUploadDir)
logUploadDir.mkdirs()
val logDir = when (type) {
"system" -> File(PathUtils.getExternalStoragePath(), "boot_log")
else -> File(PathUtils.getExternalAppCachePath(), "log")
}
FileUtils.listFilesInDir(logDir)?.forEach {
val date = Date(it.lastModified())
if (date >= startTime && date <= endTime) {
FileUtils.copy(it, File(logUploadDir, it.name))
}
}
ZipUtils.zipFile(logUploadDir, logZipFile)
FileUtils.delete(logUploadDir)
MainHttpClient.uploadLog(logZipFile, "$type${formatSrc.format(startTime)}${formatSrc.format(endTime)}${App.getDeviceSN()}.zip")
} catch (e: Exception) {}
state = State.INITED
}.start()
} catch (e: Exception) {}
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.action;
import com.bgycc.smartcanteen.App;
import com.bgycc.smartcanteen.Storage.PayStorage;
import com.bgycc.smartcanteen.event.PayStateEvent;
import com.bgycc.smartcanteen.event.QRCodeRepeatEvent;
import com.bgycc.smartcanteen.helper.TTSHelper;
import com.bgycc.smartcanteen.helper.TimerHelper;
import com.bgycc.smartcanteen.server.websocket.MainWebSocket;
import com.bgycc.smartcanteen.task.QRCodeTask;
import com.bgycc.smartcanteen.util.DateUtil;
import com.bgycc.smartcanteen.util.LogUtil;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Locale;
public class PayOfflineAction extends Action {
private static final String TAG = PayOfflineAction.class.getSimpleName();
private static PayOfflineAction sInstance;
public static PayOfflineAction getDefault() {
if (sInstance == null) {
sInstance = new PayOfflineAction();
}
return sInstance;
}
private ArrayList<String> mPayCodeHistory = new ArrayList<>();
public PayOfflineAction() {
super(ActionEnum.PAY_OFFLINE.name());
}
@Override
protected void setState(State state) {
setState(state, "");
}
protected void setState(State state, String message) {
if (state != getState()) {
switch (state) {
case INITED:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.IDLE, message));
break;
case FAIL:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.FAIL, message));
TimerHelper.INSTANCE.timeout(new Runnable() {
@Override
public void run() {
setState(State.INITED);
}
}, 3000);
break;
case RESQUEST_SUCCESS:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.SUCCESS, message));
TimerHelper.INSTANCE.timeout(new Runnable() {
@Override
public void run() {
setState(State.INITED);
}
}, 3000);
break;
}
}
super.setState(state);
}
public void exec(String payCode, String payCodeType) {
exec(payCode, payCodeType, true);
}
public void exec(String payCode, String payCodeType, boolean checkRepeat) {
setState(State.STARTED);
if (!QRCodeTask.TYPE_BHPAY.equals(payCodeType)) {
setState(State.FAIL, "第三方支付异常");
return;
}
if (checkRepeat && mPayCodeHistory.contains(payCode)) {
setState(State.FAIL, "请不要重复扫码");
EventBus.getDefault().post(new QRCodeRepeatEvent());
return;
}
addPayCodeHistory(payCode);
try {
JSONObject params = new JSONObject();
params.put("equipmentNo", App.Companion.getDeviceSN());
params.put("payCode", payCode);
params.put("terminalType", payCodeType);
params.put("time", DateUtil.getCurrentTimestamp());
PayStorage.getInstance().add(params);
PayStorage.getInstance().savePayList();
setState(State.RESQUEST_SUCCESS);
LogUtil.i(TAG, params.toString());
} catch (Exception e) {
e.printStackTrace();
setState(State.FAIL);
}
}
public void upload() {
JSONArray list = PayStorage.getInstance().getPayList();
if (list == null || list.length() == 0) return;
final MainWebSocket.Response response = new MainWebSocket.Response() {
@Override
protected void onSuccess(long sessionId, JSONObject data, String message) {
cancel();
PayStorage.getInstance().clearPayList();
}
@Override
protected void onFail(long sessionId, JSONObject data, String message, String code) {
cancel();
}
};
MainWebSocket.action(getAction(), list, response);
LogUtil.i(TAG, list.toString());
}
public void addPayCodeHistory(String payCode) {
if (mPayCodeHistory.contains(payCode)) {
mPayCodeHistory.remove(payCode);
}
mPayCodeHistory.add(payCode);
if (mPayCodeHistory.size() > 2000) {
mPayCodeHistory.remove(0);
}
}
}
package com.bgycc.smartcanteen.action;
import com.bgycc.smartcanteen.App;
import com.bgycc.smartcanteen.event.PayStateEvent;
import com.bgycc.smartcanteen.helper.TimerHelper;
import com.bgycc.smartcanteen.server.websocket.MainWebSocket;
import com.bgycc.smartcanteen.util.DateUtil;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONObject;
public class PayOnlineAction extends Action {
private static final String TAG = PayOnlineAction.class.getSimpleName();
private static PayOnlineAction sInstance;
public static PayOnlineAction getDefault() {
if (sInstance == null) {
sInstance = new PayOnlineAction();
}
return sInstance;
}
private String mPayCode;
private long mTimeoutId = -1;
private Runnable mActionRunnable;
public PayOnlineAction() {
super(ActionEnum.PAY_ONLINE.name());
MainWebSocket.subscribe(ActionEnum.PAY_RESULT.name(), mPayResultResponse);
}
private void cancelTimeout() {
TimerHelper.INSTANCE.cancel(mTimeoutId);
mTimeoutId = -1;
mActionRunnable = null;
}
private void setState(State state, String message) {
if (state != getState()) {
switch (state) {
case INITED:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.IDLE, message));
break;
case STARTED:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.WAIT, message));
cancelTimeout();
break;
case RESPONSE_SUCCESS:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.SUCCESS, message));
cancelTimeout();
mPayCode = "";
success(message);
TimerHelper.INSTANCE.timeout(new Runnable() {
@Override
public void run() {
setState(State.INITED, "");
}
}, 3000);
break;
case RESQUEST_FAIL:
case RESPONSE_FAIL:
EventBus.getDefault().post(new PayStateEvent(PayStateEvent.StateEnum.FAIL, message));
cancelTimeout();
mPayCode = "";
fail(message);
TimerHelper.INSTANCE.timeout(new Runnable() {
@Override
public void run() {
setState(State.INITED, "");
}
}, 3000);
break;
case TIMEOUT:
cancelTimeout();
mPayCode = "";
fail(message);
setState(State.INITED);
return;
}
}
setState(state);
}
public void exec(final String payCode, final String payCodeType, ActionResult result) {
if (getState() != State.INITED) {
if (result != null) {
result.onFail("正在忙");
}
return;
}
setState(State.STARTED, "");
setActionResult(result);
mPayCode = payCode;
PayOfflineAction.getDefault().addPayCodeHistory(payCode);
mTimeoutId = TimerHelper.INSTANCE.loop(new TimerHelper.LoopTask() {
@Override
public void run(long id, boolean isLastTime) {
if (isLastTime) {
setState(State.TIMEOUT, "交易超时");
PayOfflineAction.getDefault().exec(payCode, payCodeType, false);
} else {
if (mActionRunnable != null) {
mActionRunnable.run();
}
}
}
}, 5000, 2, 5000);
final MainWebSocket.Response response = new MainWebSocket.Response() {
@Override
protected void onSuccess(long sessionId, JSONObject data, String message) {
if (getState() == State.RESQUEST) {
setState(State.RESQUEST_SUCCESS, message);
}
}
@Override
protected void onFail(long sessionId, JSONObject data, String message, String code) {
if (getState() == State.RESQUEST) {
setState(State.RESQUEST_FAIL, message);
}
}
};
try {
final JSONObject params = new JSONObject();
params.put("equipmentNo", App.Companion.getDeviceSN());
params.put("payCode", payCode);
params.put("terminalType", payCodeType);
params.put("time", DateUtil.getCurrentTimestamp());
mActionRunnable = new Runnable() {
@Override
public void run() {
setState(State.RESQUEST, "");
MainWebSocket.action(getAction(), params, response);
}
};
mActionRunnable.run();
} catch (Exception e) {
e.printStackTrace();
}
}
MainWebSocket.Response mPayResultResponse = new MainWebSocket.Response() {
@Override
protected void onSuccess(long sessionId, JSONObject data, String message) {
if (data == null) return;
String payCode = data.optString("payCode", "");
if (payCode.equals(mPayCode)) {
setState(State.RESPONSE_SUCCESS, message);
MainWebSocket.response(sessionId, "0", "已接收");
}
}
@Override
protected void onFail(long sessionId, JSONObject data, String message, String code) {
if (data == null) return;
String payCode = data.optString("payCode", "");
if (payCode.equals(mPayCode)) {
setState(State.RESPONSE_FAIL, message);
MainWebSocket.response(sessionId, "0", "已接收");
}
}
};
}
package com.bgycc.smartcanteen.action
import com.bgycc.smartcanteen.BuildConfig
import com.bgycc.smartcanteen.event.SettingStateEvent
import com.bgycc.smartcanteen.helper.TimerHelper
import com.bgycc.smartcanteen.util.DeviceProxy
import com.blankj.utilcode.util.*
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.greenrobot.eventbus.EventBus
import org.json.JSONObject
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
class UpdateAction private constructor() : Action(ActionEnum.CONFIG_UPDATE.name) {
companion object {
private val TAG = UpdateAction::class.java.simpleName
private val FILE_UPDATE_APK = File(PathUtils.getExternalStoragePath(), "SmartCanteen-update.apk")
private var sDefault: UpdateAction? = null
@Synchronized fun getDefault(): UpdateAction {
if (sDefault == null) {
sDefault = UpdateAction()
}
return sDefault!!
}
}
private var mTimeoutId = -1L
private val mHttp = OkHttpClient.Builder()
.connectTimeout(6, TimeUnit.SECONDS)
.readTimeout(6, TimeUnit.SECONDS)
.writeTimeout(6, TimeUnit.SECONDS)
.build()
private fun finish() {
TimerHelper.cancel(mTimeoutId)
mTimeoutId = -1
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
}
fun initialize() {
FileUtils.delete(FILE_UPDATE_APK)
}
fun exec(data: JSONObject) {
if (state != State.INITED) return
val url: String
try {
url = data.getString("url")
if (url.isEmpty()) return
FILE_UPDATE_APK.delete()
FILE_UPDATE_APK.parentFile.mkdirs()
} catch (e: IOException) {
return
}
EventBus.getDefault().post(SettingStateEvent(1, "正在下载更新包"))
state = State.STARTED
mTimeoutId = TimerHelper.timeout({
EventBus.getDefault().post(SettingStateEvent(1, "更新包下载超时"))
finish()
}, 10000)
val request = Request.Builder().url(url).build()
mHttp.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: Call, e: IOException) {
EventBus.getDefault().post(SettingStateEvent(1, "更新包下载失败"))
finish()
}
override fun onResponse(call: Call, response: Response) {
try {
val body = response.body()!!
val success = FileIOUtils.writeFileFromIS(FILE_UPDATE_APK, BufferedInputStream(body.byteStream()))
if (success) {
val info = AppUtils.getApkInfo(FILE_UPDATE_APK)
if (info == null ||
(info.packageName == BuildConfig.APPLICATION_ID && info.versionCode < BuildConfig.VERSION_CODE)) {
EventBus.getDefault().post(SettingStateEvent(99, "不允许安装低版本"))
} else {
EventBus.getDefault().post(SettingStateEvent(99, "安装更新包"))
DeviceProxy.updateApp(FILE_UPDATE_APK)
}
finish()
return
}
} catch (e: Exception) {}
EventBus.getDefault().post(SettingStateEvent(99, "更新包安装失败"))
finish()
}
})
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.action
import android.util.Log
import com.bgycc.smartcanteen.event.SettingStateEvent
import com.bgycc.smartcanteen.helper.TimerHelper
import com.bgycc.smartcanteen.helper.WifiHelpler
import org.greenrobot.eventbus.EventBus
import org.json.JSONObject
class WifiAction private constructor() : Action(ActionEnum.CONFIG_WIFI.name) {
companion object {
private val TAG = WifiAction::class.java.simpleName
private var sDefault: WifiAction? = null
@Synchronized fun getDefault(): WifiAction {
if (sDefault == null) {
sDefault = WifiAction()
}
return sDefault!!
}
}
private fun fail() {
fail("")
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
}
fun exec(data: JSONObject) {
if (state != State.INITED) return
try {
val ssid = data.getString("ssid")
val pwd = data.getString("pwd")
val type = data.optString("type", "wpa")
val identity = data.optString("identity")
state = State.STARTED
WifiHelpler.connect(ssid, identity, pwd, type)
EventBus.getDefault().post(SettingStateEvent(1, "正在启动Wifi"))
if (!WifiHelpler.setEnable(true)) {
EventBus.getDefault().post(SettingStateEvent(1, "无法启动Wifi"))
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
return
}
EventBus.getDefault().post(SettingStateEvent(10, "正在配置Wifi"))
val config = WifiHelpler.createWifiConfiguration(ssid, identity, pwd, type)
if (config == null) {
EventBus.getDefault().post(SettingStateEvent(10, "Wifi参数有误"))
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
return
}
try {
if (!WifiHelpler.removeWifiConfiguration(ssid)) throw Exception("无法修改Wifi配置")
val netId = WifiHelpler.addNetwork(config)
if (!WifiHelpler.enableNetwork(netId)) throw Exception("无法应用Wifi配置")
} catch (e: Exception) {
EventBus.getDefault().post(SettingStateEvent(20, e.message))
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
return
}
EventBus.getDefault().post(SettingStateEvent(30, "正在连接Wifi"))
TimerHelper.loop({ id, isLastTime ->
if (isLastTime) {
if (state == State.STARTED) {
state = State.FAIL
EventBus.getDefault().post(SettingStateEvent(30, "无法连接Wifi"))
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
}
} else {
val info = WifiHelpler.getWifiInfo()
if (info != null) {
Log.i(TAG, "ip: " + info.ipAddress)
if (info.ipAddress != 0) {
state = State.SUCCESS
EventBus.getDefault().post(SettingStateEvent(30, "已连接Wifi"))
TimerHelper.timeout({
state = State.INITED
EventBus.getDefault().post(SettingStateEvent(-1))
}, 3000)
TimerHelper.cancel(id)
}
}
}
}, 1000, 10)
} catch (e: Exception) {
state = State.INITED
}
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.activity
import android.app.Activity
import android.view.View
import android.view.Window
import android.view.WindowManager
open class BaseActivity : Activity() {
fun translucentStatusBar() {
if (android.os.Build.VERSION.SDK_INT >= 19) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
fun lightStatusBar() {
if (android.os.Build.VERSION.SDK_INT >= 23) {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
fun hideStatusBar() {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
fun hideActionBar() {
requestWindowFeature(Window.FEATURE_NO_TITLE)
}
fun hideNavigation() {
val decorView = window.decorView
val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
decorView.systemUiVisibility = uiOptions
}
fun keepScreenOn(on: Boolean) {
when (on) {
true -> window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
false -> window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.activity
import android.content.Context
import android.media.AudioManager
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.animation.Animation
import android.view.animation.RotateAnimation
import com.bgycc.smartcanteen.App
import com.bgycc.smartcanteen.AppConfig
import com.bgycc.smartcanteen.BuildConfig
import com.bgycc.smartcanteen.R
import com.bgycc.smartcanteen.event.*
import com.bgycc.smartcanteen.helper.EthernetHelper
import com.bgycc.smartcanteen.helper.TTSHelper
import com.bgycc.smartcanteen.helper.TimerHelper
import com.bgycc.smartcanteen.helper.WifiHelpler
import com.bgycc.smartcanteen.manager.NetworkManager
import com.bgycc.smartcanteen.server.websocket.event.ConnectStateEvent
import com.bgycc.smartcanteen.server.websocket.MainWebSocket
import com.bgycc.smartcanteen.server.websocket.event.RecvMessageEvent
import com.bgycc.smartcanteen.server.websocket.event.SendMessageEvent
import com.bgycc.smartcanteen.task.ButtonTask
import com.bgycc.smartcanteen.task.QRCodeTask
import com.bgycc.smartcanteen.util.StringUtil
import com.blankj.utilcode.util.LogUtils
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : BaseActivity() {
companion object {
var TAG: String = MainActivity::class.java.simpleName
}
private var mAudioManager: AudioManager? = null
private val mHandler = Handler()
private var mTimerId = -1L
override fun onCreate(savedInstanceState: Bundle?) {
hideActionBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
hideStatusBar()
keepScreenOn(true)
initView()
EventBus.getDefault().register(this)
init()
initTimer()
}
override fun onResume() {
super.onResume()
hideNavigation()
}
override fun onDestroy() {
uninitTimer()
EventBus.getDefault().unregister(this)
super.onDestroy()
App.exit()
}
private fun init() {
mAudioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
MainWebSocket.initialize()
QRCodeTask.getInstance().start()
ButtonTask.start()
}
private fun initView() {
_debug.visibility = View.GONE
_setting.alpha = 0f
_setting_img.post{
val anim = RotateAnimation(0f, 360f, 0.5f * _setting_img.width, 0.5f * _setting_img.height)
anim.duration = 2000
anim.repeatMode = Animation.RESTART
anim.repeatCount = -1
_setting_img.startAnimation(anim)
}
_version.text = "Version: v${App.getVersionName()}(${App.getVersionCode()})"
_sn.text = "SN: ${App.getDeviceSN()}"
if (BuildConfig.DEBUG) {
_message.setOnClickListener {
App.testQRCode = "380000000000000000"
}
_debug.setOnClickListener {
_debug.visibility = View.GONE
_logo.visibility = View.VISIBLE
}
_logo.setOnClickListener {
_debug.visibility = View.VISIBLE
_logo.visibility = View.GONE
}
}
}
private fun initTimer() {
mTimerId = TimerHelper.loop({ _, _ ->
mHandler.post {
_eth.text = "Ethernet Mac: ${EthernetHelper.getMacAddress()} IP: ${EthernetHelper.getIpString()}"
_wifi.text = "Wifi Mac: ${WifiHelpler.getMacAddress()} SSID: ${WifiHelpler.getSSID()} IP: ${WifiHelpler.getIpString()}"
_time.text = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
}
}, 1000)
}
private fun uninitTimer() {
TimerHelper.cancel(mTimerId)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.i(TAG, "keyCode: $keyCode")
// ToastUtils.showLong("keyCode: $keyCode")
when(keyCode) {
KeyEvent.KEYCODE_PAGE_UP,
KeyEvent.KEYCODE_NUMPAD_ADD -> {
mAudioManager!!.adjustStreamVolume(
AudioManager.STREAM_SYSTEM,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI
)
}
KeyEvent.KEYCODE_PAGE_DOWN,
KeyEvent.KEYCODE_NUMPAD_SUBTRACT -> {
mAudioManager!!.adjustStreamVolume(
AudioManager.STREAM_SYSTEM,
AudioManager.ADJUST_LOWER,
AudioManager.FLAG_SHOW_UI
)
}
}
return super.onKeyDown(keyCode, event)
}
fun printQRCode(event: QRCodeEvent) {
var msg = "QRCode: " + event.string
if (event.payCodeType != null) {
msg += String.format(" (%s)", event.payCodeType)
}
_qrcode.text = msg
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: QRCodeEvent) {
printQRCode(event)
TTSHelper.speak("beep")
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: QRCodeInvalidEvent) {
TTSHelper.speak("无效二维码")
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: QRCodeRepeatEvent) {
TTSHelper.speak("请不要重复扫码")
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: PayStateEvent) {
if (event.state == PayStateEvent.StateEnum.IDLE) {
_message.setTextColor(0xFF333333.toInt())
_message.text = "请出示付款码"
} else if (event.state == PayStateEvent.StateEnum.WAIT) {
_message.setTextColor(0xFF333333.toInt())
_message.text = "交易处理中"
} else if (event.state == PayStateEvent.StateEnum.SUCCESS) {
_message.setTextColor(0xFF009900.toInt())
if (StringUtil.isEmpty(event.message)) event.message = "支付成功"
_message.text = event.message
TTSHelper.speak(event.message)
} else if (event.state == PayStateEvent.StateEnum.FAIL) {
_message.setTextColor(0xFFFF0000.toInt())
if (StringUtil.isEmpty(event.message)) event.message = "支付失败"
_message.text = event.message
TTSHelper.speak(event.message)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: ConnectStateEvent) {
val base = "Server: " + AppConfig.getMainWebSocketServerUrl(App.getDeviceSN(), BuildConfig.VERSION_NAME) + " "
if (event.state == ConnectStateEvent.OFFLINE) _server.text = base + "${NetworkManager.currentTypeString} 未连接"
else if (event.state == ConnectStateEvent.CONNECTING) _server.text = base + "${NetworkManager.currentTypeString} 正在连接..."
else if (event.state == ConnectStateEvent.CONNECTED) _server.text = base + "${NetworkManager.currentTypeString} 已连接"
else if (event.state == ConnectStateEvent.RECONNECTING) _server.text = base + "${NetworkManager.currentTypeString} 正在重连..."
else if (event.state == ConnectStateEvent.CHANGE_NETWORK) _server.text = base + "${NetworkManager.currentTypeString} 正在切换..."
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: SendMessageEvent) {
val time = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())
val log = "Send: $time - ${event.message}"
_send_msg.text = log
LogUtils.file(TAG, log)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: RecvMessageEvent) {
val time = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())
val log = "Receive: $time - ${event.message}"
_recv_msg.text = log
LogUtils.file(TAG, log)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: SettingStateEvent) {
if (event.progress < 0) {
_setting.animate().setDuration(300).alpha(0f)
} else {
_setting_msg.text = event.message
_setting.animate().setDuration(300).alpha(1f)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: LogEvent) {
if (_debug.visibility == View.GONE) {
_logo.visibility = View.GONE
_debug.visibility = View.VISIBLE
} else {
_logo.visibility = View.VISIBLE
_debug.visibility = View.GONE
}
}
}
package com.bgycc.smartcanteen.api;
import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface SCApi {
/**
* 上传日志文件到服务器
* @param file 要上传的日志文件
*/
@Multipart
@POST("upload")
Call<String> upload(@Part MultipartBody.Part file);
}
package com.bgycc.smartcanteen.api;
import com.bgycc.smartcanteen.BuildConfig;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;
/**
* 目前以下实例都不使用单例(因为相关操作频率都非常低)
*/
public class SCRetrofit {
private static final long TIMEOUT = 10;
public static SCApi createApi(OkHttpClient client) {
Retrofit retrofit = createRetrofit(client);
return retrofit.create(SCApi.class);
}
public static OkHttpClient.Builder createOkHttpClientBuilder() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT, TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(interceptor);
}
return builder;
}
private static Retrofit createRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.client(client)
.baseUrl(BuildConfig.MainHttpServerHost)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
}
}
package com.bgycc.smartcanteen.receiver;
package com.bgycc.smartcanteen.broadcast;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.bgycc.smartcanteen.activity.MainActivity;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
Intent intent2 = new Intent(context, MainActivity.class);
intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent2);
}
}
import com.bgycc.smartcanteen.activity.MainActivity;
// 开机广播,保证设备启动后应用能被主动唤起
public class BootBroadcast extends BroadcastReceiver {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
Intent scIntent = new Intent(context, MainActivity.class);
scIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(scIntent);
}
}
package com.bgycc.smartcanteen.command;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.CommandResponse;
import com.google.gson.Gson;
public abstract class CommandHandler {
protected Command command;
protected Gson gson;
protected CommandProgressCallback commandProgressCallback;
public CommandHandler(Command command, Gson gson, CommandProgressCallback commandProgressCallback) {
this.command = command;
this.gson = gson;
this.commandProgressCallback = commandProgressCallback;
}
public abstract CommandResponse run() throws Exception;
void idle(String message, int progress) {
commandProgressCallback.idle(message, progress);
}
void wait(String message, int progress) {
commandProgressCallback.wait(message, progress);
}
void success(String message, int progress) {
commandProgressCallback.success(message, progress);
}
void failed(String message, int progress) {
commandProgressCallback.failed(message, progress);
}
CommandResponse failedResult(String reason) {
return CommandResponse.failed(reason);
}
CommandResponse successResult(String reason) {
return CommandResponse.success(reason);
}
}
package com.bgycc.smartcanteen.command;
public interface CommandProgressCallback {
void idle(String message, int progress);
void wait(String message, int progress);
void success(String message, int progress);
void failed(String message, int progress);
}
package com.bgycc.smartcanteen.command;
import com.bgycc.smartcanteen.api.SCApi;
import com.bgycc.smartcanteen.api.SCRetrofit;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.CommandLog;
import com.bgycc.smartcanteen.entity.CommandResponse;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.ZipUtils;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import retrofit2.Response;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
/**
* 设备指令: 上传日志
*/
public class LogCommandHandler extends CommandHandler {
private static final String BOOT_LOG = "system";
private static final String ZIP_FILE = "log.zip";
private static final String UPLOAD_DIR = "upload_log";
private static final long DEFAULT_DELAY = 1000;
private volatile boolean start = false;
private SimpleDateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
private SimpleDateFormat nameFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
private String deviceSN;
private CommandLog commandLog;
private File uploadDir;
private Date startTime;
private Date endTime;
private static LogCommandHandler instance;
public static synchronized LogCommandHandler getInstance(Command command, Gson gson, String deviceSN, CommandProgressCallback callback) {
if (instance == null) {
instance = new LogCommandHandler(command, gson, deviceSN, callback);
} else {
if (!instance.start) {
instance.init(command, gson, deviceSN, callback);
}
}
return instance;
}
private void init(Command command, Gson gson, String deviceSN, CommandProgressCallback callback) {
this.command = command;
this.gson = gson;
this.commandProgressCallback = callback;
this.deviceSN = deviceSN;
this.commandLog = gson.fromJson(command.getData(), CommandLog.class);
parseDate();
}
private LogCommandHandler(Command command, Gson gson, String deviceSN, CommandProgressCallback callback) {
super(command, gson, callback);
this.deviceSN = deviceSN;
this.commandLog = gson.fromJson(command.getData(), CommandLog.class);
parseDate();
}
private void parseDate() {
CommandLog.CommandLogData data = commandLog.getData();
startTime = null;
endTime = null;
try {
startTime = parseFormat.parse(data.getStartTime());
} catch (ParseException e) {
LogUtils.w(TAG, "日志上传,起始时间解析失败: " + data.toString(), e);
}
try {
endTime = parseFormat.parse(data.getEndTime());
} catch (ParseException e) {
LogUtils.w(TAG, "日志上传,结束时间解析失败: " + data.toString(), e);
}
LogUtils.d(TAG, "筛选从" + startTime.toString() + "到" + endTime.toString() + "的日志文件");
}
// 检查传入的指定日期是否合法
private boolean checkLogCommand() {
return startTime != null && endTime != null && endTime.after(startTime);
}
// 获取指定类型及日期的日志文件
private File getZipLogs() throws InterruptedException {
CommandLog.CommandLogData data = commandLog.getData();
File logDir = getLogDirByType(data.getLogType());
wait("建立临时目录", 0);
uploadDir = tempDirInit();
Thread.sleep(DEFAULT_DELAY);
if (uploadDir == null) return null;
if (!copyTargetFiles(logDir, uploadDir)) {
LogUtils.d(TAG, "没有匹配的日志文件");
return null;
}
Thread.sleep(DEFAULT_DELAY);
File zip = new File(PathUtils.getExternalStoragePath() + File.separator + ZIP_FILE);
try {
wait("开始压缩", 40);
ZipUtils.zipFile(uploadDir, zip);
} catch (IOException e) {
LogUtils.e(TAG, "压缩日志文件失败: " + e.getMessage(), e);
FileUtils.delete(uploadDir);
uploadDir = null;
return null;
}
return zip;
}
@NotNull
private File getLogDirByType(String logType) {
File logDir;
if (logType.equals(BOOT_LOG)) {
logDir = new File(PathUtils.getExternalStoragePath(), "boot_log");
} else {
logDir = new File(LogUtils.getConfig().getDir());
}
LogUtils.d(TAG, "待上传日志文件类型: " + logType);
return logDir;
}
private File tempDirInit() {
File dir = new File(PathUtils.getExternalStoragePath() + File.separator + UPLOAD_DIR);
if (dir.exists()) {
// 清空临时文件夹
FileUtils.delete(dir);
}
boolean result = dir.mkdirs();
LogUtils.d(TAG, "临时目录创建" + (result ? "成功" : "失败"));
if (result) {
return dir;
} else {
return null;
}
}
private boolean copyTargetFiles(File src, File descDir) {
FileFilter filter = file -> {
Date date = new Date(file.lastModified());
return date.after(startTime) && date.before(endTime);
};
List<File> logFiles = FileUtils.listFilesInDirWithFilter(src, filter, false, null);
boolean copyResult = true;
wait("筛选目标日志文件", 10);
int count = 0;
File descFile;
boolean tempCopyResult;
for (File file : logFiles) {
descFile = new File(descDir, file.getName());
tempCopyResult = FileUtils.copy(file, descFile);
copyResult = copyResult && tempCopyResult;
if (tempCopyResult) count++;
}
LogUtils.d(TAG, "待上传日志文件总数: " + logFiles.size() + ", 复制成功文件总数: " + count);
return !logFiles.isEmpty() && copyResult;
}
private void upload(File zip) {
OkHttpClient client = SCRetrofit.createOkHttpClientBuilder().build();
SCApi api = SCRetrofit.createApi(client);
CommandLog.CommandLogData data = commandLog.getData();
String fileNameForServer = data.getLogType()
+ nameFormat.format(startTime)
+ nameFormat.format(endTime)
+ deviceSN + ".zip";
fileNameForServer = fileNameForServer.replace(" ", "_");
LogUtils.d(TAG, "开始上传日志: " + fileNameForServer);
RequestBody requestBody = RequestBody.create(zip, MediaType.parse("application/x-zip-compressed"));
MultipartBody.Part body = MultipartBody.Part.createFormData("file", fileNameForServer, requestBody);
try {
Response<String> response = api.upload(body).execute();
LogUtils.d(TAG, "日志文件上传成功: " + response);
} catch (IOException e) {
LogUtils.e(TAG, "日志文件上传失败: " + e.getMessage());
} finally {
FileUtils.delete(uploadDir);
FileUtils.delete(zip);
}
}
@Override
public synchronized CommandResponse run() throws InterruptedException {
if (start) {
LogUtils.w(TAG, "日志上传任务已启动");
return null;
}
start = true;
if (!checkLogCommand()) {
start = false;
return failedResult("日志上传指令不符合规范");
}
File logFile = getZipLogs();
if (logFile != null) {
wait("上传压缩文件至服务器", 60);
upload(logFile);
start = false;
return successResult("");
}
start = false;
return failedResult("");
}
}
package com.bgycc.smartcanteen.command;
import com.bgycc.smartcanteen.BuildConfig;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.CommandResponse;
import com.bgycc.smartcanteen.entity.CommandUpdate;
import com.bgycc.smartcanteen.utils.DeviceProxy;
import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import com.google.gson.Gson;
import com.liulishuo.filedownloader.BaseDownloadTask;
import com.liulishuo.filedownloader.FileDownloadListener;
import com.liulishuo.filedownloader.FileDownloadSampleListener;
import com.liulishuo.filedownloader.FileDownloader;
import java.io.File;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
/**
* 设备指令: 更新 <br/>
* 更新任务处理为单例,避免多个更新任务同时进行浪费资源
*/
public class UpdateCommandHandler extends CommandHandler {
private static final String UPDATE_APK = "SmartCanteen-update.apk";
private static final String UPDATE_APK_PATH = PathUtils.getExternalStoragePath() + File.separator + UPDATE_APK;
private static final long DEFAULT_DELAY = 5 * 1000;
// 保证更新任务不会在同一时间内重复执行
private volatile boolean start = false;
private CommandUpdate commandUpdate;
private static UpdateCommandHandler instance;
public static synchronized UpdateCommandHandler getInstance(Command command, Gson gson, CommandProgressCallback callback) {
if (instance == null) {
instance = new UpdateCommandHandler(command, gson, callback);
} else {
if (!instance.start) {
instance.init(command, gson, callback);
}
}
return instance;
}
private UpdateCommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
super(command, gson, callback);
this.commandUpdate = gson.fromJson(command.getData(), CommandUpdate.class);
}
private void init(Command command, Gson gson, CommandProgressCallback callback) {
this.command = command;
this.gson = gson;
this.commandProgressCallback = callback;
this.commandUpdate = gson.fromJson(command.getData(), CommandUpdate.class);
}
@Override
public synchronized CommandResponse run() {
if (start) {
LogUtils.w(TAG, "更新任务已启动");
return null;
}
start = true;
if (commandUpdate.getData() == null || commandUpdate.getData().getUrl() == null) {
LogUtils.d(TAG, "更新包地址异常: " + commandUpdate.toString());
return failedResult("更新包地址异常");
}
String url = commandUpdate.getData().getUrl();
FileDownloader.setup(Utils.getApp());
FileDownloader.getImpl()
.create(url)
.setPath(UPDATE_APK_PATH)
.setAutoRetryTimes(3)
.setSyncCallback(true)
.setForceReDownload(true)
.setListener(listener)
.start();
return successResult("开始下载");
}
private FileDownloadListener listener = new FileDownloadSampleListener() {
@Override
protected void started(BaseDownloadTask task) {
LogUtils.d(TAG, "更新包开始下载: " + task.getUrl());
UpdateCommandHandler.this.wait("开始下载", 0);
}
@Override
protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
int per = (int) (soFarBytes * 1f / totalBytes * 100);
UpdateCommandHandler.this.wait("下载进度: " + per + "%", per);
}
@Override
protected void error(BaseDownloadTask task, Throwable e) {
LogUtils.e(TAG, "下载失败: " + e.getMessage(), e);
failed("下载失败", 0);
try {
Thread.sleep(DEFAULT_DELAY);
} catch (Exception ignored) {
} finally {
idle("", 0);
start = false;
}
}
@Override
protected void completed(BaseDownloadTask task) {
File updateApk = new File(UPDATE_APK_PATH);
AppUtils.AppInfo info = AppUtils.getApkInfo(updateApk);
LogUtils.d(TAG, "更新包下载成功,开始安装: " + (info == null ? "null" : info.getPackageName()));
if (info == null || !info.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
FileUtils.delete(updateApk);
idle("", 0);
start = false;
return;
}
if (info.getVersionCode() == BuildConfig.VERSION_CODE) {
FileUtils.delete(updateApk);
LogUtils.w(TAG, "当前版本: " + BuildConfig.VERSION_CODE + ", 安装包版本: " + info.getVersionCode());
} else if (info.getVersionCode() < BuildConfig.VERSION_CODE) {
FileUtils.delete(updateApk);
LogUtils.d(TAG, "不允许安装低版本");
failed("不允许安装低版本", 0);
} else {
if (DeviceProxy.updateApp(updateApk)) {
success("开始安装", 100);
} else {
failed("安装文件权限修改失败", 0);
}
}
try {
Thread.sleep(DEFAULT_DELAY);
} catch (Exception ignored) {
} finally {
idle("", 0);
start = false;
}
}
};
}
package com.bgycc.smartcanteen.command;
import android.net.wifi.WifiInfo;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.CommandResponse;
import com.bgycc.smartcanteen.entity.CommandWifiConfig;
import com.bgycc.smartcanteen.utils.NetworkUtils;
import com.blankj.utilcode.util.LogUtils;
import com.google.gson.Gson;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
public class WifiConfigCommandHandler extends CommandHandler {
private static final long DEFAULT_DELAY = 3000;
private static final long POLLING_DELAY = 100;
private CommandWifiConfig wifiConfig;
private volatile boolean start = false;
private static WifiConfigCommandHandler instance;
public static synchronized WifiConfigCommandHandler getInstance(Command command, Gson gson, CommandProgressCallback callback) {
if (instance == null) {
instance = new WifiConfigCommandHandler(command, gson, callback);
} else {
if (!instance.start) {
instance.init(command, gson, callback);
}
}
return instance;
}
private WifiConfigCommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
super(command, gson, callback);
this.wifiConfig = gson.fromJson(command.getData(), CommandWifiConfig.class);
}
private void init(Command command, Gson gson, CommandProgressCallback callback) {
this.command = command;
this.gson = gson;
this.commandProgressCallback = callback;
this.wifiConfig = gson.fromJson(command.getData(), CommandWifiConfig.class);
}
@Override
public synchronized CommandResponse run() throws InterruptedException {
if (start) {
LogUtils.w(TAG, "Wifi配置任务已启动");
return null;
}
start = true;
CommandWifiConfig.CommandWifiConfigData data = wifiConfig.getData();
if (data == null) {
start = false;
return null;
}
if (!NetworkUtils.isWifiEnabled()) {
wait("正在启动Wifi", 5);
if (!NetworkUtils.setEnable(true)) {
String failedMessage = "无法启动Wifi";
wait(failedMessage, 5);
LogUtils.e(TAG, failedMessage);
Thread.sleep(DEFAULT_DELAY);
start = false;
return failedResult(failedMessage);
}
}
String ssid = data.getSsid();
String identity = data.getIdentity();
String pwd = data.getPwd();
String type = data.getType();
wait("正在配置Wifi", 10);
LogUtils.d(TAG, "开始配置wifi, ssid: " + ssid + ", identity: " + identity + ", pwd: " + pwd + ", type: " + type);
try {
NetworkUtils.connect(ssid, identity, pwd, type);
// 轮训检查wifi是否链接成功
for (int i = 0; i < 30; i++) {
Thread.sleep(POLLING_DELAY);
WifiInfo info = NetworkUtils.getWifiInfo();
if (info == null || info.getIpAddress() == 0) {
continue;
}
String message = "Wifi配置成功";
wait(message, 50);
LogUtils.d(TAG, message);
Thread.sleep(DEFAULT_DELAY);
return successResult(message);
}
} catch (Exception e) {
LogUtils.e(TAG, "链接wifi过程出错: " + e.getMessage(), e);
Thread.sleep(DEFAULT_DELAY);
return failedResult(e.getMessage());
} finally {
start = false;
}
return failedResult("无法连接Wifi");
}
}
package com.bgycc.smartcanteen.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import com.bgycc.smartcanteen.data.dao.CommandDao;
import com.bgycc.smartcanteen.data.dao.PayDataDao;
import com.bgycc.smartcanteen.data.dao.PayResponseDao;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.PayData;
import com.bgycc.smartcanteen.entity.PayResponse;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.Utils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@Database(
entities = {
Command.class,
PayData.class,
PayResponse.class
},
version = 1,
exportSchema = false)
public abstract class DatabaseManager extends RoomDatabase {
private static final String TAG = "SmartCanteen_Database";
private static final int UPDATE_MAX_TIMES = 5;
@NonNull
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
return null;
}
@NonNull
@Override
protected InvalidationTracker createInvalidationTracker() {
return null;
}
@Override
public void clearAllTables() {
}
private static final String DB_NAME = "smart_canteen.db";
private static volatile DatabaseManager instance;
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
instance = create(Utils.getApp().getApplicationContext());
}
return instance;
}
private static DatabaseManager create(final Context context) {
return Room.databaseBuilder(
context.getApplicationContext(),
DatabaseManager.class,
DB_NAME)
.addCallback(new Callback() {
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
// 数据库启动时,将标记为"在线支付"的订单统一改为"离线支付"
// 避免某个在线支付订单在支付过程中App因特殊状况强制退出导致漏支付
Cursor cursor = db.query("select * from " + PayData.TABLE_NAME + " where payState == 0");
if (cursor == null || cursor.getCount() <= 0) return;
cursor.moveToFirst();
List<PayData> list = selectPayOnlineData(cursor);
forceToPayOffline(db, list);
}
})
// 一旦版本不兼容则清空数据库
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build();
}
@NotNull
private static List<PayData> selectPayOnlineData(Cursor cursor) {
List<PayData> list = new ArrayList<>();
do {
PayData payData = new PayData();
payData.setEquipmentNo(cursor.getString(cursor.getColumnIndex("equipmentNo")));
payData.setPayCode(cursor.getString(cursor.getColumnIndex("payCode")));
payData.setTerminalType(cursor.getString(cursor.getColumnIndex("terminalType")));
payData.setTime(cursor.getLong(cursor.getColumnIndex("time")));
payData.setPayState(cursor.getInt(cursor.getColumnIndex("payState")));
list.add(payData);
} while (cursor.moveToNext());
return list;
}
private static void forceToPayOffline(@NonNull SupportSQLiteDatabase db, List<PayData> list) {
StringBuilder ids = new StringBuilder();
db.beginTransaction();
for (PayData payData : list) {
payData.payOffline();
ContentValues values = new ContentValues();
values.put("payState", -1);
int r;
int count = 0;
do {
r = db.update(PayData.TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values,
"payCode=?", new String[]{payData.getPayCode()});
count++;
if (count == UPDATE_MAX_TIMES) {
LogUtils.file(TAG, "订单: " + payData.toString() + "在线支付异常");
}
} while (r <= 0 && count < UPDATE_MAX_TIMES);
ids.append(payData.getPayCode())
.append(",");
}
db.setTransactionSuccessful();
db.endTransaction();
if (ids.length() > 0) {
LogUtils.d(TAG, "订单号: " + ids.substring(0, ids.length() - 1) + " 未能接收后台支付结果");
}
}
public abstract CommandDao getCommandDao();
public abstract PayDataDao getPayDataDao();
public abstract PayResponseDao getPayResponseDao();
}
package com.bgycc.smartcanteen.data.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.bgycc.smartcanteen.entity.Command;
import java.util.List;
@Dao
public interface CommandDao {
// 搜索所有未完成的指令(0为false)
@Query("select * from command where finish == 0 order by id asc")
LiveData<List<Command>> queryUndoneCommand();
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertCommand(Command command);
@Update(onConflict = OnConflictStrategy.REPLACE)
int updateCommand(Command command);
}
package com.bgycc.smartcanteen.data.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.bgycc.smartcanteen.entity.PayData;
import java.util.List;
@Dao
public interface PayDataDao {
/**
* 获取所有需要离线支付的订单
*/
@Query("select * from paydata where payState == -1")
List<PayData> queryPayOfflineData();
/**
* 根据支付码获取指定订单
*/
@Query("select * from paydata where payCode == :payCode")
PayData queryPayDataByPayCode(String payCode);
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertPayData(PayData data);
@Update(onConflict = OnConflictStrategy.REPLACE)
int updatePayData(PayData data);
@Update(onConflict = OnConflictStrategy.REPLACE)
int updatePayData(List<PayData> data);
}
package com.bgycc.smartcanteen.data.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import com.bgycc.smartcanteen.entity.PayResponse;
@Dao
public interface PayResponseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertPayResponse(PayResponse response);
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class CheckDevice {
private String action;
private CheckDeviceData data;
private long serialNumber;
public CheckDevice(String action, String deviceSN) {
this.action = action;
this.data = new CheckDeviceData(deviceSN);
this.serialNumber = System.currentTimeMillis();
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public CheckDeviceData getData() {
return data;
}
public void setData(CheckDeviceData data) {
this.data = data;
}
public long getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(long serialNumber) {
this.serialNumber = serialNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CheckDevice that = (CheckDevice) o;
return serialNumber == that.serialNumber &&
Objects.equals(action, that.action) &&
Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(action, data, serialNumber);
}
@Override
public String toString() {
return "CheckDevice{" +
"action='" + action + '\'' +
", data=" + data +
", serialNumber=" + serialNumber +
'}';
}
private static class CheckDeviceData {
private String equipmentId;
private CheckDeviceData(String deviceSN) {
this.equipmentId = deviceSN;
}
public String getEquipmentId() {
return equipmentId;
}
public void setEquipmentId(String equipmentId) {
this.equipmentId = equipmentId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CheckDeviceData that = (CheckDeviceData) o;
return Objects.equals(equipmentId, that.equipmentId);
}
@Override
public int hashCode() {
return Objects.hash(equipmentId);
}
@Override
public String toString() {
return "CheckDeviceData{" +
"equipmentId='" + equipmentId + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity;
import androidx.annotation.StringDef;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.util.Objects;
@Entity(tableName = Command.TABLE_NAME)
public class Command {
public static final String TABLE_NAME = "command";
public static final String LOG_UPLOAD = "LOG_PULL";
public static final String APP_UPDATE = "CONFIG_UPDATE";
public static final String CONFIG_WIFI = "CONFIG_WIFI";
public static final String CONFIG_LOG = "CONFIG_LOG";
@StringDef(value = {LOG_UPLOAD, APP_UPDATE, CONFIG_WIFI, CONFIG_LOG})
public @interface COMMAND_ACTION {
}
@PrimaryKey(autoGenerate = true)
private long id;
private String data;
private String action;
private boolean finish;
public Command() {
}
public Command(String command, @COMMAND_ACTION String action) {
this.data = command;
this.action = action;
this.finish = false;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public boolean isFinish() {
return finish;
}
public void setFinish(boolean finish) {
this.finish = finish;
}
public void finish() {
this.finish = true;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Command command = (Command) o;
return id == command.id &&
finish == command.finish &&
Objects.equals(data, command.data) &&
Objects.equals(action, command.action);
}
@Override
public int hashCode() {
return Objects.hash(id, data, action, finish);
}
@Override
public String toString() {
return "Command{" +
"id=" + id +
", data='" + data + '\'' +
", action='" + action + '\'' +
", finish=" + finish +
'}';
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class CommandLog {
private String action;
private CommandLogData data;
private String equipmentId;
public CommandLog() {
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public CommandLogData getData() {
return data;
}
public void setData(CommandLogData data) {
this.data = data;
}
public String getEquipmentId() {
return equipmentId;
}
public void setEquipmentId(String equipmentId) {
this.equipmentId = equipmentId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandLog that = (CommandLog) o;
return Objects.equals(action, that.action) &&
Objects.equals(data, that.data) &&
Objects.equals(equipmentId, that.equipmentId);
}
@Override
public int hashCode() {
return Objects.hash(action, data, equipmentId);
}
@Override
public String toString() {
return "CommandLog{" +
"action='" + action + '\'' +
", data=" + data +
", equipmentId='" + equipmentId + '\'' +
'}';
}
public static class CommandLogData {
private String logType;
private String startTime;
private String endTime;
public String getLogType() {
return logType;
}
public void setLogType(String logType) {
this.logType = logType;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandLogData that = (CommandLogData) o;
return Objects.equals(logType, that.logType) &&
Objects.equals(startTime, that.startTime) &&
Objects.equals(endTime, that.endTime);
}
@Override
public int hashCode() {
return Objects.hash(logType, startTime, endTime);
}
@Override
public String toString() {
return "CommandLogData{" +
"logType='" + logType + '\'' +
", startTime='" + startTime + '\'' +
", endTime='" + endTime + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class CommandResponse {
private boolean success;
private String message;
private CommandResponse(boolean success, String message) {
this.success = success;
this.message = message;
}
public static CommandResponse failed(String reason) {
return new CommandResponse(false, reason);
}
public static CommandResponse success(String reason) {
return new CommandResponse(true, reason);
}
public boolean success() {
return success;
}
public String getMessage() {
return message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandResponse that = (CommandResponse) o;
return success == that.success &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(success, message);
}
@Override
public String toString() {
return "CommandResponse{" +
"success=" + success +
", message='" + message + '\'' +
'}';
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class CommandUpdate {
private String action;
private CommandUpdateData data;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public CommandUpdateData getData() {
return data;
}
public void setData(CommandUpdateData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandUpdate that = (CommandUpdate) o;
return Objects.equals(action, that.action) &&
Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(action, data);
}
@Override
public String toString() {
return "CommandUpdate{" +
"action='" + action + '\'' +
", data=" + data +
'}';
}
public static class CommandUpdateData {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandUpdateData that = (CommandUpdateData) o;
return Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(url);
}
@Override
public String toString() {
return "CommandUpdateData{" +
"url='" + url + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class CommandWifiConfig {
private String action;
private CommandWifiConfigData data;
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public CommandWifiConfigData getData() {
return data;
}
public void setData(CommandWifiConfigData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandWifiConfig that = (CommandWifiConfig) o;
return Objects.equals(action, that.action) &&
Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(action, data);
}
@Override
public String toString() {
return "CommandWifiConfig{" +
"action='" + action + '\'' +
", data=" + data +
'}';
}
public static class CommandWifiConfigData {
private static final String DEFAULT_TYPE = "wpa";
private String ssid;
private String pwd;
private String type = DEFAULT_TYPE;
private String identity;
public String getSsid() {
return ssid;
}
public void setSsid(String ssid) {
this.ssid = ssid;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getIdentity() {
return identity;
}
public void setIdentity(String identity) {
this.identity = identity;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandWifiConfigData that = (CommandWifiConfigData) o;
return Objects.equals(ssid, that.ssid) &&
Objects.equals(pwd, that.pwd) &&
Objects.equals(type, that.type) &&
Objects.equals(identity, that.identity);
}
@Override
public int hashCode() {
return Objects.hash(ssid, pwd, type, identity);
}
@Override
public String toString() {
return "CommandWifiConfigData{" +
"ssid='" + ssid + '\'' +
", pwd='" + pwd + '\'' +
", type='" + type + '\'' +
", identity='" + identity + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class Heartbeat {
private String equipmentId;
private long serialNumber;
private String action = "AUTO_CHECK_DEVICE";
private SocketDeviceInfo data;
public Heartbeat(String equipmentId) {
this.equipmentId = equipmentId;
this.serialNumber = System.currentTimeMillis();
SocketDeviceInfo deviceInfo = new SocketDeviceInfo();
deviceInfo.setEquipmentId(equipmentId);
this.data = deviceInfo;
}
public String getEquipmentId() {
return equipmentId;
}
public long getSerialNumber() {
return serialNumber;
}
public String getAction() {
return action;
}
public SocketDeviceInfo getData() {
return data;
}
public void setData(SocketDeviceInfo data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Heartbeat heartbeat = (Heartbeat) o;
return serialNumber == heartbeat.serialNumber &&
Objects.equals(equipmentId, heartbeat.equipmentId) &&
Objects.equals(action, heartbeat.action) &&
Objects.equals(data, heartbeat.data);
}
@Override
public int hashCode() {
return Objects.hash(equipmentId, serialNumber, action, data);
}
@Override
public String toString() {
return "Heartbeat{" +
"equipmentId='" + equipmentId + '\'' +
", serialNumber=" + serialNumber +
", action='" + action + '\'' +
", data=" + data +
'}';
}
public static class SocketDeviceInfo {
private String equipmentId;
public String getEquipmentId() {
return equipmentId;
}
public void setEquipmentId(String equipmentId) {
this.equipmentId = equipmentId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SocketDeviceInfo that = (SocketDeviceInfo) o;
return Objects.equals(equipmentId, that.equipmentId);
}
@Override
public int hashCode() {
return Objects.hash(equipmentId);
}
@Override
public String toString() {
return "SocketDeviceInfo{" +
"equipmentId='" + equipmentId + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity;
import java.util.Objects;
public class PayAck {
private long serialNumber;
private String code;
private String message;
public PayAck(long serialNumber) {
this.serialNumber = serialNumber;
this.code = "0";
this.message = "已接收";
}
public long getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(long serialNumber) {
this.serialNumber = serialNumber;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PayAck ack = (PayAck) o;
return serialNumber == ack.serialNumber &&
Objects.equals(code, ack.code) &&
Objects.equals(message, ack.message);
}
@Override
public int hashCode() {
return Objects.hash(serialNumber, code, message);
}
@Override
public String toString() {
return "PayAck{" +
"serialNumber=" + serialNumber +
", code='" + code + '\'' +
", message='" + message + '\'' +
'}';
}
}
package com.bgycc.smartcanteen.entity;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
@Entity(tableName = PayData.TABLE_NAME)
public class PayData {
public static final String TABLE_NAME = "paydata";
public static final String TYPE_COMMAND = "COMMAND";
public static final String TYPE_ALIPAY = "ALIPAY";
public static final String TYPE_WXPAY = "WXPAY";
public static final String TYPE_BHPAY = "BHPAY";
private static final int PAY_SUCCESS = 1;
private static final int PAY_ONLINE = 0;
private static final int PAY_OFFLINE = -1;
private static final int PAY_FAILED = -2;
private String equipmentNo;
@NonNull
@PrimaryKey
private String payCode = "";
private String terminalType;
private long time;
/**
* 1 -> 支付成功(在线支付/离线支付成功后标记)
* 0 -> 在线支付(任务创建时默认标记)
* -1 -> 离线支付(在线支付超时/离线支付超时/创建订单Socket未链接 标记)
* -2 -> 支付失败(在线支付失败后标记)
*/
private int payState;
public PayData() {
}
public PayData(String deviceSN, @NonNull String payCode, String terminalType) {
this.equipmentNo = deviceSN;
this.payCode = payCode;
this.terminalType = terminalType;
this.time = System.currentTimeMillis() / 1000;
this.payState = PAY_ONLINE;
}
public boolean success() {
return payState == PAY_SUCCESS;
}
public void paySuccess() {
payState = PAY_SUCCESS;
}
public void payOffline() {
payState = PAY_OFFLINE;
}
public void payFailed() {
payState = PAY_FAILED;
}
public static String matchTerminalType(String code) {
if (Pattern.matches("^(10|11|12|13|14|15)\\d{16}$", code)) return TYPE_WXPAY;
if (Pattern.matches("^(25|26|27|28|29|30)\\d{14,22}$", code)) return TYPE_ALIPAY;
if (Pattern.matches("^(38|39)\\d{16}$", code)) return TYPE_BHPAY;
return TYPE_COMMAND;
}
public String getEquipmentNo() {
return equipmentNo;
}
public void setEquipmentNo(String equipmentNo) {
this.equipmentNo = equipmentNo;
}
@NotNull
public String getPayCode() {
return payCode;
}
public void setPayCode(@NotNull String payCode) {
this.payCode = payCode;
}
public String getTerminalType() {
return terminalType;
}
public void setTerminalType(String terminalType) {
this.terminalType = terminalType;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public int getPayState() {
return payState;
}
public void setPayState(int payState) {
this.payState = payState;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PayData data = (PayData) o;
return time == data.time &&
payState == data.payState &&
Objects.equals(equipmentNo, data.equipmentNo) &&
Objects.equals(payCode, data.payCode) &&
Objects.equals(terminalType, data.terminalType);
}
@Override
public int hashCode() {
return Objects.hash(equipmentNo, payCode, terminalType, time, payState);
}
@Override
public String toString() {
return "PayData{" +
"equipmentNo='" + equipmentNo + '\'' +
", payCode='" + payCode + '\'' +
", terminalType='" + terminalType + '\'' +
", time=" + time +
", payState=" + payState +
'}';
}
}
package com.bgycc.smartcanteen.entity;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class PayRequest {
private static final String PAY_ONLINE = "PAY_ONLINE";
private static final String PAY_OFFLINE = "PAY_OFFLINE";
private long serialNumber;
private String equipmentId;
private String action;
private List<PayData> data;
public PayRequest() {
}
public PayRequest(String deviceSN, PayData data) {
this.serialNumber = System.currentTimeMillis();
this.equipmentId = deviceSN;
this.action = PAY_ONLINE;
this.data = new ArrayList<>();
this.data.add(data);
}
public PayRequest(String deviceSN, List<PayData> data) {
this.serialNumber = System.currentTimeMillis();
this.equipmentId = deviceSN;
this.action = PAY_OFFLINE;
this.data = data;
}
public boolean isOnlinePay() {
return action.equals(PAY_ONLINE);
}
public long getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(long serialNumber) {
this.serialNumber = serialNumber;
}
public String getEquipmentId() {
return equipmentId;
}
public void setEquipmentId(String equipmentId) {
this.equipmentId = equipmentId;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public List<PayData> getData() {
return data;
}
public void addData(PayData payData) {
if (data == null) {
data = new ArrayList<>();
}
data.add(payData);
}
public void setData(List<PayData> data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PayRequest request = (PayRequest) o;
return serialNumber == request.serialNumber &&
Objects.equals(equipmentId, request.equipmentId) &&
Objects.equals(action, request.action) &&
Objects.equals(data, request.data);
}
@Override
public int hashCode() {
return Objects.hash(serialNumber, equipmentId, action, data);
}
@Override
public String toString() {
return "PayRequest{" +
"serialNumber=" + serialNumber +
", equipmentId='" + equipmentId + '\'' +
", action='" + action + '\'' +
", data=" + data +
'}';
}
}
package com.bgycc.smartcanteen.entity;
import androidx.room.Embedded;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.util.Objects;
/**
* 支付订单的结果 <br/>
* 通过{@link #paySuccess()}和{@link #payFailed()}方法来判断支付成功与否 <br/>
* 通过{@link #getPayCode()}方法获取订单号 <br/>
*/
@Entity(tableName = PayResponse.TABLE_NAME)
public class PayResponse {
public static final String TABLE_NAME = "payresponse";
private static final String PAY_RESULT_SUCCESS = "0";
private static final String PAY_RESULT_FAILED = "-1";
@PrimaryKey(autoGenerate = true)
private long id;
private String action;
// 当没有code值时,默认为支付失败
private String code = PAY_RESULT_FAILED;
@Embedded
private PayResponseData data;
private String equipmentId;
private String message;
private long serialNumber;
public String getPayCode() {
if (data == null || data.payCode == null) {
return "";
}
return data.payCode;
}
public boolean matchDevice(String deviceSN) {
return equipmentId.equals(deviceSN);
}
public boolean paySuccess() {
return code.equals(PAY_RESULT_SUCCESS);
}
public boolean payFailed() {
return code.equals(PAY_RESULT_FAILED);
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public PayResponseData getData() {
return data;
}
public void setData(PayResponseData data) {
this.data = data;
}
public String getEquipmentId() {
return equipmentId;
}
public void setEquipmentId(String equipmentId) {
this.equipmentId = equipmentId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public long getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(long serialNumber) {
this.serialNumber = serialNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PayResponse that = (PayResponse) o;
return id == that.id &&
serialNumber == that.serialNumber &&
Objects.equals(action, that.action) &&
Objects.equals(code, that.code) &&
Objects.equals(data, that.data) &&
Objects.equals(equipmentId, that.equipmentId) &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(id, action, code, data, equipmentId, message, serialNumber);
}
@Override
public String toString() {
return "PayResponse{" +
"id=" + id +
", action='" + action + '\'' +
", code='" + code + '\'' +
", data=" + data +
", equipmentId='" + equipmentId + '\'' +
", message='" + message + '\'' +
", serialNumber=" + serialNumber +
'}';
}
public static class PayResponseData {
private String payCode;
public String getPayCode() {
return payCode;
}
public void setPayCode(String payCode) {
this.payCode = payCode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PayResponseData that = (PayResponseData) o;
return Objects.equals(payCode, that.payCode);
}
@Override
public int hashCode() {
return Objects.hash(payCode);
}
@Override
public String toString() {
return "PayResponseData{" +
"payCode='" + payCode + '\'' +
'}';
}
}
}
package com.bgycc.smartcanteen.entity.typeadapter;
import com.bgycc.smartcanteen.entity.PayData;
import com.bgycc.smartcanteen.entity.PayRequest;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PayRequestTypeAdapter extends TypeAdapter<PayRequest> {
@Override
public void write(JsonWriter out, PayRequest value) throws IOException {
out.beginObject();
out.name("serialNumber");
out.value(value.getSerialNumber());
out.name("equipmentId");
out.value(value.getEquipmentId());
out.name("action");
out.value(value.getAction());
out.name("data");
if (value.isOnlinePay()) {
PayData data = value.getData().get(0);
writePayData(out, data);
} else {
out.beginArray();
for (PayData data : value.getData()) {
writePayData(out, data);
}
out.endArray();
}
out.endObject();
}
private void writePayData(JsonWriter out, PayData data) throws IOException {
out.beginObject();
out.name("equipmentNo");
out.value(data.getEquipmentNo());
out.name("payCode");
out.value(data.getPayCode());
out.name("terminalType");
out.value(data.getTerminalType());
out.name("time");
out.value(data.getTime());
out.endObject();
}
@Override
public PayRequest read(JsonReader in) {
PayRequest payRequest = new PayRequest();
try {
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "serialNumber":
payRequest.setSerialNumber(in.nextLong());
break;
case "equipmentId":
payRequest.setEquipmentId(in.nextString());
break;
case "action":
payRequest.setAction(in.nextString());
break;
case "data":
if (payRequest.isOnlinePay()) {
PayData data = readPayData(in);
payRequest.addData(data);
} else {
List<PayData> list = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
list.add(readPayData(in));
}
in.endArray();
payRequest.setData(list);
}
break;
}
}
in.endObject();
} catch (IOException e) {
return null;
}
return payRequest;
}
private PayData readPayData(JsonReader in) throws IOException {
PayData data = new PayData();
data.paySuccess();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "equipmentNo":
data.setEquipmentNo(in.nextString());
break;
case "payCode":
data.setPayCode(in.nextString());
break;
case "terminalType":
data.setTerminalType(in.nextString());
break;
case "time":
data.setTime(in.nextLong());
break;
}
}
in.endObject();
return data;
}
}
package com.bgycc.smartcanteen.event;
public class LogEvent {
}
package com.bgycc.smartcanteen.event;
public class PayStateEvent {
public enum StateEnum {
IDLE,
WAIT,
SUCCESS,
FAIL
}
public StateEnum state;
public String message;
public PayStateEvent(StateEnum state) {
this.state = state;
}
public PayStateEvent(StateEnum state, String message) {
this(state);
this.message = message;
}
}
package com.bgycc.smartcanteen.event;
public class QRCodeEvent {
public byte[] data;
public String string;
public String payCodeType;
public QRCodeEvent(byte[] data, String string, String payCodeType) {
this.data = data;
this.string = string;
this.payCodeType = payCodeType;
}
public boolean isEmpty() {
return this.string == null || this.string.isEmpty();
}
public boolean equal(QRCodeEvent event) {
if (event == null) return false;
if (isEmpty() != event.isEmpty()) return false;
if (isEmpty()) return true;
return this.string.equals(event.string);
}
}
package com.bgycc.smartcanteen.event
class QRCodeInvalidEvent {
}
\ No newline at end of file
package com.bgycc.smartcanteen.event
class QRCodeRepeatEvent {
}
\ No newline at end of file
package com.bgycc.smartcanteen.event;
public class SettingStateEvent {
public int progress;
public String message;
public SettingStateEvent(int progress) {
this.progress = progress;
this.message = "";
}
public SettingStateEvent(int progress, String message) {
this.progress = progress;
this.message = message;
}
}
package com.bgycc.smartcanteen.executor;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class DefaultTaskExecutor extends TaskExecutor {
private final Object mLock = new Object();
private final ScheduledExecutorService mThread = Executors.newScheduledThreadPool(6, new ThreadFactory() {
private static final String THREAD_NAME_STEM = "mqtt_thread_%d";
private final AtomicInteger mThreadId = new AtomicInteger(0);
@Override
public Thread newThread(@NotNull Runnable r) {
Thread t = new Thread(r);
t.setName(String.format(Locale.CHINA, THREAD_NAME_STEM, mThreadId.getAndIncrement()));
return t;
}
});
@Nullable
private volatile Handler mMainHandler;
@Override
public void executeOnDiskIO(@NotNull Runnable runnable) {
mThread.execute(runnable);
}
@Override
public <T> Future<T> submit(Callable<T> callable) {
return mThread.submit(callable);
}
@Override
public <T> Future<T> submit(Runnable runnable, T result) {
return mThread.submit(runnable, result);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
return mThread.schedule(command, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
return mThread.scheduleAtFixedRate(command, initialDelay, period, unit);
}
@Override
public void postToMainThread(@NotNull Runnable runnable) {
if (mMainHandler == null) {
synchronized (mLock) {
if (mMainHandler == null) {
mMainHandler = new Handler(Looper.getMainLooper());
}
}
}
mMainHandler.post(runnable);
}
@Override
public boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
@Override
public void quit() {
mThread.shutdownNow();
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.executor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unused")
public class SCTaskExecutor extends TaskExecutor {
private static volatile SCTaskExecutor sInstance;
@NonNull
private TaskExecutor mDelegate;
@NonNull
private TaskExecutor mDefaultTaskExecutor;
@NonNull
private static final Executor sMainThreadExecutor = command -> getInstance().postToMainThread(command);
@NonNull
private static final Executor sIOThreadExecutor = command -> getInstance().executeOnDiskIO(command);
private SCTaskExecutor() {
mDefaultTaskExecutor = new DefaultTaskExecutor();
mDelegate = mDefaultTaskExecutor;
}
@NonNull
public static SCTaskExecutor getInstance() {
if (sInstance != null) {
return sInstance;
}
synchronized (SCTaskExecutor.class) {
if (sInstance == null) {
sInstance = new SCTaskExecutor();
}
}
return sInstance;
}
/**
* Sets a delegate to parse task execution requests.
* <p>
* If you have a common executor, you can set it as the delegate and App Toolkit components will
* use your executors. You may also want to use this for your tests.
* <p>
* Calling this method with {@code null} sets it to the default TaskExecutor.
*
* @param taskExecutor The task executor to parse task requests.
*/
public void setDelegate(@Nullable TaskExecutor taskExecutor) {
mDelegate = taskExecutor == null ? mDefaultTaskExecutor : taskExecutor;
}
@Override
public void executeOnDiskIO(@NotNull Runnable runnable) {
mDelegate.executeOnDiskIO(runnable);
}
@Override
public void postToMainThread(@NotNull Runnable runnable) {
mDelegate.postToMainThread(runnable);
}
@Override
public <T> Future<T> submit(Callable<T> callable) {
return mDelegate.submit(callable);
}
@Override
public <T> Future<T> submit(Runnable runnable, T result) {
return mDelegate.submit(runnable, result);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
return mDelegate.schedule(command, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
return mDelegate.scheduleAtFixedRate(command, initialDelay, period, unit);
}
@NonNull
public static Executor getMainThreadExecutor() {
return sMainThreadExecutor;
}
@NonNull
public static Executor getIOThreadExecutor() {
return sIOThreadExecutor;
}
@Override
public boolean isMainThread() {
return mDelegate.isMainThread();
}
@Override
public void quit() {
mDelegate.quit();
sInstance = null;
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.executor;
import androidx.annotation.NonNull;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public abstract class TaskExecutor {
public abstract void executeOnDiskIO(@NonNull Runnable runnable);
public abstract <T> Future<T> submit(Callable<T> callable);
public abstract <T> Future<T> submit(Runnable runnable, T result);
public abstract ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
public abstract ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
public abstract void postToMainThread(@NonNull Runnable runnable);
public void executeOnMainThread(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
} else {
postToMainThread(runnable);
}
}
public abstract boolean isMainThread();
public abstract void quit();
}
package com.bgycc.smartcanteen.helper;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
public class EthernetHelper {
public static String getMacAddress() {
try {
NetworkInterface networkInterface = NetworkInterface.getByName("eth0");
byte[] data = networkInterface.getHardwareAddress();
return String.format("%02x:%02x:%02x:%02x:%02x:%02x", data[0], data[1], data[2], data[3], data[4], data[5]);
} catch (Exception e) {
}
return "02:00:00:00:00:00";
}
public static String getIpString() {
try {
NetworkInterface networkInterface = NetworkInterface.getByName("eth0");
for (Enumeration<InetAddress> enumIpAddr = networkInterface.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
String address = inetAddress.getHostAddress();
if(!address.contains("::")) return address;
}
}
} catch (Exception ex) {}
return "0.0.0.0";
}
}
package com.bgycc.smartcanteen.helper;
import okhttp3.*;
import org.json.JSONObject;
import java.util.concurrent.TimeUnit;
public class OkHttpHelper {
private static OkHttpHelper sDefault;
public static OkHttpHelper getDefault() {
if (sDefault == null) {
OkHttpClient http = new OkHttpClient.Builder()
.connectTimeout(6, TimeUnit.SECONDS)
.readTimeout(6, TimeUnit.SECONDS)
.writeTimeout(6, TimeUnit.SECONDS)
.build();
sDefault = new OkHttpHelper(http);
}
return sDefault;
}
private OkHttpClient mHttp;
private OkHttpHelper(OkHttpClient http) {
mHttp = http;
}
public void post(String url, JSONObject params, okhttp3.Callback callback) {
MediaType mediaType = MediaType.parse("application/json;charset:utf-8");
RequestBody body = RequestBody.create(mediaType, params.toString());
Request request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json;charset:utf-8")
.post(body)
.build();
mHttp.newCall(request).enqueue(callback);
}
}
package com.bgycc.smartcanteen.helper
import android.content.Context
import android.content.res.AssetManager
import android.media.AudioManager
import android.media.SoundPool
import android.speech.tts.TextToSpeech
import com.bgycc.smartcanteen.util.LogUtil
import com.blankj.utilcode.util.LogUtils
import java.util.*
import kotlin.collections.HashMap
object TTSHelper {
private val TAG = TTSHelper::class.java.simpleName
private var mAssetManager: AssetManager? = null
private var mSupportTTS = false
private var mTTS: TextToSpeech? = null
private val mSoundPool: SoundPool = SoundPool(1, AudioManager.STREAM_SYSTEM, 0)
private val mSoundIdMap = HashMap<String, Int>()
fun initialize(context: Context) {
mAssetManager = context.assets
mSoundPool.setOnLoadCompleteListener { soundPool, sampleId, status ->
if (status == 0) {
soundPool.play(sampleId, 1f, 1f, 100, 0, 1f)
}
}
mTTS = TextToSpeech(context) {
if (it == TextToSpeech.SUCCESS) {
val result = mTTS?.setLanguage(Locale.CHINESE)!!
if (result >= TextToSpeech.LANG_AVAILABLE) {
mSupportTTS = true
}
}
}
}
fun speak(msg: String) {
if (mSupportTTS) {
mTTS?.speak(msg, TextToSpeech.QUEUE_FLUSH, null)
} else {
playSound(msg)
}
}
private fun playSound(msg: String) {
mAssetManager ?: return
val soundName = when (msg) {
"beep" -> "beep.mp3"
"无效二维码" -> "qrcode-invalid.mp3"
"请不要重复扫码" -> "qrcode-repeat.mp3"
"支付成功" -> "pay-success.mp3"
"支付失败" -> "pay-fail.mp3"
"未获取到当前设备信息" -> "pay-device-not-found.mp3"
"设备已禁用" -> "pay-device-disable.mp3"
"未设置食堂参数" -> "pay-canteen-params-unset.mp3"
"当前不支持第三方支付" -> "pay-unsupported-other-platform.mp3"
"超过扣款限制" -> "pay-over-cost-limit.mp3"
"设备未绑定商品" -> "pay-unbind.mp3"
"扣款金额为0" -> "pay-price-zero.mp3"
"未获取到当前用户信息" -> "pay-no-user-info.mp3"
"已就餐,请勿重复支付" -> "pay-once.mp3"
"您的就餐权限已失效" -> "pay-invalid-permission.mp3"
"非本窗口营业时间" -> "pay-window-rest-time.mp3"
"非营业时间" -> "pay-canteen-rest-time.mp3"
"余额不足" -> "pay-balance-not-enough.mp3"
"第三方支付异常" -> "pay-other-platform-offline.mp3"
"支付成功(异常时间)" -> "pay-success-error-time.mp3"
else -> "pay-fail.mp3"
}
var id = mSoundIdMap[soundName]
if (id == null) {
id = mSoundPool.load(mAssetManager!!.openFd(soundName), 1)
mSoundIdMap[soundName] = id
} else {
mSoundPool.play(id, 1f, 1f, 0, 0, 1f)
}
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.helper
import android.util.Log
import android.util.LongSparseArray
import java.lang.Exception
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
object TimerHelper {
private val TAG = TimerHelper::class.java.simpleName
private var mId: Long = 0L
get() = ++field
private val mScheduledExecutorService = Executors.newScheduledThreadPool(5)
private val mFutureList = LongSparseArray<ScheduledFuture<*>>()
fun shutdown() {
mFutureList.clear()
mScheduledExecutorService.shutdown()
}
@Synchronized fun cancel(id: Long) {
try {
val future = mFutureList.get(id) ?: return
mFutureList.remove(id)
future.cancel(true)
} catch (e: Exception) {}
}
fun timeout(runnable: Runnable, time: Long): Long {
return timeout({runnable.run()}, time)
}
fun timeout(runnable: () -> Unit, time: Long): Long {
val id = mId
mFutureList.put(id, mScheduledExecutorService.schedule({
mFutureList.remove(id)
try {
runnable()
} catch (e: Exception) {
Log.w(TAG, "timeout id: $id error: ${e.message}")
}
}, time, TimeUnit.MILLISECONDS))
return id
}
fun loop(task: LoopTask, period: Long): Long {
return loop(task, period, -1, 0)
}
fun loop(task: LoopTask, period: Long, times: Int): Long {
return loop({ id: Long, isLastTime: Boolean ->
task.run(id, isLastTime)
}, period, times, 0)
}
fun loop(task: LoopTask, period: Long, times: Int, delay: Long): Long {
return loop({ id: Long, isLastTime: Boolean ->
task.run(id, isLastTime)
}, period, times, delay)
}
fun loop(runnable: (id: Long, isLastTime: Boolean) -> Unit, period: Long, times: Int = -1, delay: Long = 0): Long {
val id = mId
mFutureList.put(id, mScheduledExecutorService.scheduleAtFixedRate(object: Runnable {
var vTimes = 0
override fun run() {
if (times >= 0) {
vTimes++
if (vTimes > times) {
cancel(id)
return
}
}
try {
runnable(id, vTimes == times)
} catch (e: Exception) {
Log.w(TAG, "loop id: $id times: $vTimes error: ${e.message}")
}
}
}, delay, period, TimeUnit.MILLISECONDS))
return id
}
interface LoopTask {
fun run(id: Long, isLastTime: Boolean)
}
}
\ No newline at end of file
package com.bgycc.smartcanteen.helper;
import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import java.net.NetworkInterface;
import java.util.List;
/**
* Created by patpat on 2018/6/11.
*/
public class WifiHelpler {
protected static WifiManager sWifiManager;
public static void initialize(Context context) {
if (context == null)
return;
sWifiManager = (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
public static DhcpInfo getDhcpInfo() {
try {
return sWifiManager.getDhcpInfo();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static WifiInfo getWifiInfo() {
try {
return sWifiManager.getConnectionInfo();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getMacAddress() {
try {
NetworkInterface networkInterface = NetworkInterface.getByName("wlan0");
byte[] data = networkInterface.getHardwareAddress();
return String.format("%02x:%02x:%02x:%02x:%02x:%02x", data[0], data[1], data[2], data[3], data[4], data[5]);
} catch (Exception e) {
}
return "02:00:00:00:00:00";
}
public static int getWifiNetworkID() {
try {
return getWifiInfo().getNetworkId();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public static boolean setEnable(boolean enable) {
try {
if (isWifiEnabled()) return true;
return sWifiManager.setWifiEnabled(enable);
} catch (Exception e) {
return false;
}
}
public static boolean isWifiEnabled() {
try {
return sWifiManager.isWifiEnabled();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static String getSSID() {
try {
return getWifiInfo().getSSID();
} catch (Exception e) {
return "02:00:00:00:00:00";
}
}
public static String getIpString() {
try {
int ip = getWifiInfo().getIpAddress();
StringBuilder sb = new StringBuilder();
sb.append(ip & 0xFF).append(".");
sb.append((ip >> 8) & 0xFF).append(".");
sb.append((ip >> 16) & 0xFF).append(".");
sb.append((ip >> 24) & 0xFF);
return sb.toString();
} catch (Exception e) {
return "0.0.0.0";
}
}
public static WifiConfiguration getWifiConfiguration(String ssid) {
try {
List<WifiConfiguration> existingConfigs = sWifiManager.getConfiguredNetworks();
for (WifiConfiguration existingConfig : existingConfigs) {
if (existingConfig.SSID.equals("\"" + ssid + "\"")) {
return existingConfig;
}
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}
public static boolean removeWifiConfiguration(String ssid) {
try {
WifiConfiguration wifiConfig = getWifiConfiguration(ssid);
if(wifiConfig != null) {
return sWifiManager.removeNetwork(wifiConfig.networkId);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
protected static WifiConfiguration createWifiConfiguration(String ssid, String pwd, String type) {
return createWifiConfiguration(ssid, null, pwd, type);
}
public static WifiConfiguration createWifiConfiguration(String ssid, String identity, String pwd, String type) {
if(ssid == null || ssid.isEmpty())
return null;
if (pwd != null && !pwd.isEmpty()) {
if (type == null || type.isEmpty())
type = "wpa";
}
else {
type = null;
}
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
config.SSID = "\"" + ssid + "\"";
if (type == null || type.isEmpty()) {
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
}
else if ("wep".compareToIgnoreCase(type) == 0) {
config.hiddenSSID = true;
config.wepKeys[0]= "\"" + pwd + "\"";
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
else if ("wpa".compareToIgnoreCase(type) == 0) {
config.preSharedKey = "\"" + pwd + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
config.status = WifiConfiguration.Status.ENABLED;
}
else if ("wpa_eap".compareToIgnoreCase(type) == 0) {
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
enterpriseConfig.setIdentity(identity);
enterpriseConfig.setPassword(pwd);
enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.PEAP);
config.enterpriseConfig = enterpriseConfig;
}
else {
return null;
}
return config;
}
public static int addNetwork(WifiConfiguration config) {
return sWifiManager.addNetwork(config);
}
public static boolean enableNetwork(int netId) {
return sWifiManager.enableNetwork(netId, true);
}
public static boolean connect(String ssid, String identity, String pwd, String type) {
WifiConfiguration config = createWifiConfiguration(ssid, identity, pwd, type);
if (config == null)
return false;
if (!isWifiEnabled())
return false;
boolean ret = removeWifiConfiguration(ssid);
int netId = sWifiManager.addNetwork(config);
if (!sWifiManager.enableNetwork(netId, true))
return false;
sWifiManager.reconnect();
return true;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment