diff --git a/CountBoard/.gitignore b/CountBoard/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/CountBoard/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/CountBoard/.idea/.gitignore b/CountBoard/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/CountBoard/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/CountBoard/.idea/.name b/CountBoard/.idea/.name
new file mode 100644
index 0000000..d3f190d
--- /dev/null
+++ b/CountBoard/.idea/.name
@@ -0,0 +1 @@
+XixunPlayer
\ No newline at end of file
diff --git a/CountBoard/.idea/compiler.xml b/CountBoard/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/CountBoard/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/.idea/gradle.xml b/CountBoard/.idea/gradle.xml
new file mode 100644
index 0000000..ae388c2
--- /dev/null
+++ b/CountBoard/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/.idea/misc.xml b/CountBoard/.idea/misc.xml
new file mode 100644
index 0000000..a25e2a9
--- /dev/null
+++ b/CountBoard/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/.idea/vcs.xml b/CountBoard/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/CountBoard/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/app/.gitignore b/CountBoard/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/CountBoard/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/CountBoard/app/build.gradle b/CountBoard/app/build.gradle
new file mode 100644
index 0000000..1439462
--- /dev/null
+++ b/CountBoard/app/build.gradle
@@ -0,0 +1,53 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'com.xixun.xixunplayer'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "com.xixun.xixunplayer"
+ minSdk 26
+ targetSdk 34
+ versionCode 1
+ versionName "1.0-timerboard"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ buildFeatures {
+ aidl true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation files('libs/gnph.jar')
+ implementation files('libs/xixun_card_settings_1.2.4.jar')
+}
+
+def getAppName() {
+ def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
+ String s = new XmlParser().parse(stringsFile).string.find { it.@name.equals 'app_name' }.text();
+ return s.replaceAll("\"", "");
+}
+
+// Change the Apk name
+android.applicationVariants.configureEach { variant ->
+ variant.outputs.configureEach {
+ def fileName = "${getAppName()}-${versionName}.apk"
+ outputFileName = fileName
+ }
+}
diff --git a/CountBoard/app/libs/connService2.jar b/CountBoard/app/libs/connService2.jar
new file mode 100644
index 0000000..9bb1491
Binary files /dev/null and b/CountBoard/app/libs/connService2.jar differ
diff --git a/CountBoard/app/libs/gnph.jar b/CountBoard/app/libs/gnph.jar
new file mode 100644
index 0000000..49484bf
Binary files /dev/null and b/CountBoard/app/libs/gnph.jar differ
diff --git a/CountBoard/app/libs/ijkplayer-arm64-0.8.8.aar b/CountBoard/app/libs/ijkplayer-arm64-0.8.8.aar
new file mode 100644
index 0000000..105f873
Binary files /dev/null and b/CountBoard/app/libs/ijkplayer-arm64-0.8.8.aar differ
diff --git a/CountBoard/app/libs/ijkplayer-armv7a-0.8.8.aar b/CountBoard/app/libs/ijkplayer-armv7a-0.8.8.aar
new file mode 100644
index 0000000..d84b81a
Binary files /dev/null and b/CountBoard/app/libs/ijkplayer-armv7a-0.8.8.aar differ
diff --git a/CountBoard/app/libs/ijkplayer-java-0.8.8.aar b/CountBoard/app/libs/ijkplayer-java-0.8.8.aar
new file mode 100644
index 0000000..8224130
Binary files /dev/null and b/CountBoard/app/libs/ijkplayer-java-0.8.8.aar differ
diff --git a/CountBoard/app/libs/xixun_card_settings_1.2.4.jar b/CountBoard/app/libs/xixun_card_settings_1.2.4.jar
new file mode 100644
index 0000000..0d17c22
Binary files /dev/null and b/CountBoard/app/libs/xixun_card_settings_1.2.4.jar differ
diff --git a/CountBoard/app/libs/zip4j-2.10.0.jar b/CountBoard/app/libs/zip4j-2.10.0.jar
new file mode 100644
index 0000000..d80d844
Binary files /dev/null and b/CountBoard/app/libs/zip4j-2.10.0.jar differ
diff --git a/CountBoard/app/proguard-rules.pro b/CountBoard/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/CountBoard/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/CountBoard/app/src/main/AndroidManifest.xml b/CountBoard/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..47da6ae
--- /dev/null
+++ b/CountBoard/app/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl b/CountBoard/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl
new file mode 100644
index 0000000..7726d8e
--- /dev/null
+++ b/CountBoard/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl
@@ -0,0 +1,47 @@
+// PlayerInfo.aidl
+package com.xixun.util;
+
+// Declare any non-default types here with import statements
+
+interface PlayerInfo {
+ //***需要实现,用于外部接口获取当前播放的节目名
+ String getProgramName();
+ //不需要实现
+ String getVersion();
+ //外部接口通知播放器屏幕宽高发生变化
+ void setScreenWidth(int w);
+ void setScreenHeight(int h);
+ //不需要
+ void taskScreenshot(String cmdId);
+ //不需要
+ void setExternalTemperature(float t);
+ void setInternalTemperature(float t);
+ void setHumidity(float h);
+ //暂时不需要
+ boolean forcePlayProgram(String pid);
+ boolean finishForcePlay();
+ //需要实现,让其他进程获取到当前播放的节目id
+ String getCurProgramId();
+ //需要,外部进程设置播放器的USB节目解压密码
+ void setUSBProgramPwd(String pwd);
+ //***需要,接收平台节目接口
+ String executeJosnCommand(String josn);
+ //暂停播放,以前的版本没有实现
+ void pausePlayer(boolean b);
+ //查询节目是否暂停播放
+ boolean isPause();
+ //***需要,清空节目和下载的素材
+ boolean clearTasks();
+ //需要,返回当前已有的节目数量
+ int countOfPrograms(int type);
+ //需要,指定id播放插播节目
+ void playInsertTask(String pid);
+ //需要,指定id停止播放插播节目
+ void stopInsertTask(String pid);
+ //***需要,回读当前节目json
+ String getProgramTask();
+ //不需要
+ void setUploadLogUrl(String playLog);
+ //不需要
+ String getUploadLogUrl();
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/assets/imgs/W0.png b/CountBoard/app/src/main/assets/imgs/W0.png
new file mode 100644
index 0000000..3e37d21
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W0.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W1.png b/CountBoard/app/src/main/assets/imgs/W1.png
new file mode 100644
index 0000000..5dee862
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W1.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W10.png b/CountBoard/app/src/main/assets/imgs/W10.png
new file mode 100644
index 0000000..7d55efe
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W10.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W13.png b/CountBoard/app/src/main/assets/imgs/W13.png
new file mode 100644
index 0000000..8577bdc
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W13.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W14.png b/CountBoard/app/src/main/assets/imgs/W14.png
new file mode 100644
index 0000000..2537239
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W14.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W15.png b/CountBoard/app/src/main/assets/imgs/W15.png
new file mode 100644
index 0000000..4be21b3
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W15.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W16.png b/CountBoard/app/src/main/assets/imgs/W16.png
new file mode 100644
index 0000000..0f2e015
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W16.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W17.png b/CountBoard/app/src/main/assets/imgs/W17.png
new file mode 100644
index 0000000..04f83db
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W17.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W18.png b/CountBoard/app/src/main/assets/imgs/W18.png
new file mode 100644
index 0000000..a3aec72
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W18.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W19.png b/CountBoard/app/src/main/assets/imgs/W19.png
new file mode 100644
index 0000000..32736e2
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W19.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W2.png b/CountBoard/app/src/main/assets/imgs/W2.png
new file mode 100644
index 0000000..c27cea2
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W2.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W20.png b/CountBoard/app/src/main/assets/imgs/W20.png
new file mode 100644
index 0000000..2b326a8
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W20.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W29.png b/CountBoard/app/src/main/assets/imgs/W29.png
new file mode 100644
index 0000000..1178e2e
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W29.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W3.png b/CountBoard/app/src/main/assets/imgs/W3.png
new file mode 100644
index 0000000..10e825b
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W3.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W30.png b/CountBoard/app/src/main/assets/imgs/W30.png
new file mode 100644
index 0000000..de3615c
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W30.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W31.png b/CountBoard/app/src/main/assets/imgs/W31.png
new file mode 100644
index 0000000..b4dd147
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W31.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W32.png b/CountBoard/app/src/main/assets/imgs/W32.png
new file mode 100644
index 0000000..581663d
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W32.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W33.png b/CountBoard/app/src/main/assets/imgs/W33.png
new file mode 100644
index 0000000..3b51e5a
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W33.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W34.png b/CountBoard/app/src/main/assets/imgs/W34.png
new file mode 100644
index 0000000..0bc4e72
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W34.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W35.png b/CountBoard/app/src/main/assets/imgs/W35.png
new file mode 100644
index 0000000..cfa67ec
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W35.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W36.png b/CountBoard/app/src/main/assets/imgs/W36.png
new file mode 100644
index 0000000..2b326a8
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W36.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W4.png b/CountBoard/app/src/main/assets/imgs/W4.png
new file mode 100644
index 0000000..17a05d8
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W4.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W44.png b/CountBoard/app/src/main/assets/imgs/W44.png
new file mode 100644
index 0000000..d829389
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W44.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W45.png b/CountBoard/app/src/main/assets/imgs/W45.png
new file mode 100644
index 0000000..9129ef8
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W45.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W46.png b/CountBoard/app/src/main/assets/imgs/W46.png
new file mode 100644
index 0000000..9129ef8
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W46.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W5.png b/CountBoard/app/src/main/assets/imgs/W5.png
new file mode 100644
index 0000000..5f7cbd7
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W5.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W6.png b/CountBoard/app/src/main/assets/imgs/W6.png
new file mode 100644
index 0000000..2e1a63c
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W6.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W7.png b/CountBoard/app/src/main/assets/imgs/W7.png
new file mode 100644
index 0000000..d37cc41
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W7.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W8.png b/CountBoard/app/src/main/assets/imgs/W8.png
new file mode 100644
index 0000000..a3394db
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W8.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/W9.png b/CountBoard/app/src/main/assets/imgs/W9.png
new file mode 100644
index 0000000..8e98a1c
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/W9.png differ
diff --git a/CountBoard/app/src/main/assets/imgs/logo3.png b/CountBoard/app/src/main/assets/imgs/logo3.png
new file mode 100644
index 0000000..3b4ea20
Binary files /dev/null and b/CountBoard/app/src/main/assets/imgs/logo3.png differ
diff --git a/CountBoard/app/src/main/java/com/xixun/command/reply/ReplyBase.java b/CountBoard/app/src/main/java/com/xixun/command/reply/ReplyBase.java
new file mode 100644
index 0000000..d2f446a
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/command/reply/ReplyBase.java
@@ -0,0 +1,10 @@
+package com.xixun.command.reply;
+
+import java.io.Serializable;
+
+
+public abstract class ReplyBase implements Serializable{
+
+ private static final long serialVersionUID = -3630726876519388513L;
+ public String commandId;
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java b/CountBoard/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java
new file mode 100644
index 0000000..54b2a64
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java
@@ -0,0 +1,18 @@
+package com.xixun.command.reply;
+
+public class TaskProgressReply extends ReplyBase {
+
+ private static final long serialVersionUID = 6264049742389542806L;
+ public int percent;
+ public String taskItemId;
+ public int speed;
+ public int remainingSeconds;
+
+ public TaskProgressReply(String commandId, String taskItemId, int percent, int sp, int rs) {
+ this.commandId = commandId;
+ this.taskItemId = taskItemId;
+ this.percent = percent;
+ this.speed = sp;
+ this.remainingSeconds = rs;
+ }
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/AIDLService.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/AIDLService.java
new file mode 100644
index 0000000..1682c71
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/AIDLService.java
@@ -0,0 +1,232 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.xixun.command.reply.TaskProgressReply;
+import com.xixun.util.PlayerInfo;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+
+import gnph.util.IOs;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.URLConn;
+
+public class AIDLService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ PlayerInfo.Stub binder = new PlayerInfo.Stub() {
+ @Override
+ public String getProgramName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getVersion() throws RemoteException {
+ Util.println("Server getVersion. <-"+null);
+ return null;
+ }
+
+ @Override
+ public void setScreenWidth(int width) throws RemoteException {
+ Util.println("Server setScreenWidth. ->"+width);
+ }
+
+ @Override
+ public void setScreenHeight(int height) throws RemoteException {
+ Util.println("Server setScreenHeight. ->"+height);
+
+ }
+
+ @Override
+ public void taskScreenshot(String cmdId) throws RemoteException {
+ Util.println("Server taskScreenshot. ->"+cmdId);
+ }
+
+ @Override
+ public void setExternalTemperature(float t) throws RemoteException {
+
+ }
+
+ @Override
+ public void setInternalTemperature(float t) throws RemoteException {
+
+ }
+
+ @Override
+ public void setHumidity(float h) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean forcePlayProgram(String pid) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean finishForcePlay() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getCurProgramId() throws RemoteException {
+ Util.println("Server getCurProgramId ...");
+ return null;
+ }
+
+ @Override
+ public void setUSBProgramPwd(String pwd) throws RemoteException {
+
+ }
+
+ @SuppressLint("ResourceType")
+ @Override
+ public String executeJosnCommand(String jsonstr) throws RemoteException {
+ Util.println("AIDL executeJsonCommand ..."+jsonstr);//{"_type":"DeleteTask","id":"652522a0e81d1e000009201a","sendTo":"yzd-player"}
+ String commandId = null;
+ try {
+ var jsonBytes = jsonstr.getBytes(StandardCharsets.UTF_8);
+ var json = JSMap.from(jsonBytes);
+ var _type = json.stnn("_type");
+ commandId = json.stnn("id");
+ if(_type.equals("DeleteTask")) {
+ return new JSMap(
+ "_type", "DataCallback",
+ "result", "received command",
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } else if(_type.equals("SetPlayerBackground")) {
+ var url = json.str("url");
+ if(url==null) new File(Util.backImgFile).delete();
+ else {
+ var fout = new FileOutputStream(Util.backImgFile);
+ IOs.write(fout, new URLConn(url).in());
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ }
+ MainActivity.ins.runOnUiThread(() -> {
+ MainActivity.ins.backView.cosImg = url==null ? null : BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ MainActivity.ins.backView.invalidate();
+ });
+ return new JSMap(
+ "_type", "Success",
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } else if(_type.equals("GetPlayerBackground")) {
+ var backImg = new File(Util.backImgFile);
+ var img = Base64.getEncoder().encodeToString(IOs.readBytesClose(backImg.exists() ? new FileInputStream(backImg) : MainActivity.ins.getResources().openRawResource(R.drawable.back)));
+ return new JSMap(
+ "_type", "DataCallback",
+ "cardId", Util.getCardId(),
+ "commandId", commandId,
+ "img", img
+ ).toString();
+ } else if(_type.equalsIgnoreCase("getProgramTask")) {
+ var task = JSMap.fromClose(new FileInputStream(Util.programDir+"/program")).jsmap("task");
+ return new JSMap(
+ "_type", "ProgramTaskCallback",
+ "cardId", Util.getCardId(),
+ "commandId", commandId,
+ "task", task
+ ).toString();
+ }
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", "Unknown Type: "+_type,
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", Util.toStr(e),
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ }
+ }
+
+ @Override
+ public void pausePlayer(boolean b) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPause() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean clearTasks() throws RemoteException {
+ return true;
+ }
+
+ @Override
+ public int countOfPrograms(int type) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void playInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public void stopInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public String getProgramTask() throws RemoteException {
+ try {
+ return IOs.readStrClose(new FileInputStream(Util.programDir+"/program"));
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ return Util.toStr(e);
+ }
+ }
+
+ @Override
+ public void setUploadLogUrl(String playLog) throws RemoteException {
+
+ }
+
+ @Override
+ public String getUploadLogUrl() throws RemoteException {
+ return null;
+ }
+ };
+
+ static class Src {
+ String filename;
+ String url;
+
+ public Src(String filename, String url) {
+ this.filename = filename;
+ this.url = url;
+ }
+ }
+ static class NeedDowns {
+ String prog;
+ ArrayList srcs = new ArrayList<>();
+ }
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java
new file mode 100644
index 0000000..b00610b
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java
@@ -0,0 +1,107 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class AbsLayout extends ViewGroup {
+
+ public AbsLayout(Context context) {
+ this(context, null);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int count = getChildCount();
+ int maxHeight = 0;
+ int maxWidth = 0;
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ for(int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if(child.getVisibility() != GONE) {
+ int childRight;
+ int childBottom;
+ var lp = (LayoutParams) child.getLayoutParams();
+ childRight = lp.x + child.getMeasuredWidth();
+ childBottom = lp.y + child.getMeasuredHeight();
+ maxWidth = Math.max(maxWidth, childRight);
+ maxHeight = Math.max(maxHeight, childBottom);
+ }
+ }
+ maxWidth += getPaddingLeft() + getPaddingRight();
+ maxHeight += getPaddingTop() + getPaddingBottom();
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(0, 0, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if(child.getVisibility() != GONE) {
+ var lp = (LayoutParams) child.getLayoutParams();
+ int childLeft = getPaddingLeft() + lp.x;
+ int childTop = getPaddingTop() + lp.y;
+ child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
+ }
+ }
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+
+ public int x;
+ public int y;
+
+ public LayoutParams(int x, int y, int width, int height) {
+ super(width, height);
+ this.x = x;
+ this.y = y;
+ }
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+ }
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/BackView.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/BackView.java
new file mode 100644
index 0000000..2085153
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/BackView.java
@@ -0,0 +1,40 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextPaint;
+import android.view.View;
+
+public class BackView extends View {
+
+ Bitmap cosImg;
+ Rect rect = new Rect();
+ Paint[] paints = {new TextPaint(), new TextPaint()};
+ String[] txts = {"0.00", ""};
+ int size2 = Util.screenHeight/2;
+
+ public BackView(Context context) {
+ super(context);
+ paints[0].setTextAlign(Paint.Align.RIGHT);
+ paints[1].setTextAlign(Paint.Align.RIGHT);
+ for(int i=0; i<2; i++) {
+ paints[i].setTextSize(size2);
+ paints[i].setColor(0xffffffff);
+ }
+ paints[0].setLinearText(true);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ rect.right = Util.screenWidth;
+ rect.bottom = Util.screenHeight;
+ canvas.clipRect(rect);
+ if(cosImg!=null) canvas.drawBitmap(cosImg, null, rect, null);
+ canvas.drawText(txts[0], Util.screenWidth-6, -paints[0].ascent(), paints[0]);
+ canvas.drawText(txts[1], Util.screenWidth-6, (Util.screenHeight+paints[0].getTextSize()-paints[1].ascent()-paints[1].descent())/2, paints[1]);
+ }
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/BootCompletedReceiver.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/BootCompletedReceiver.java
new file mode 100644
index 0000000..7433834
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/BootCompletedReceiver.java
@@ -0,0 +1,17 @@
+
+package com.xixun.xixunplayer;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Util.println("==== BootCompletedReceiver onReceive >> "+intent.getAction());
+ Util.initDir(context);
+ if(MainActivity.ins!=null) return;
+ var inten = new Intent(context, MainActivity.class);
+ inten.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(inten);
+ }
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainActivity.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
new file mode 100644
index 0000000..66d2404
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
@@ -0,0 +1,178 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.StrictMode;
+import android.view.Choreographer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.xixun.joey.aidlset.CardService;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+
+import gnph.util.JSMap;
+
+public class MainActivity extends Activity implements Choreographer.FrameCallback {
+
+ public static MainActivity ins;
+ ArrayList services = new ArrayList<>();
+ BackView backView;
+
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ins = this;
+ Util.println("==>> MainActivity onCreate >>>> this "+hashCode()+" Thread: "+Thread.currentThread().getId());
+
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
+ setContentView(backView = new BackView(MainActivity.this));
+
+ if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init();
+ else {
+ Util.println("---- No permission, Try again ...");
+ Util.makeText(this, "No permission, Try again ...").show();
+ ActivityCompat.requestPermissions(this, new String[]{
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.MANAGE_EXTERNAL_STORAGE,
+ android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
+ android.Manifest.permission.INTERNET
+ }, 999);
+ }
+ }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if(Util.programDir!=null) return;
+ if(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED) init();
+ else {
+ Util.println("---- Request Permission Failed");
+ Util.makeText(this, "Request Permission Failed").show();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Util.println("==<< MainActivity onDestroy <<<< this "+hashCode());
+ ins = null;
+ for(var service : services) unbindService(service);
+ try (var fout = new FileOutputStream(Util.programDir+"/cfg")){
+ Util.cfg.write(fout);
+ fout.flush();
+ fout.getFD().sync();
+ } catch (Exception ignored) {
+ }
+ System.gc();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Util.println(" ==>> MainActivity onStart >> "+hashCode());
+ }
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Util.println(" ==<< MainActivity onStop << "+hashCode());
+ }
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ Util.println(" ==>> MainActivity onRestart >> "+hashCode());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Util.println(" ==>> MainActivity onResume >> "+hashCode());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Util.println(" ==<< MainActivity onPause << "+hashCode());
+ }
+
+ CardService serviCard;
+ Intent intenCard;
+ ServiceConnection connCard;
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ public void init() {
+ Util.initDir(this);
+ backView.cosImg = BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ backView.invalidate();
+ if(MainService.ins==null) startService(new Intent(this, MainService.class));
+
+ var cfg = new File(Util.programDir+"/cfg");
+ if(! cfg.isFile()) Util.cfg = new JSMap();
+ else {
+ try {
+ Util.cfg = JSMap.fromClose(new FileInputStream(cfg));
+ } catch (Exception e) {
+ Util.cfg = new JSMap();
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ Util.println(Util.cfg.toStr());
+ connCard = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName name) {
+ serviCard = null;
+ Util.println("<-<- AIDL Service cardsystem Disconnected");
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder iBinder) {
+ unbindService(this);
+ services.remove(this);
+ Util.println("->-> AIDL Service cardsystem Connected");
+ serviCard = CardService.Stub.asInterface(iBinder);
+ try {
+ //Util.screenWidth = serviCard.getScreenWidth();
+ //Util.screenHeight = serviCard.getScreenHeight();
+ Util.println(" screen: "+Util.screenWidth+" x "+Util.screenHeight);
+ backView.invalidate();
+ } catch (Exception e) {
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ };
+ intenCard = new Intent("com.xixun.joey.aidlset.SettingsService");
+ intenCard.setPackage("com.xixun.joey.cardsystem");
+ bindService(intenCard, connCard, Context.BIND_AUTO_CREATE);
+ services.add(connCard);
+ }
+
+
+ Choreographer choreographer = Choreographer.getInstance();
+ boolean canAdd = true;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(backView == null) {
+ canAdd = true;
+ return;
+ }
+ choreographer.postFrameCallback(this);
+ canAdd = false;
+ //var milli = System.currentTimeMillis();
+ }
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainService.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainService.java
new file mode 100644
index 0000000..ae7b23d
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/MainService.java
@@ -0,0 +1,35 @@
+package com.xixun.xixunplayer;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class MainService extends Service {
+
+ static MainService ins;
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ins = this;
+ Util.println("==>> MainService onCreate >>>> "+hashCode()+" Thread: "+Thread.currentThread().getId());
+ UDPThread.startSocket();
+ TCPThread.startServer(3333);
+ if(MainActivity.ins==null) {
+ var intent = new Intent(this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ }
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Util.println("==<< MainService onDestroy <<<< this "+hashCode());
+ ins = null;
+ System.gc();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}
\ No newline at end of file
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/SrcCopy.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/SrcCopy.java
new file mode 100644
index 0000000..2f38af5
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/SrcCopy.java
@@ -0,0 +1,36 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+public class SrcCopy extends View {
+
+ View view;
+ float scaleX = 1, scaleY = 1;
+
+ public SrcCopy(Context context, View view) {
+ super(context);
+ this.view = view;
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ if(view==null) return;
+ if(scaleX==0) {
+ if(view.getWidth()!=0&&getWidth()!=0) {
+ scaleX = getWidth() / (float) view.getWidth();
+ scaleY = getHeight() / (float) view.getHeight();
+ } else {
+ invalidate();
+ return;
+ }
+ }
+ if(scaleX!=1 || scaleY!=1) canvas.scale(scaleX, scaleY);
+ view.draw(canvas);
+ invalidate();
+ }
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/TCPThread.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/TCPThread.java
new file mode 100644
index 0000000..7b04694
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/TCPThread.java
@@ -0,0 +1,204 @@
+package com.xixun.xixunplayer;
+
+import android.app.ActivityManager;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.os.StatFs;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.TimeZone;
+import java.util.concurrent.CountDownLatch;
+
+import gnph.util.IOs;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.NumFmts;
+import gnph.util.O;
+
+public class TCPThread extends Thread {
+
+ public static void startServer(int port) {
+ new Thread(()->{
+ try {
+ var serverSocket = new ServerSocket(port);
+ while(true) {
+ try {
+ Util.println("\nAccepting ...");
+ var socket = serverSocket.accept();
+ new TCPThread(MainActivity.ins, socket).start();
+ Util.println("\nAccepted");
+ } catch (Throwable e) {
+ var ins = MainActivity.ins;
+ if(ins!=null) ins.runOnUiThread(() -> Util.makeText(ins, Util.toStr(e)).show());
+ Util.printStackTrace(e);
+ }
+ }
+ } catch (Throwable e) {
+ var msg = e.getMessage();
+ if(msg==null || ! msg.contains("in use")) MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.ins, Util.toStr(e)).show());
+ Util.printStackTrace(e);
+ }
+ }).start();
+ }
+
+ MainActivity main;
+ Socket socket;
+ InputStream in;
+ OutputStream out;
+ SimpleDateFormat fmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
+
+ public TCPThread(MainActivity main, Socket socket) {
+ this.main = main;
+ this.socket = socket;
+ }
+ public void run() {
+ try {
+ socket.setSoTimeout(600000);
+ Util.socketThreads.add(this);
+ in = socket.getInputStream();
+ out = socket.getOutputStream();
+ HashSet existed = null;
+ ByteArrayOutputStream progJson = null;
+ while(true) {
+ var obj = JSMap.from(in);
+ var _type = obj.stnn("_type");
+ Util.println("_type: "+_type);
+ if("consult".equals(_type)) {
+ JSList files = obj.jslist("files");
+ if(files==null) new JSMap("_type", _type, "idList", obj.jslist("idList")).write(out);
+ else {
+ existed = new HashSet<>();
+ for(var fil : files) {
+ var name = fil.stnn("name");
+ var file = new File(Util.programDir + "/" + name);
+ if(file.isFile() && file.length()==fil.intg("size")) existed.add(name);
+ }
+ new JSMap("_type", _type, "existed", existed).write(out);
+ }
+ } else if("setBackImg".equals(_type) || "imgFileStart".equals(_type)) {
+ var fout = new FileOutputStream(Util.backImgFile);
+ IOs.write(fout, in, obj.intg("size"));
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ var img = BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ if(main!=null) main.runOnUiThread(() -> {
+ main.backView.cosImg = img;
+ main.backView.invalidate();
+ });
+ new JSMap("success", true).write(out);
+ } else if("DelBackImg".equals(_type)) {
+ MainActivity.ins.runOnUiThread(() -> {
+ MainActivity.ins.backView.cosImg = null;
+ MainActivity.ins.backView.invalidate();
+ });
+ new JSMap("success", new File(Util.backImgFile).delete()).write(out);
+ } else if("Config".equals(_type)) {
+ var entrySet = obj.entrySet();
+ for(var entry : entrySet) if(! entry.getKey().equals("_type")) Util.cfg.put(entry.getKey(), entry.getValue());
+ try (var fOut = new FileOutputStream(Util.programDir + "/cfg")) {
+ Util.cfg.write(fOut);
+ fOut.flush();
+ fOut.getFD().sync();
+ }
+ new JSMap("success", new File(Util.backImgFile).delete()).write(out);
+ } else if("GetInfo".equals(_type)) {
+ var writer = new OutputStreamWriter(out);
+ var fmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS");
+ writer.append(" Current: ").append(fmt.format(System.currentTimeMillis())).append(" ").append(String.valueOf(TimeZone.getDefault().getRawOffset()/3600000f)).append(" ").append(TimeZone.getDefault().getDisplayName()).append("\n");
+
+ var statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
+ writer.append(" Disk: ").append(String.valueOf(statFs.getTotalBytes()/1000000)).append(" ").append(String.valueOf(statFs.getAvailableBytes()/1000000)).append(" ").append(String.valueOf(statFs.getFreeBytes()/1000000)).append(" (total avail free)\n");
+ if(main!=null) {
+ var actManager = (ActivityManager) main.getSystemService(MainActivity.ACTIVITY_SERVICE);
+ var memoryInfo = new ActivityManager.MemoryInfo();
+ actManager.getMemoryInfo(memoryInfo);
+ writer.append(" Memory: ").append(String.valueOf(memoryInfo.totalMem/1000000)).append(" ").append(String.valueOf(memoryInfo.availMem/1000000)).append(" ").append(String.valueOf(memoryInfo.threshold/1000000)).append(" ").append(String.valueOf(memoryInfo.lowMemory)).append(" (total avail threshold low)\n");
+ }
+ var runtime = Runtime.getRuntime();
+ writer.append("Runtime: ").append(String.valueOf(runtime.maxMemory()/1000000)).append(" ").append(String.valueOf(runtime.totalMemory()/1000000)).append(" ").append(NumFmts.cfix2().format(runtime.freeMemory()*0.000001)).append(" (max total free)\n");
+ writer.append("\nSockets ").append(String.valueOf(Util.socketThreads.size())).append("\n");
+ for(var socket : Util.socketThreads) writer.append(" ").append(String.valueOf(socket.socket.getInetAddress())).append(":").append(String.valueOf(socket.socket.getPort())).append(" Buf:").append(String.valueOf(socket.socket.getReceiveBufferSize()/1000)).append("k SoLinger:").append(String.valueOf(socket.socket.getSoLinger())).append("\n");
+
+ writer.append("\n\n");
+ writer.append(Util.cfg.toStr());
+ writer.append("\n\n");
+
+ var latch = new CountDownLatch(1);
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {}
+ writer.flush();
+ } else if("ListProgFiles".equals(_type)) {
+ var files = new File(Util.programDir).listFiles();
+ if(files!=null) {
+ Arrays.sort(files, (f1, f2) -> Long.signum(f2.lastModified() - f1.lastModified()));
+ var writer = new OutputStreamWriter(out);
+ for(var file : files) writer.append(fmt.format(new Date(file.lastModified()))).append(' ').append(file.getName()).append(' ').append(String.valueOf(file.length())).append('\n');
+ writer.flush();
+ }
+ } else if("GetFile".equals(_type)) {
+ var name = obj.str("name");
+ if(name==null) new JSMap("msg", "name is null").write(out);
+ else {
+ var file = new File(Util.programDir+"/"+name);
+ if(! file.isFile()) new JSMap("msg", "file not exist").write(out);
+ else {
+ new JSMap("len", file.length()).write(out);
+ IOs.writeCloseIn(out, new FileInputStream(file));
+ }
+ }
+ } else if("GetJsonWithFileInfo".equals(_type)) {
+ var inse = new File(Util.programDir+"/insert");
+ var files = new File(Util.programDir).listFiles();
+ if(inse.isFile()) {
+ var json = IOs.readStrClose(new FileInputStream(inse));
+ if(files!=null) for(var file : files) if(! "program".equals(file.getName())) json = json.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/");
+ var writer = new OutputStreamWriter(out);
+ writer.append("insert:\n");
+ writer.append(json);
+ if(! json.endsWith("\n")) writer.append("\n");
+ writer.flush();
+ }
+ var prog = new File(Util.programDir+"/program");
+ if(! prog.isFile()) new JSMap("msg", "'program' file not exist").write(out);
+ else {
+ var json = IOs.readStrClose(new FileInputStream(prog));
+ if(files!=null) for(var file : files) if(! "program".equals(file.getName())) json = json.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/");
+ var writer = new OutputStreamWriter(out);
+ writer.append("\nprogram:\n");
+ writer.append(json);
+ writer.flush();
+ }
+ }
+ out.flush();
+ Util.println("cmd end");
+ }
+ } catch (Throwable e) {
+ var emsg = e.getMessage();
+ if(e instanceof SocketTimeoutException || "Socket closed".equals(emsg) || (emsg!=null && emsg.endsWith("end-of-input"))) {
+ Util.println(emsg);
+ return;
+ }
+ if(main!=null) main.runOnUiThread(() -> Util.makeText(main, Util.toStr(e)).show());
+ Util.printStackTrace(e);
+ } finally {
+ Util.socketThreads.remove(this);
+ O.close(in, out, socket);
+ Util.println("conn end\n");
+ }
+ }
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/UDPThread.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/UDPThread.java
new file mode 100644
index 0000000..a1c1359
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/UDPThread.java
@@ -0,0 +1,116 @@
+package com.xixun.xixunplayer;
+
+import android.graphics.BitmapFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.util.Base64;
+
+import gnph.util.JSMap;
+import gnph.util.NumFmts;
+import gnph.util.URLConn;
+
+public class UDPThread extends Thread {
+
+ public static void startSocket() {
+ new Thread(()->{
+ try (var socket = new DatagramSocket(33331)) {
+ byte[] buffer = new byte[1024];
+ var imgBuf = new ByteArrayOutputStream(10240);
+ Util.println("\nAccepting");
+ while(true) {
+ try {
+ var packet = new DatagramPacket(buffer, buffer.length);
+ socket.receive(packet);
+ var len = packet.getLength();
+ if(buffer[0]!=0x5A || buffer[len-1]!=(byte)0xA5 || buffer[1]<0x0a) continue;
+ var ins = MainActivity.ins;
+ if(ins==null) continue;
+ var view = ins.backView;
+ if(buffer[1]==0x0a) {
+ var buf = new StringBuilder(16);
+ if(buffer[2]!=0) buf.append(buffer[2]&0xff).append(":");
+
+ if(buf.length()!=0) buf.append(NumFmts.zz().format(buffer[3]&0xff)).append(":");
+ else if(buffer[3]!=0) buf.append(buffer[3]&0xff).append(":");
+ if(buf.length()!=0) buf.append(NumFmts.zz().format(buffer[4]&0xff));
+ else buf.append(buffer[4]&0xff);
+
+ if(buffer[7]>=1) buf.append(".").append(buffer[5]&0xff);
+ if(buffer[7]>=2) buf.append(buffer[6]&0xff);
+ var txt = buf.toString();
+ ins.runOnUiThread(() -> {
+ view.txts[0] = txt;
+ view.invalidate();
+ });
+ } else if(buffer[1]==0x0d) {
+ var txt = new String(buffer, 4, packet.getLength()-6);
+ ins.runOnUiThread(() -> {
+ view.paints[1].setTextSize(view.size2);
+ var tlen = view.paints[1].measureText(view.txts[1] = txt);
+ if(tlen > Util.screenWidth) view.paints[1].setTextSize(Util.screenWidth * view.size2 / tlen);
+ view.invalidate();
+ });
+ } else if(buffer[1]==0x0b) {
+ int size = buffer[3]&0xff;
+ if(buffer[2]==0) ins.runOnUiThread(() -> {
+ view.paints[0].setTextSize(size);
+ if(size + view.paints[1].getTextSize() > Util.screenHeight) view.paints[1].setTextSize(Util.screenHeight-size);
+ view.invalidate();
+ });
+ else if(buffer[2]==1) ins.runOnUiThread(() -> {
+ view.paints[1].setTextSize(view.size2 = size);
+ var tlen = view.paints[1].measureText(view.txts[1]);
+ if(tlen > Util.screenWidth) view.paints[1].setTextSize(Util.screenWidth * view.size2 / tlen);
+ view.invalidate();
+ });
+ } else if(buffer[1]==0x0f) {
+ int color = 0xff000000 | (buffer[3]&0xff)<<16 | (buffer[4]&0xff)<<8 | buffer[5]&0xff;
+ int idx = buffer[2];
+ if(idx==0||idx==1) ins.runOnUiThread(() -> {
+ view.paints[idx].setColor(color);
+ view.invalidate();
+ });
+ } else if(buffer[1]==0x0c) {
+ int val = buffer[2]&0xff;
+ var json = new JSMap();
+ json.put("_id", "SetBrightness");
+ json.put("_type", "SetBrightness");
+ json.put("brightnessPercentage", val*100/255);
+ var res = new URLConn("http://localhost:2016/settings").writeJson(json.toStr()).read();
+ Util.println(res);
+ } else if(buffer[1]==0x0e) {
+ //Util.println(new String(Hex.to(buffer, packet.getLength(), null)));
+ if(buffer[3]==1) imgBuf.reset();
+ imgBuf.write(buffer, 6, packet.getLength()-8);
+ if(buffer[3]==buffer[2]) {
+ var data = Base64.getDecoder().decode(imgBuf.toByteArray());
+ var fout = new FileOutputStream(Util.backImgFile);
+ fout.write(data);
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ imgBuf.reset();
+ var img = BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ ins.runOnUiThread(() -> {
+ view.cosImg = img;
+ view.invalidate();
+ });
+ }
+ }
+ } catch (Throwable e) {
+ var ins = MainActivity.ins;
+ if(ins!=null) ins.runOnUiThread(() -> Util.makeText(ins, Util.toStr(e)).show());
+ Util.printStackTrace(e);
+ }
+ }
+ } catch (Throwable e) {
+ var msg = e.getMessage();
+ if(msg==null || ! msg.contains("in use")) MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.ins, Util.toStr(e)).show());
+ Util.printStackTrace(e);
+ }
+ }).start();
+ }
+}
diff --git a/CountBoard/app/src/main/java/com/xixun/xixunplayer/Util.java b/CountBoard/app/src/main/java/com/xixun/xixunplayer/Util.java
new file mode 100644
index 0000000..45d8dcd
--- /dev/null
+++ b/CountBoard/app/src/main/java/com/xixun/xixunplayer/Util.java
@@ -0,0 +1,91 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.Environment;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Vector;
+
+import gnph.util.IOs;
+import gnph.util.JSMap;
+
+public class Util {
+ public static JSMap cfg;
+ public static int screenWidth = 320, screenHeight = 160;
+
+ public static void initDir(Context ctx) {
+ var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : ctx.getExternalFilesDir(null).getAbsolutePath();
+ Util.println("\n---- Init dir "+dir);
+ Util.programDir = dir + "/program";
+ Util.backImgFile = dir + "/background";
+
+ var program = new File(Util.programDir);
+ if(program.isFile()) program.delete();
+ Util.println(" mkdir: "+program.mkdirs());
+ }
+
+ public static final BitmapFactory.Options noScaled = new BitmapFactory.Options();
+ static {
+ noScaled.inScaled = false;
+ }
+ public static final Vector socketThreads = new Vector<>();
+
+ public static StringBuffer buf = new StringBuffer();
+ public static SimpleDateFormat dateFmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS");
+
+ public static void println(String msg) {
+ System.out.println(msg);
+ if(buf.length()>1000000) buf.replace(0, 100000, "");
+ buf.append(msg).append("\n");
+ }
+ public static void printStackTrace(Throwable e) {
+ println(toStackTrace(e));
+ }
+
+ public static String toStr(Throwable e) {
+ var traces = e.getStackTrace();
+ var msg = e.toString();
+ for(var trace : traces) if(trace.getClassName().startsWith("com.xixun.xixunplayer.")) {
+ msg += "\n at "+trace.getFileName()+'.'+trace.getMethodName()+':'+trace.getLineNumber();
+ }
+ return msg;
+ }
+ public static String toStackTrace(Throwable e) {
+ var out = new CharArrayWriter();
+ var writer = new PrintWriter(out);
+ e.printStackTrace(writer);
+ writer.flush();
+ return out.toString();
+ }
+
+ public static Toast makeText(Context context, CharSequence text) {
+ var toast = Toast.makeText(context, text, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.TOP | Gravity.LEFT, 0, 0);
+ return toast;
+ }
+
+ public static String programDir, backImgFile;
+ public static String getCardId() {
+ try {
+ var bytes = IOs.readBytesClose(new FileInputStream("/data/joey/signed/card.id"));
+ if(bytes.length < 40) return "";
+ byte[] cMyKey = new byte[]{97, 119, 38, 3, 46, 112, 36, 93, 58, 100, 103, 62, 115, 112, 114, 51, 43, 61, 2, 101, 119};
+ for(int i=0; i<20; ++i) bytes[i] = (byte) (bytes[i * 2] - cMyKey[i] - i - (bytes[i * 2 + 1] - 3));
+ var cardId = new String(bytes);
+ if(cardId.length() > 13) cardId = cardId.substring(0, 13);
+ return cardId;
+ } catch (IOException e) {
+ printStackTrace(e);
+ return "";
+ }
+ }
+}
diff --git a/CountBoard/app/src/main/java/gnph/android/LinearBox.java b/CountBoard/app/src/main/java/gnph/android/LinearBox.java
new file mode 100644
index 0000000..f6606af
--- /dev/null
+++ b/CountBoard/app/src/main/java/gnph/android/LinearBox.java
@@ -0,0 +1,36 @@
+package gnph.android;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class LinearBox extends LinearLayout {
+
+ public LinearBox(Context context) {
+ super(context);
+ }
+ public LinearBox(ViewGroup box) {
+ super(box.getContext());
+ box.addView(this);
+ }
+
+ public LinearBox horizontal() {
+ setOrientation(HORIZONTAL);
+ return this;
+ }
+ public LinearBox vertical() {
+ setOrientation(VERTICAL);
+ return this;
+ }
+
+ public LinearBox addSpacing(int spacing) {
+ if(getOrientation()==HORIZONTAL) addView(new View(getContext()), new LayoutParams(spacing, 0));
+ else addView(new View(getContext()), new LayoutParams(0, spacing));
+ return this;
+ }
+ public LinearBox addStretch() {
+ addView(new View(getContext()), new LayoutParams(0, 0,1));
+ return this;
+ }
+}
diff --git a/CountBoard/app/src/main/res/drawable/back.png b/CountBoard/app/src/main/res/drawable/back.png
new file mode 100644
index 0000000..7d65f1e
Binary files /dev/null and b/CountBoard/app/src/main/res/drawable/back.png differ
diff --git a/CountBoard/app/src/main/res/drawable/ic_launcher_background.xml b/CountBoard/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/CountBoard/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CountBoard/app/src/main/res/drawable/ic_launcher_foreground.xml b/CountBoard/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/CountBoard/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/CountBoard/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/CountBoard/app/src/main/res/values/colors.xml b/CountBoard/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/CountBoard/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/CountBoard/app/src/main/res/values/strings.xml b/CountBoard/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a619236
--- /dev/null
+++ b/CountBoard/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ XixunPlayer
+
\ No newline at end of file
diff --git a/CountBoard/build.gradle b/CountBoard/build.gradle
new file mode 100644
index 0000000..3daed1d
--- /dev/null
+++ b/CountBoard/build.gradle
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+id 'com.android.application' version '8.1.1' apply false
+}
\ No newline at end of file
diff --git a/CountBoard/gradle.properties b/CountBoard/gradle.properties
new file mode 100644
index 0000000..3e927b1
--- /dev/null
+++ b/CountBoard/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/CountBoard/gradle/wrapper/gradle-wrapper.jar b/CountBoard/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/CountBoard/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/CountBoard/gradle/wrapper/gradle-wrapper.properties b/CountBoard/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d587198
--- /dev/null
+++ b/CountBoard/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 11 11:55:49 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/CountBoard/gradlew b/CountBoard/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/CountBoard/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/CountBoard/gradlew.bat b/CountBoard/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/CountBoard/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/CountBoard/settings.gradle b/CountBoard/settings.gradle
new file mode 100644
index 0000000..366ef33
--- /dev/null
+++ b/CountBoard/settings.gradle
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ jcenter()
+ }
+}
+
+rootProject.name = "XixunPlayer"
+include ':app'