Browse Source

first commit

bruceyong 2 years ago
commit
012c07e6bf
94 changed files with 4714 additions and 0 deletions
  1. 16 0
      .gitignore
  2. 23 0
      README.MD
  3. 1 0
      app/.gitignore
  4. 1 0
      app/agconnect-services.json
  5. 85 0
      app/build.gradle
  6. 21 0
      app/proguard-rules.pro
  7. 26 0
      app/src/androidTest/java/com/elexlab/cybercontroller/ExampleInstrumentedTest.java
  8. 58 0
      app/src/main/AndroidManifest.xml
  9. BIN
      app/src/main/assets/admin.jpg
  10. 0 0
      app/src/main/assets/command_scripts/excel_sum.py
  11. 6 0
      app/src/main/assets/command_scripts/key_click.py
  12. 7 0
      app/src/main/assets/command_scripts/key_event.py
  13. 7 0
      app/src/main/assets/command_scripts/text_input.py
  14. BIN
      app/src/main/assets/hello_kitty.jpeg
  15. BIN
      app/src/main/assets/hello_kitty2.jpeg
  16. BIN
      app/src/main/assets/ok.jpg
  17. BIN
      app/src/main/assets/tv/tv1.png
  18. BIN
      app/src/main/assets/tv/tv2.png
  19. BIN
      app/src/main/assets/tv/tv3.png
  20. BIN
      app/src/main/assets/tv/tv4.png
  21. BIN
      app/src/main/assets/tv/tv5.png
  22. BIN
      app/src/main/assets/tv/tv6.png
  23. BIN
      app/src/main/assets/tv_happy.png
  24. 53 0
      app/src/main/java/com/elexlab/cybercontroller/CyberApplication.java
  25. 334 0
      app/src/main/java/com/elexlab/cybercontroller/MainActivity.java
  26. 208 0
      app/src/main/java/com/elexlab/cybercontroller/communication/BluetoothClient.java
  27. 195 0
      app/src/main/java/com/elexlab/cybercontroller/communication/BluetoothKeyboard.java
  28. 7 0
      app/src/main/java/com/elexlab/cybercontroller/communication/DescriptorCollection.java
  29. 234 0
      app/src/main/java/com/elexlab/cybercontroller/communication/TcpClient.java
  30. 32 0
      app/src/main/java/com/elexlab/cybercontroller/communication/tcp/PackageUtil.java
  31. 37 0
      app/src/main/java/com/elexlab/cybercontroller/pojo/CommandMessage.java
  32. 40 0
      app/src/main/java/com/elexlab/cybercontroller/pojo/ScriptMessage.java
  33. 141 0
      app/src/main/java/com/elexlab/cybercontroller/services/ClockService.java
  34. 271 0
      app/src/main/java/com/elexlab/cybercontroller/services/CommandAnalyzer.java
  35. 53 0
      app/src/main/java/com/elexlab/cybercontroller/services/FaceAnalyzer.java
  36. 89 0
      app/src/main/java/com/elexlab/cybercontroller/services/FaceRecognizer.java
  37. 81 0
      app/src/main/java/com/elexlab/cybercontroller/services/HideCamera.java
  38. 119 0
      app/src/main/java/com/elexlab/cybercontroller/services/SpeechRecognizer.java
  39. 58 0
      app/src/main/java/com/elexlab/cybercontroller/services/TextRecognizer.java
  40. 56 0
      app/src/main/java/com/elexlab/cybercontroller/services/Translator.java
  41. 250 0
      app/src/main/java/com/elexlab/cybercontroller/ui/activities/LoginActivity.java
  42. 51 0
      app/src/main/java/com/elexlab/cybercontroller/ui/activities/TestActivity.java
  43. 76 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/AssetsAnimationImageView.java
  44. 123 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/CoolDigitalClock.java
  45. 67 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/InfoBoxView.java
  46. 313 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/SevenPartDigitalTube.java
  47. 144 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/SpeechRecordView.java
  48. 90 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/TouchboardView.java
  49. 134 0
      app/src/main/java/com/elexlab/cybercontroller/ui/widget/TranslationView.java
  50. 68 0
      app/src/main/java/com/elexlab/cybercontroller/utils/AssetsUtils.java
  51. 32 0
      app/src/main/java/com/elexlab/cybercontroller/utils/DeviceUtil.java
  52. 25 0
      app/src/main/java/com/elexlab/cybercontroller/utils/PermissionUtil.java
  53. 68 0
      app/src/main/java/com/elexlab/cybercontroller/utils/SharedPreferencesUtil.java
  54. 9 0
      app/src/main/res/anim/login_transition.xml
  55. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  56. 10 0
      app/src/main/res/drawable/ic_baseline_settings.xml
  57. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  58. BIN
      app/src/main/res/drawable/liveness_detection_frame.9.png
  59. BIN
      app/src/main/res/drawable/mask.png
  60. 10 0
      app/src/main/res/drawable/stroke_corner_background.xml
  61. 107 0
      app/src/main/res/layout/activity_login.xml
  62. 87 0
      app/src/main/res/layout/activity_main.xml
  63. 17 0
      app/src/main/res/layout/activity_test.xml
  64. 41 0
      app/src/main/res/layout/cool_digital_clock_layout.xml
  65. 76 0
      app/src/main/res/layout/dialog_settings.xml
  66. 28 0
      app/src/main/res/layout/view_info_box_layout.xml
  67. 18 0
      app/src/main/res/layout/view_speech_record.xml
  68. 29 0
      app/src/main/res/layout/view_translation_layout.xml
  69. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  70. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  71. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  72. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  73. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  74. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  75. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  76. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  77. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  78. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  79. BIN
      app/src/main/res/mipmap-xxhdpi/ic_microphone.png
  80. BIN
      app/src/main/res/mipmap-xxhdpi/tv_sleep.png
  81. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  82. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  83. 16 0
      app/src/main/res/values-night/themes.xml
  84. 10 0
      app/src/main/res/values/colors.xml
  85. 3 0
      app/src/main/res/values/strings.xml
  86. 16 0
      app/src/main/res/values/themes.xml
  87. 17 0
      app/src/test/java/com/elexlab/cybercontroller/ExampleUnitTest.java
  88. 27 0
      build.gradle
  89. 19 0
      gradle.properties
  90. BIN
      gradle/wrapper/gradle-wrapper.jar
  91. 6 0
      gradle/wrapper/gradle-wrapper.properties
  92. 172 0
      gradlew
  93. 84 0
      gradlew.bat
  94. 2 0
      settings.gradle

+ 16 - 0
.gitignore

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

+ 23 - 0
README.MD

@@ -0,0 +1,23 @@
+### 重要事项
+- 请把自己在HMS上申请的agconnect-services.json文件替换掉本项目的,并把build.gradle中的applicationId改成你自己的
+- 把CyberApplication中配置的apiKey(setApiKey)改成你自己的
+
+### 手机设置
+- 使用前先让手机和电脑蓝牙配对和连接
+- 把本App加入到电池白名单(电池优化选项中找到本App,选择不优化),否则息屏一段时间后,系统将断开socket连接,并且早Doze Mode下无法重连,电脑三连翻译快捷键将无法唤醒屏幕
+- 在手机旋转设置中打开“旋转锁屏界面”,否则每次解锁(或点亮屏幕)的横竖屏切换体验不佳
+- 最好关闭手机的锁屏界面,否则每次息屏后要解锁,体验不佳
+
+### 关键代码思路提示
+电脑上要执行的功能实际上是本App通过TCP连接注入的,这些功能脚本在`assets/command_scripts`下,比如`key_click.py`是在电脑上执行按键点击的功能脚本模板
+```python
+def process(params):
+    keys = params.split(',')
+    for key in keys:
+        key_down(key)
+        key_up(key)
+process(params)
+```
+在向电脑发送命令的时候只需要把这段脚本的文本,以及需要传入的`params`,然后发送给电脑就可以了,电脑上的python程序会自动解析并执行。
+
+> 也就是说,如果有新的对电脑的控制,不需要修改电脑上的python程序,直接在客户端编写新的脚本发送过去注入执行就好。

+ 1 - 0
app/.gitignore

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

+ 1 - 0
app/agconnect-services.json

@@ -0,0 +1 @@
+#请替换成自己的配置

+ 85 - 0
app/build.gradle

@@ -0,0 +1,85 @@
+plugins {
+    id 'com.android.application'
+}
+
+android {
+    compileSdkVersion 30
+    buildToolsVersion "30.0.3"
+
+    defaultConfig {
+        applicationId "com.elexlab.cybercontroller"
+        minSdkVersion 28
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.3.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    testImplementation 'junit:junit:4.+'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+    implementation 'com.github.mik3y:usb-serial-for-android:3.4.3'
+    implementation 'com.huawei.agconnect:agconnect-core:1.5.2.300'
+    // 引入基础SDK
+    implementation 'com.huawei.hms:ml-computer-translate:3.3.0.306'
+    // 引入文本翻译算法包
+    implementation 'com.huawei.hms:ml-computer-translate-model:3.3.0.306'
+
+    // 引入基础SDK
+    implementation 'com.huawei.hms:ml-computer-vision-handkeypoint:3.3.0.300'
+    // 引入手部关键点检测模型包
+    implementation 'com.huawei.hms:ml-computer-vision-handkeypoint-model:3.3.0.300'
+    // 引入手势识别模型包
+    implementation 'com.huawei.hms:ml-computer-vision-gesture-model:3.3.0.300'
+
+
+    // Liveness Detection sdk and FULL SDK
+    implementation 'com.huawei.hms:ml-computer-vision-livenessdetection:2.2.0.300'
+
+    //faceVerification
+    implementation 'com.huawei.hms:ml-computer-vision-faceverify:2.2.0.300'
+    implementation 'com.huawei.hms:ml-computer-vision-faceverify-model:2.2.0.300'
+
+    // 引入基础SDK
+    implementation 'com.huawei.hms:ml-computer-vision-face:3.3.0.300'
+    // 引入人脸轮廓+关键点检测模型包
+    implementation 'com.huawei.hms:ml-computer-vision-face-shape-point-model:3.3.0.300'
+    // 引入表情检测模型包
+    implementation 'com.huawei.hms:ml-computer-vision-face-emotion-model:3.3.0.300'
+    // 引入特征检测模型包
+    implementation 'com.huawei.hms:ml-computer-vision-face-feature-model:3.3.0.300'
+    // 引入3d检测模型包
+    implementation 'com.huawei.hms:ml-computer-vision-face-3d-model:3.3.0.300'
+
+    // 引入实时语音识别服务插件
+    implementation 'com.huawei.hms:ml-computer-voice-asr-plugin:3.3.0.300'
+
+    // 引入基础SDK
+    implementation 'com.huawei.hms:ml-computer-vision-ocr:3.3.0.301'
+    // 引入拉丁语文字识别模型包
+    implementation 'com.huawei.hms:ml-computer-vision-ocr-latin-model:3.3.0.301'
+    // 引入日韩语文字识别模型包
+    implementation 'com.huawei.hms:ml-computer-vision-ocr-jk-model:3.3.0.301'
+    // 引入中英文文字识别模型包
+    implementation 'com.huawei.hms:ml-computer-vision-ocr-cn-model:3.3.0.301'
+}
+
+apply plugin: 'com.huawei.agconnect'

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/com/elexlab/cybercontroller/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.elexlab.cybercontroller;
+
+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.elexlab.cybercontroller", appContext.getPackageName());
+    }
+}

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

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.elexlab.cybercontroller">
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+
+    <uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
+    <uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
+    <uses-feature android:name="android.hardware.sensor.accelerometer" android:required="false"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="com.huawei.android.launcher.permission.WRITE_SETTINGS"/>
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:name=".CyberApplication"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.CyberController">
+
+        <activity
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+            android:screenOrientation="landscape"
+            android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+            android:launchMode="singleInstance"
+            android:name=".ui.activities.LoginActivity">
+
+        </activity>
+    </application>
+
+</manifest>

BIN
app/src/main/assets/admin.jpg


+ 0 - 0
app/src/main/assets/command_scripts/excel_sum.py


+ 6 - 0
app/src/main/assets/command_scripts/key_click.py

@@ -0,0 +1,6 @@
+def process(params):
+    keys = params.split(',')
+    for key in keys:
+        key_down(key)
+        key_up(key)
+process(params)

+ 7 - 0
app/src/main/assets/command_scripts/key_event.py

@@ -0,0 +1,7 @@
+def process(params):
+    keys = params.split(',')
+    for key in keys:
+        key_down(key)
+    for key in keys:
+        key_up(key)
+process(params)

+ 7 - 0
app/src/main/assets/command_scripts/text_input.py

@@ -0,0 +1,7 @@
+def text_input(params):
+    pyperclip.copy(params)
+    key_down("ctrl")
+    key_down("v")
+    key_up("v")
+    key_up("ctrl")
+text_input(params)

BIN
app/src/main/assets/hello_kitty.jpeg


BIN
app/src/main/assets/hello_kitty2.jpeg


BIN
app/src/main/assets/ok.jpg


BIN
app/src/main/assets/tv/tv1.png


BIN
app/src/main/assets/tv/tv2.png


BIN
app/src/main/assets/tv/tv3.png


BIN
app/src/main/assets/tv/tv4.png


BIN
app/src/main/assets/tv/tv5.png


BIN
app/src/main/assets/tv/tv6.png


BIN
app/src/main/assets/tv_happy.png


+ 53 - 0
app/src/main/java/com/elexlab/cybercontroller/CyberApplication.java

@@ -0,0 +1,53 @@
+package com.elexlab.cybercontroller;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.elexlab.cybercontroller.communication.BluetoothClient;
+import com.huawei.hms.mlsdk.common.MLApplication;
+
+public class CyberApplication extends Application {
+    private static Context context;
+
+    public static Context getContext() {
+        return context;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        this.context = this;
+        MLApplication.getInstance().setApiKey("替换成你自己的ApiKey");
+
+        BluetoothClient.bindContext(this);
+
+        ScreenStatusReceiver mScreenStatusReceiver = new ScreenStatusReceiver();
+
+        IntentFilter intentFilter = new IntentFilter();
+
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+        registerReceiver(mScreenStatusReceiver, intentFilter);
+    }
+
+    @Override
+    public void onTerminate() {
+        super.onTerminate();
+    }
+
+    private class ScreenStatusReceiver extends BroadcastReceiver{
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if ( "android.intent.action.SCREEN_ON".equals(intent.getAction())) {
+
+                BluetoothClient.getInstance().active();
+            }
+        }
+    }
+}

+ 334 - 0
app/src/main/java/com/elexlab/cybercontroller/MainActivity.java

@@ -0,0 +1,334 @@
+package com.elexlab.cybercontroller;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.elexlab.cybercontroller.communication.BluetoothKeyboard;
+import com.elexlab.cybercontroller.communication.TcpClient;
+import com.elexlab.cybercontroller.pojo.CommandMessage;
+import com.elexlab.cybercontroller.pojo.ScriptMessage;
+import com.elexlab.cybercontroller.services.CommandAnalyzer;
+import com.elexlab.cybercontroller.services.FaceAnalyzer;
+import com.elexlab.cybercontroller.services.HideCamera;
+import com.elexlab.cybercontroller.services.SpeechRecognizer;
+import com.elexlab.cybercontroller.services.TextRecognizer;
+import com.elexlab.cybercontroller.services.Translator;
+import com.elexlab.cybercontroller.ui.activities.LoginActivity;
+import com.elexlab.cybercontroller.ui.widget.InfoBoxView;
+import com.elexlab.cybercontroller.ui.widget.SpeechRecordView;
+import com.elexlab.cybercontroller.ui.widget.TouchboardView;
+import com.elexlab.cybercontroller.ui.widget.TranslationView;
+import com.elexlab.cybercontroller.utils.AssetsUtils;
+import com.elexlab.cybercontroller.utils.DeviceUtil;
+import com.elexlab.cybercontroller.utils.PermissionUtil;
+import com.elexlab.cybercontroller.utils.SharedPreferencesUtil;
+import com.google.gson.Gson;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialProber;
+import com.huawei.hmf.tasks.OnFailureListener;
+import com.huawei.hmf.tasks.OnSuccessListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.mlsdk.common.LensEngine;
+import com.huawei.hms.mlsdk.common.MLAnalyzer;
+import com.huawei.hms.mlsdk.common.MLFrame;
+import com.huawei.hms.mlsdk.gesture.MLGesture;
+import com.huawei.hms.mlsdk.gesture.MLGestureAnalyzer;
+import com.huawei.hms.mlsdk.gesture.MLGestureAnalyzerFactory;
+import com.huawei.hms.mlsdk.gesture.MLGestureAnalyzerSetting;
+import com.huawei.hms.mlsdk.handkeypoint.MLHandKeypointAnalyzer;
+import com.huawei.hms.mlsdk.handkeypoint.MLHandKeypointAnalyzerFactory;
+import com.huawei.hms.mlsdk.handkeypoint.MLHandKeypointAnalyzerSetting;
+import com.huawei.hms.mlsdk.handkeypoint.MLHandKeypoints;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MainActivity extends Activity {
+    private final static String TAG = MainActivity.class.getSimpleName();
+    private static TcpClient tcpClient = new TcpClient();
+    private Translator translator;
+    private TranslationView tvTranslation;
+    private InfoBoxView ivInfoBoxView;
+    private SpeechRecordView speechRecordView;
+    private TouchboardView touchboardView;
+    private BluetoothKeyboard bluetoothKeyboard;
+    private Handler handler = new Handler();
+    public static void startMe(Activity activity){
+        Intent intent = new Intent(activity, MainActivity.class);
+        activity.startActivity(intent);
+        activity.overridePendingTransition(R.anim.login_transition, R.anim.login_transition);
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        PermissionUtil.checkAndRequestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO});
+
+        setContentView(R.layout.activity_main);
+        speechRecordView = findViewById(R.id.speechRecordView);
+        tvTranslation = findViewById(R.id.tvTranslation);
+        touchboardView = findViewById(R.id.touchboardView);
+        ivInfoBoxView = findViewById(R.id.ivInfoBoxView);
+        ivInfoBoxView.dismiss(0);
+
+        translator = new Translator();
+
+        initSetting();
+        initTcpClient();
+        initMicrophone();
+        initTouchboard();
+
+    }
+
+    private void setImage(Bitmap bitmap){
+        //test
+        ImageView ivPreview = findViewById(R.id.ivPreview);
+        ivPreview.setImageBitmap(bitmap);
+    }
+
+    private void recognizeTextFormImg(Bitmap bitmap){
+        new TextRecognizer()
+                .setCallback((String text)->{
+                    translator.translate(text.toLowerCase(),(String result)->{
+                        onTranslateSuccess(text.toLowerCase(),result);
+
+                    });
+                })
+                .recognize(bitmap);
+       // handler.post(()->{setImage(bitmap);});
+    }
+
+    private void initSetting(){
+        findViewById(R.id.rlSettings).setOnClickListener((View view)->{
+            AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+            View settingView = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_settings, null,false);
+            builder.setView(settingView);
+            final Dialog dialog = builder.create();
+            //dialog.setContentView(settingView);
+            dialog.show();
+            ViewGroup.LayoutParams layoutParams = settingView.getLayoutParams();
+            layoutParams.width = (int) (DeviceUtil.getDeiveSize(MainActivity.this).widthPixels*0.8);
+            layoutParams.height = (int) (DeviceUtil.getDeiveSize(MainActivity.this).heightPixels*0.8);
+            settingView.setLayoutParams(layoutParams);
+
+            dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                @Override
+                public void onDismiss(DialogInterface dialog) {
+                }
+            });
+            EditText etHostIp = settingView.findViewById(R.id.etHostIp);
+            EditText etHostPort = settingView.findViewById(R.id.etHostPort);
+            etHostIp.setText(SharedPreferencesUtil.getPreference(MainActivity.this,"settings","hostIp","not set yet"));
+            int port = SharedPreferencesUtil.getPreference(MainActivity.this,"settings","hostPort",2233);
+            etHostPort.setText(String.valueOf(port));
+
+            settingView.findViewById(R.id.btnSave).setOnClickListener((View v)->{
+                Map<String,Object> preferences = new HashMap<String,Object>();
+
+                preferences.put("hostIp",etHostIp.getText().toString());
+                try{
+                    preferences.put("hostPort",Integer.parseInt(etHostPort.getText().toString()));
+                }catch (NumberFormatException e){
+                    e.printStackTrace();
+                    Toast.makeText(MainActivity.this,"端口号必须是数字",Toast.LENGTH_SHORT).show();
+
+                }
+                SharedPreferencesUtil.setPreferences(MainActivity.this,"settings",preferences);
+                Toast.makeText(MainActivity.this,"设置已更新,重启App生效",Toast.LENGTH_LONG).show();
+                dialog.dismiss();
+
+            });
+
+            settingView.findViewById(R.id.btnCancel).setOnClickListener((View v)->{
+                dialog.dismiss();
+            });
+
+        });
+    }
+
+    private void onTranslateSuccess(String source, String result){
+        tvTranslation.setContent(source,result);
+        DeviceUtil.acquireWakeLock(MainActivity.this,TranslationView.AUTO_DISMISS_TIME);
+    }
+
+
+    private void initTcpClient(){
+        tcpClient.onReceive((int type, byte[] data)->{
+            if(type==2){
+                //andler.postDelayed(()->{setImage(data);},1000);
+                Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
+                recognizeTextFormImg(bitmap);
+                return;
+            }
+            Log.d("!isInLoginActivity",""+!isInLoginActivity());
+            String text = null;
+            try {
+                text = new String(data,"UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                e.printStackTrace();
+                return;
+            }
+            Log.d(TAG,text);
+                CommandMessage commandMessage = new Gson().fromJson(text, CommandMessage.class);
+                if(commandMessage.getCommand() == CommandMessage.CommandType.TRANS){
+                    translator.translate(commandMessage.getMessage(), new OnSuccessListener<String>() {
+                        @Override
+                        public void onSuccess(String s) {
+                            Log.d("OnSuccessListener:",s);
+                            onTranslateSuccess(commandMessage.getMessage(),s);
+                        }
+                    });
+                }else if(commandMessage.getCommand() == CommandMessage.CommandType.LOCK && !isInLoginActivity()){
+                    LoginActivity.startMe(MainActivity.this,false);
+                }
+            }
+        );
+
+    }
+
+    private void initMicrophone(){
+        speechRecordView.setListener(new SpeechRecordView.SpeechListener() {
+            @Override
+            public void onRecognizingResults(int mode, String result) {
+                ivInfoBoxView.setInfo(result);
+            }
+
+            @Override
+            public void onResults(int mode, String result) {
+                ivInfoBoxView.setInfo(result);
+
+            }
+
+            @Override
+            public void onRecognizeStart(int mode) {
+                Log.d(TAG,"onRecognizeStart");
+
+                String title = "正在语音输入";
+                if(mode==2){
+                    title="正在语音命令";
+                }
+                ivInfoBoxView.setTitle(title);
+                ivInfoBoxView.setInfo("");
+                ivInfoBoxView.show(1000);
+            }
+
+            @Override
+            public void onRecognizeEnd(int mode, String result) {
+                Log.d(TAG,"onRecognizeEnd");
+                if(mode == 1){
+                    String script = AssetsUtils.loadCommandScripts(MainActivity.this,"text_input.py");
+                    ScriptMessage scriptMessage = new ScriptMessage();
+                    scriptMessage.setScript(script);
+                    scriptMessage.setParams(result);
+                    tcpClient.send(new Gson().toJson(scriptMessage));
+                }else if(mode == 2){
+                    List<ScriptMessage> scriptMessages = new CommandAnalyzer().analyzeCommand(result);
+                    for(ScriptMessage scriptMessage:scriptMessages){
+                        if(scriptMessage != null){
+                            tcpClient.send(new Gson().toJson(scriptMessage));
+                        }
+                    }
+
+                }
+
+
+
+                handler.postDelayed(()->{ivInfoBoxView.dismiss(1000);},1000);
+            }
+        });
+    }
+
+
+    private void initTouchboard(){
+        touchboardView.setTouchCallback(new TouchboardView.TouchCallback() {
+            @Override
+            public void onSwitchWindow(int direction) {
+                String script = AssetsUtils.loadCommandScripts(MainActivity.this,"key_event.py");
+                String direct = direction==0?"left_arrow":"right_arrow";
+                String params = "ctrl,win,"+direct;
+                ScriptMessage scriptMessage = new ScriptMessage();
+                scriptMessage.setScript(script);
+                scriptMessage.setParams(params);
+                tcpClient.send(new Gson().toJson(scriptMessage));
+            }
+
+            @Override
+            public void onWindowTab() {
+                String script = AssetsUtils.loadCommandScripts(MainActivity.this,"key_event.py");
+                String params = "win,tab";
+                ScriptMessage scriptMessage = new ScriptMessage();
+                scriptMessage.setScript(script);
+                scriptMessage.setParams(params);
+                tcpClient.send(new Gson().toJson(scriptMessage));
+            }
+        });
+    }
+
+
+
+    private Bitmap getImageFromAssetsFile(Context context, String fileName) {
+        Bitmap image = null;
+        AssetManager am = context.getResources().getAssets();
+        try {
+            InputStream is = am.open(fileName);
+            image = BitmapFactory.decodeStream(is);
+            is.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return image;
+    }
+
+    private boolean isInLoginActivity()
+    {
+        ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
+        ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
+        return cn.getClassName().contains(LoginActivity.class.getSimpleName());
+    }
+
+
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+    }
+
+    @Override
+    public void onBackPressed() {
+
+    }
+}

+ 208 - 0
app/src/main/java/com/elexlab/cybercontroller/communication/BluetoothClient.java

@@ -0,0 +1,208 @@
+package com.elexlab.cybercontroller.communication;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class BluetoothClient extends BluetoothHidDevice.Callback{
+    private static final String TAG = BluetoothClient.class.getSimpleName();
+
+    public interface Listener{
+        void onConnected();
+        void onDisConnected();
+    }
+
+    private static BluetoothClient instance;
+    public static BluetoothClient getInstance(){
+        if (instance == null){
+            Log.e(TAG,"BluetoothClient need bound a Context first");
+        }
+        return instance;
+    }
+    private BluetoothClient(){
+
+    }
+
+    public static void bindContext(Context context){
+        if(instance != null){
+            Log.e(TAG,"BluetoothClient already bind a Context");
+            return;
+        }
+        instance = new BluetoothClient(context);
+    }
+
+
+    private BluetoothHidDevice btHid;
+    private BluetoothDevice bluetoothDevice;
+    private BluetoothDevice hostDevice;
+    private BluetoothDevice mpluggedDevice;
+    private BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+    private Context context;
+    private ServiceListener serviceListener = new ServiceListener();
+    private Listener listener;
+
+    private BluetoothHidDeviceAppSdpSettings sdpRecord
+            = new BluetoothHidDeviceAppSdpSettings("Pixel HID1",
+                                                "Mobile BController",
+                                                  "bla",
+                                                    BluetoothHidDevice.SUBCLASS1_COMBO,
+                                                    DescriptorCollection.MOUSE_KEYBOARD_COMBO
+                                                  );
+
+    private BluetoothHidDeviceAppQosSettings qosOut
+            = new BluetoothHidDeviceAppQosSettings(BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,
+                                                        800,
+                                                        9,
+                                                        0,
+                                                        11250,
+                                                        BluetoothHidDeviceAppQosSettings.MAX
+                                                        );
+
+    public void setListener(Listener listener){
+        this.listener = listener;
+    }
+
+    public void connect(){
+        btHid.connect(mpluggedDevice);
+    }
+
+    public void sendData(int id,byte[] data){
+        btHid.sendReport(hostDevice, id, data);
+    }
+    public void active(){
+        if(btHid == null){
+            return;
+        }
+        btHid.registerApp(sdpRecord, null, qosOut, new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                command.run();
+            }
+        }, BluetoothClient.this);
+    }
+    public void stop(){
+        btHid.unregisterApp();
+    }
+
+    public void destory(){
+        btAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, btHid);
+    }
+
+
+    private BluetoothClient(Context context){
+        this.context = context;
+        init();
+    }
+
+    private void init(){
+        if(btHid != null){
+            return;
+        }
+        btAdapter.getProfileProxy(context,serviceListener, BluetoothProfile.HID_DEVICE);
+    }
+
+    private class ServiceListener implements BluetoothProfile.ServiceListener{
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            Log.i(TAG, "Connected to service");
+            if (profile != BluetoothProfile.HID_DEVICE) {
+                Log.wtf(TAG, "WTF:"+profile);
+                return;
+            }
+            if (!(proxy instanceof BluetoothHidDevice)) {
+                Log.wtf(TAG, "WTF? Proxy received but it's not BluetoothHidDevice");
+
+                return;
+            }
+            btHid= (BluetoothHidDevice)proxy;
+            active();
+
+            //btAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 300000);
+
+            try {
+                for(Method m:BluetoothAdapter.class.getMethods()){
+                    if("setScanMode".equals(m.getName())&& m.getParameterCount()>1){
+                        m.invoke(btAdapter,BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 300000);
+                        break;
+                    }
+
+                }
+//                Method method = BluetoothAdapter.class.getMethod("setScanMode", int.class, long.class);
+//                method.invoke(btAdapter,BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 300000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            Log.e(TAG, "Service disconnected!");
+            if (profile == BluetoothProfile.HID_DEVICE)
+                btHid = null;
+        }
+    }
+
+    @Override
+    public void onConnectionStateChanged(BluetoothDevice device, int state) {
+        super.onConnectionStateChanged(device, state);
+        Log.d(TAG,"Connection state:"+state);
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            if (device != null) {
+                hostDevice = device;
+                if(listener != null){
+                    listener.onConnected();
+                }
+
+
+            } else {
+                Log.e(TAG, "Device not connected");
+            }
+        } else {
+            hostDevice = null;
+            if(state == BluetoothProfile.STATE_DISCONNECTED)
+            {
+                if(listener != null){
+                    listener.onDisConnected();
+                }
+            }
+
+        }
+    }
+
+    @Override
+    public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
+        super.onAppStatusChanged(pluggedDevice, registered);
+        if(registered)
+        {
+            int[] states = new int[]{BluetoothProfile.STATE_CONNECTING,BluetoothProfile.STATE_CONNECTED,BluetoothProfile.STATE_DISCONNECTED,BluetoothProfile.STATE_DISCONNECTING};
+            List<BluetoothDevice> pairedDevices = btHid.getDevicesMatchingConnectionStates(states);
+            Log.d(TAG, "paired devices: "+pairedDevices);
+            mpluggedDevice = pluggedDevice;
+            if (pluggedDevice != null && btHid.getConnectionState(pluggedDevice) == BluetoothProfile.STATE_DISCONNECTED) {
+                btHid.connect(pluggedDevice);
+            } else {
+                BluetoothDevice pairedDevice =  pairedDevices.get(0);
+
+                int pairedDState = btHid.getConnectionState(pairedDevice);
+                Log.d(TAG,"paired "+pairedDState);
+                if (pairedDState == BluetoothProfile.STATE_DISCONNECTED) {
+                    btHid.connect(pairedDevice);
+                }
+
+            }
+        }
+    }
+
+
+}

+ 195 - 0
app/src/main/java/com/elexlab/cybercontroller/communication/BluetoothKeyboard.java

@@ -0,0 +1,195 @@
+package com.elexlab.cybercontroller.communication;
+
+import android.content.Context;
+import android.view.KeyEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+public class BluetoothKeyboard {
+    public static Map<String,Byte> KEY2BYTE = new HashMap<String,Byte> ();
+    public static Map<String,Boolean> SHITBYTE = new HashMap<String,Boolean> ();
+    static {
+        KEY2BYTE.put("A", (byte) 4);
+        KEY2BYTE.put("B",(byte)5);
+        KEY2BYTE.put("C",(byte)6);
+        KEY2BYTE.put("D",(byte)7);
+        KEY2BYTE.put("E",(byte)8);
+        KEY2BYTE.put("F",(byte)9);
+        KEY2BYTE.put("G",(byte)0);
+        KEY2BYTE.put("H",(byte)11);
+        KEY2BYTE.put("I",(byte)12);
+        KEY2BYTE.put("J",(byte)13);
+        KEY2BYTE.put("K",(byte)14);
+        KEY2BYTE.put("L",(byte)15);
+        KEY2BYTE.put("M",(byte)16);
+        KEY2BYTE.put("N",(byte)17);
+        KEY2BYTE.put("O",(byte)18);
+        KEY2BYTE.put("P",(byte)19);
+        KEY2BYTE.put("Q",(byte)20);
+        KEY2BYTE.put("R",(byte)21);
+        KEY2BYTE.put("S",(byte)22);
+        KEY2BYTE.put("T",(byte)23);
+        KEY2BYTE.put("U",(byte)24);
+        KEY2BYTE.put("V",(byte)25);
+        KEY2BYTE.put("W",(byte)26);
+        KEY2BYTE.put("X",(byte)27);
+        KEY2BYTE.put("Y",(byte)28);
+        KEY2BYTE.put("Z",(byte)29);
+
+        KEY2BYTE.put("1",(byte)30);
+        KEY2BYTE.put("2",(byte)31);
+        KEY2BYTE.put("3",(byte)32);
+        KEY2BYTE.put("4",(byte)33);
+        KEY2BYTE.put("5",(byte)34);
+        KEY2BYTE.put("6",(byte)35);
+        KEY2BYTE.put("7",(byte)36);
+        KEY2BYTE.put("8",(byte)37);
+        KEY2BYTE.put("9",(byte)38);
+        KEY2BYTE.put("0",(byte)39);
+
+        KEY2BYTE.put("ENTER",(byte)40);
+        KEY2BYTE.put("ESC",(byte)41);
+        KEY2BYTE.put("BACK_SPACE",(byte)42);
+        KEY2BYTE.put("TAB",(byte)43);
+        KEY2BYTE.put("SPACE",(byte)44);
+        KEY2BYTE.put("-",(byte)45);
+        KEY2BYTE.put("=",(byte)46);
+        KEY2BYTE.put("[",(byte)47);
+        KEY2BYTE.put("]",(byte)48);
+        KEY2BYTE.put("\\",(byte)49);
+        KEY2BYTE.put(";",(byte)51);
+        KEY2BYTE.put("'",(byte)52);
+        KEY2BYTE.put("`",(byte)53);
+        KEY2BYTE.put(",",(byte)54);
+        KEY2BYTE.put(".",(byte)55);
+        KEY2BYTE.put("/",(byte)56);
+        KEY2BYTE.put("SCROLL_LOCK",(byte)71);
+        KEY2BYTE.put("INSERT ",(byte)73);
+        KEY2BYTE.put("HOME ",(byte)74);
+        KEY2BYTE.put("PAGE_UP  ",(byte)75);
+        KEY2BYTE.put("DELETE ",(byte)76);
+        KEY2BYTE.put("END ",(byte)77);
+        KEY2BYTE.put("PAGE_DOWN ",(byte)78);
+        KEY2BYTE.put("DPAD_RIGHT ",(byte)79);
+        KEY2BYTE.put("KEYCODE_DPAD_LEFT ",(byte)80);
+        KEY2BYTE.put("KEYCODE_DPAD_DOWN ",(byte)81);
+        KEY2BYTE.put("KEYCODE_DPAD_UP ",(byte)82);
+        KEY2BYTE.put("NUM_LOCK ",(byte)83);
+
+
+
+
+        KEY2BYTE.put("!",(byte)30);
+        SHITBYTE.put("!",true);
+
+        KEY2BYTE.put("@",(byte)31);
+        SHITBYTE.put("@",true);
+
+        KEY2BYTE.put("#",(byte)32);
+        SHITBYTE.put("#",true);
+
+        KEY2BYTE.put("$",(byte)33);
+        SHITBYTE.put("$",true);
+
+        KEY2BYTE.put("%",(byte)34);
+        SHITBYTE.put("%",true);
+
+        KEY2BYTE.put("^",(byte)35);
+        SHITBYTE.put("^",true);
+
+        KEY2BYTE.put("&",(byte)36);
+        SHITBYTE.put("&",true);
+
+        KEY2BYTE.put("*",(byte)37);
+        SHITBYTE.put("*",true);
+
+        KEY2BYTE.put("(",(byte)38);
+        SHITBYTE.put("(",true);
+
+        KEY2BYTE.put(")",(byte)39);
+        SHITBYTE.put(")",true);
+
+
+        KEY2BYTE.put("_",(byte)45);
+        SHITBYTE.put("_",true);
+
+        KEY2BYTE.put("+",(byte)46);
+        SHITBYTE.put("+",true);
+
+
+        KEY2BYTE.put("{",(byte)47);
+        SHITBYTE.put("{",true);
+
+        KEY2BYTE.put("}",(byte)48);
+        SHITBYTE.put("}",true);
+
+        KEY2BYTE.put("|",(byte)49);
+        SHITBYTE.put("|",true);
+
+        KEY2BYTE.put(":",(byte)51);
+        SHITBYTE.put(":",true);
+
+        KEY2BYTE.put("\"",(byte)52);
+        SHITBYTE.put("\"",true);
+
+        KEY2BYTE.put("<",(byte)54);
+        SHITBYTE.put("<",true);
+
+        KEY2BYTE.put(">",(byte)55);
+        SHITBYTE.put(">",true);
+
+        KEY2BYTE.put("?",(byte)56);
+        SHITBYTE.put("?",true);
+
+
+
+    }
+
+    private BluetoothClient bluetoothClient;
+    public BluetoothKeyboard(Context context){
+        bluetoothClient = BluetoothClient.getInstance();
+        bluetoothClient.setListener(new BluetoothClient.Listener() {
+            @Override
+            public void onConnected() {
+            }
+
+            @Override
+            public void onDisConnected() {
+                bluetoothClient.active();
+
+            }
+        });
+    }
+
+    public void sendKey(String key){
+        byte b1 = 0;
+
+        if(key.length()<=1){
+            char keyChar =  key.charAt(0);
+            if((keyChar>=65)&&(keyChar<=90)){
+                b1 = 2;
+            }
+        }
+
+
+        if(BluetoothKeyboard.SHITBYTE.containsKey(key)){
+            b1 = 2;
+        }
+
+        bluetoothClient.sendData(8, new byte[]{b1,0,BluetoothKeyboard.KEY2BYTE.get(key.toUpperCase())});
+        bluetoothClient.sendData( 8, new byte[]{0,0,0});
+
+    }
+
+    public void active(){
+        bluetoothClient.active();
+    }
+    public void stop(){
+        bluetoothClient.stop();
+    }
+
+
+
+}

+ 7 - 0
app/src/main/java/com/elexlab/cybercontroller/communication/DescriptorCollection.java

@@ -0,0 +1,7 @@
+package com.elexlab.cybercontroller.communication;
+
+public class DescriptorCollection {
+    public static final byte[]
+    MOUSE_KEYBOARD_COMBO = new byte[]{(byte)5, (byte)1, (byte)9, (byte)2, (byte)161, (byte)1, (byte)5, (byte)1, (byte)9, (byte)2, (byte)161, (byte)2, (byte)133, (byte)4, (byte)9, (byte)1, (byte)161, (byte)0, (byte)5, (byte)9, (byte)25, (byte)1, (byte)41, (byte)2, (byte)21, (byte)0, (byte)37, (byte)1, (byte)117, (byte)1, (byte)149, (byte)2, (byte)129, (byte)2, (byte)149, (byte)1, (byte)117, (byte)6, (byte)129, (byte)3, (byte)5, (byte)1, (byte)9, (byte)48, (byte)9, (byte)49, (byte)22, (byte)1, (byte)248, (byte)38, (byte)255, (byte)7, (byte)117, (byte)16, (byte)149, (byte)2, (byte)129, (byte)6, (byte)161, (byte)2, (byte)133, (byte)6, (byte)9, (byte)72, (byte)21, (byte)0, (byte)37, (byte)1, (byte)53, (byte)1, (byte)69, (byte)4, (byte)117, (byte)2, (byte)149, (byte)1, (byte)177, (byte)2, (byte)133, (byte)4, (byte)9, (byte)56, (byte)21, (byte)129, (byte)37, (byte)127, (byte)53, (byte)0, (byte)69, (byte)0, (byte)117, (byte)8, (byte)149, (byte)1, (byte)129, (byte)6, (byte)192, (byte)161, (byte)2, (byte)133, (byte)6, (byte)9, (byte)72, (byte)21, (byte)0, (byte)37, (byte)1, (byte)53, (byte)1, (byte)69, (byte)4, (byte)117, (byte)2, (byte)149, (byte)1, (byte)177, (byte)2, (byte)53, (byte)0, (byte)69, (byte)0, (byte)117, (byte)4, (byte)177, (byte)3, (byte)133, (byte)4, (byte)5, (byte)12, (byte)10, (byte)56, (byte)2, (byte)21, (byte)129, (byte)37, (byte)127, (byte)117, (byte)8, (byte)149, (byte)1, (byte)129, (byte)6, (byte)192, (byte)192, (byte)192, (byte)192, (byte)5, (byte)1, (byte)9, (byte)6, (byte)161, (byte)1, (byte)133, (byte)8, (byte)5, (byte)7, (byte)25, (byte)224, (byte)41, (byte)231, (byte)21, (byte)0, (byte)37, (byte)1, (byte)117, (byte)1, (byte)149, (byte)8, (byte)129, (byte)2, (byte)149, (byte)1, (byte)117, (byte)8, (byte)129, (byte)1, (byte)149, (byte)1, (byte)117, (byte)8, (byte)21, (byte)0, (byte)37, (byte)101, (byte)5, (byte)7, (byte)25, (byte)0, (byte)41, (byte)101, (byte)129, (byte)0, (byte)192};
+
+}

+ 234 - 0
app/src/main/java/com/elexlab/cybercontroller/communication/TcpClient.java

@@ -0,0 +1,234 @@
+package com.elexlab.cybercontroller.communication;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.elexlab.cybercontroller.CyberApplication;
+import com.elexlab.cybercontroller.communication.tcp.PackageUtil;
+import com.elexlab.cybercontroller.utils.SharedPreferencesUtil;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+public class TcpClient {
+
+    private static final String TAG = TcpClient.class.getSimpleName();
+    private static final int PACKAGE_HEAD_LENGTH = 8;
+
+    public interface ReceiveListener{
+        void onReceive(int type, byte[] data);
+    }
+    private ReceiveListener receiveListener;
+    private String hostip = "192.168.3.28";
+    private int port = 2233;
+    private  Socket socketClient;
+    private BufferedReader reader;
+    private BufferedWriter writer;
+    private Handler handler;
+    private Thread receiveThread;
+
+    public TcpClient(){
+        HandlerThread tcpThread = new HandlerThread("tcpThread");
+        tcpThread.start();
+        handler = new Handler(tcpThread.getLooper());
+        hostip = SharedPreferencesUtil.getPreference(CyberApplication.getContext(),"settings","hostIp",hostip);
+        port = SharedPreferencesUtil.getPreference(CyberApplication.getContext(),"settings","hostPort",port);
+
+        tryConnect();
+    }
+
+    private void tryConnect(){
+
+        handler.post(()->{
+            while(true){
+                if(connect()){
+                    startReceive();
+                    break;
+                }
+            }
+
+        });
+
+    }
+
+    private boolean connect(){
+        closeAll();
+        try{
+            socketClient = new Socket();
+            SocketAddress socAddress = new InetSocketAddress(hostip, port);
+            socketClient.connect(socAddress,5000);
+            Log.i(TAG,"socket 连接成功");
+            writer = new BufferedWriter(new OutputStreamWriter(socketClient.getOutputStream()));
+
+        }catch (UnknownHostException e){
+            e.printStackTrace();
+            return false;
+        }catch (IOException e) {
+            e.printStackTrace();
+            return false;
+
+        }
+        return true;
+
+    }
+    public void send(String msg){
+        Log.d(TAG,"send msg:"+msg);
+        byte[] data = PackageUtil.pack(msg.getBytes(),1);
+        send(data);
+    }
+
+    public void send(byte[] data){
+        Log.d(TAG,"send data len:"+data.length);
+        handler.post(()->{
+            try {
+                OutputStream outputStream = socketClient.getOutputStream();
+                outputStream.write(data);
+            } catch (SocketException e) {
+                e.printStackTrace();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+
+            }
+        });
+    }
+
+    public void onReceive(ReceiveListener receiveListener){
+        this.receiveListener = receiveListener;
+    }
+
+    public void destroy(){
+        if(receiveThread!=null){
+            receiveThread.interrupt();
+        }
+        closeAll();
+    }
+
+    private void closeAll(){
+        if(socketClient == null || socketClient.isClosed() || !socketClient.isConnected()){
+            return;
+        }
+
+        try {
+            socketClient.shutdownInput();
+            socketClient.shutdownOutput();
+            socketClient.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void startReceive(){
+        if(receiveThread != null){
+            receiveThread.interrupt();
+        }
+        receiveThread = new Thread(new Receiver());
+        receiveThread.start();
+    }
+    private class Receiver implements Runnable{
+
+        @Override
+        public void run() {
+            while (true){
+                if(receiveThread.isInterrupted()){
+                    break;
+                }
+                try {
+                    InputStream inputStream = socketClient.getInputStream();
+                    boolean badPackage = false;//a flag control the program blow
+                    int countHeadBytesLength = 0;
+                    byte[] headData = new byte[PACKAGE_HEAD_LENGTH];
+                    while (countHeadBytesLength < PACKAGE_HEAD_LENGTH) {
+                        int result = inputStream.read(headData, countHeadBytesLength, PACKAGE_HEAD_LENGTH - countHeadBytesLength);
+                        if (result == -1) {
+                            //it's a bad package when we read a package head
+                            //but it not enough for 8 byte
+                            badPackage = true;
+                            break;
+                        }
+                        countHeadBytesLength += result;
+                    }
+                    if(badPackage){
+                        Log.e(TAG,"badPackage!");
+                        continue;
+                    }
+                    Log.d(TAG,"headData:"+ Arrays.toString(headData));
+
+                    int packageType = getTypeFromHeadByte(headData);
+                    int packageBodyBytesLength = getLengthFromHeadByte(headData);
+                    Log.d(TAG,"new data received type:"+packageType+" body len:"+packageBodyBytesLength);
+
+                    int countBodyBytesLength = 0;
+                    byte[] bodyData = new byte[packageBodyBytesLength];
+                    while (countBodyBytesLength < packageBodyBytesLength) {
+                        int result = inputStream.read(bodyData, countBodyBytesLength, packageBodyBytesLength - countBodyBytesLength);
+                        if (result == -1) {
+                            //we follow the strict protocol
+                            badPackage = true;
+                            break;
+                        }
+                        countBodyBytesLength += result;
+                    }
+                    if(badPackage){
+                        Log.e(TAG,"badPackage!");
+                        continue;
+                    }
+
+                    if(receiveListener != null){
+                        receiveListener.onReceive(packageType, bodyData);
+                    }
+                    bodyData=null;
+
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    if(receiveThread.isInterrupted()){
+                        break;
+                    }
+                    tryConnect();
+                    break;
+                }
+
+            }
+        }
+    }
+
+    private int getLengthFromHeadByte(byte[] b) throws IOException{
+        if(b.length!=8){
+            throw new EOFException();
+        }
+        int ch1 = b[4] & 0x00FF;
+        int ch2 = b[5] & 0x00FF;
+        int ch3 = b[6] & 0x00FF;
+        int ch4 = b[7] & 0x00FF;
+
+        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
+    }
+
+    private int getTypeFromHeadByte(byte[] b) throws IOException{
+        if(b.length!=8){
+            throw new EOFException();
+        }
+        int ch1 = b[0] & 0x00FF;
+        int ch2 = b[1] & 0x00FF;
+        int ch3 = b[2] & 0x00FF;
+        int ch4 = b[3] & 0x00FF;
+
+        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
+    }
+
+
+}

+ 32 - 0
app/src/main/java/com/elexlab/cybercontroller/communication/tcp/PackageUtil.java

@@ -0,0 +1,32 @@
+package com.elexlab.cybercontroller.communication.tcp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class PackageUtil {
+    public static byte[] pack(byte[] bytes,int type){
+        DataOutputStream dOutput = null;
+        try {
+            ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
+            dOutput = new DataOutputStream(bOutput);
+            dOutput.writeInt(type);
+            dOutput.writeInt(bytes.length);
+            dOutput.write(bytes);
+            byte[] dataPackage = bOutput.toByteArray();
+            return dataPackage;
+        }catch (IOException e){
+            e.printStackTrace();
+        }finally {
+            if(dOutput!=null){
+                try {
+                    dOutput.close();
+                }catch (IOException e){
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return null;
+    }
+}

+ 37 - 0
app/src/main/java/com/elexlab/cybercontroller/pojo/CommandMessage.java

@@ -0,0 +1,37 @@
+package com.elexlab.cybercontroller.pojo;
+
+public class CommandMessage {
+    public static interface CommandType{
+        int TRANS = 1;
+        int LOCK = 2;
+        int INPUT = 3;
+        int KEY_EVENT = 4;
+
+    }
+    private int command;
+    private String message;
+
+    public CommandMessage() {
+    }
+
+    public CommandMessage(int command, String message) {
+        this.command = command;
+        this.message = message;
+    }
+
+    public int getCommand() {
+        return command;
+    }
+
+    public void setCommand(int command) {
+        this.command = command;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}

+ 40 - 0
app/src/main/java/com/elexlab/cybercontroller/pojo/ScriptMessage.java

@@ -0,0 +1,40 @@
+package com.elexlab.cybercontroller.pojo;
+
+public class ScriptMessage {
+    private int command=5;
+    private String script;
+    private String params;
+    private int delay=0;
+
+    public int getCommand() {
+        return command;
+    }
+
+    public void setCommand(int command) {
+        this.command = command;
+    }
+
+    public String getScript() {
+        return script;
+    }
+
+    public void setScript(String script) {
+        this.script = script;
+    }
+
+    public String getParams() {
+        return params;
+    }
+
+    public void setParams(String params) {
+        this.params = params;
+    }
+
+    public int getDelay() {
+        return delay;
+    }
+
+    public void setDelay(int delay) {
+        this.delay = delay;
+    }
+}

+ 141 - 0
app/src/main/java/com/elexlab/cybercontroller/services/ClockService.java

@@ -0,0 +1,141 @@
+package com.elexlab.cybercontroller.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Created by BruceYoung on 16/2/12.
+ */
+public class ClockService {
+    private static final String TAG = "ClockService";
+    private static ClockService instance;
+
+
+    private static final long DIDA_TIME = 1000;
+    private int day;
+    private int hour;
+    private int min;
+    private TimeReceiver timeReceiver;
+    private Context context;
+    public ClockService(Context context){
+        this.context = context;
+
+        timeReceiver = new TimeReceiver();
+        registerTimeReceiver();
+    }
+
+
+
+
+    private void timeTickTask(){
+        int currentDay = Calendar.getInstance().getTime().getDay();
+        int currentHour = Calendar.getInstance().getTime().getHours();
+        int currentMin = Calendar.getInstance().getTime().getMinutes();
+
+        if(currentDay!=day){
+            dida.setNewDay(true);
+            day = currentDay;
+        }else{
+            dida.setNewDay(false);
+        }
+        if(currentHour!=hour){
+            dida.setNewHour(true);
+            hour = currentHour;
+        }else{
+            dida.setNewHour(false);
+        }
+        if(currentMin!=min){
+            dida.setNewMin(true);
+            min = currentMin;
+        }else{
+            dida.setNewMin(false);
+        }
+        notifyClockObserver();
+        //ThreadService.getInstance().getClockHandler().postDelayed(this,DIDA_TIME);
+    }
+
+    private class TimeReceiver extends BroadcastReceiver{
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG,"TimeReceiver onReceive"+new SimpleDateFormat("yy/MM/DD hh:mm:ss").format(new Date()));
+            timeTickTask();
+        }
+    }
+    private void  registerTimeReceiver(){
+        IntentFilter filter=new IntentFilter();
+        filter.addAction(Intent.ACTION_TIME_TICK);
+        context.registerReceiver(timeReceiver, filter);
+    }
+    //all observer
+    private static Set<ClockObserverInterf> notifySet=new HashSet<ClockObserverInterf>();
+
+    public synchronized void registClockObserver(ClockObserverInterf observer){
+        notifySet.add(observer);
+    }
+    public synchronized void unregistClockObserver(ClockObserverInterf observer){
+        notifySet.remove(observer);
+    }
+
+
+    private Dida dida = new Dida();
+    //notify observers when the data changed
+    public synchronized void notifyClockObserver(){
+        Log.d(TAG,"dida");
+        for(ClockObserverInterf observer:notifySet){
+            if(!observer.dida(dida)) {
+                break;
+            }
+        }
+    }
+
+    public void destroy(){
+        context.unregisterReceiver(timeReceiver);
+
+    }
+
+    public static interface ClockObserverInterf{
+        public boolean dida(Dida dida);
+
+    }
+
+    public static class Dida{
+        private boolean newDay;
+        private boolean newHour;
+        private boolean newMin;
+
+        public boolean isNewDay() {
+            return newDay;
+        }
+
+        public void setNewDay(boolean newDay) {
+            this.newDay = newDay;
+        }
+
+        public boolean isNewHour() {
+            return newHour;
+        }
+
+        public void setNewHour(boolean newHour) {
+            this.newHour = newHour;
+        }
+
+        public boolean isNewMin() {
+            return newMin;
+        }
+
+        public void setNewMin(boolean newMin) {
+            this.newMin = newMin;
+        }
+    }
+}

+ 271 - 0
app/src/main/java/com/elexlab/cybercontroller/services/CommandAnalyzer.java

@@ -0,0 +1,271 @@
+package com.elexlab.cybercontroller.services;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.elexlab.cybercontroller.CyberApplication;
+import com.elexlab.cybercontroller.MainActivity;
+import com.elexlab.cybercontroller.pojo.ScriptMessage;
+import com.elexlab.cybercontroller.utils.AssetsUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 图一乐,不知道TNT的意图识别是怎么实现的,这里用一些正则的手段硬编码
+ */
+public class CommandAnalyzer {
+    private final static String TAG = CommandAnalyzer.class.getSimpleName();
+    private static Map<String,String> colorParmas = new HashMap<>();
+    static {
+        /*
+         *只录入了部分颜色,实现原理是模拟按键,然后用快捷键切换颜色
+         */
+        colorParmas.put("红色","alt,h,f,c,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,right_arrow,enter");
+        colorParmas.put("橙色","alt,h,f,c,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,right_arrow,right_arrow,enter");
+        colorParmas.put("黄色","alt,h,f,c,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,right_arrow,right_arrow,right_arrow,enter");
+        colorParmas.put("绿色","alt,h,f,c,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,right_arrow,right_arrow,right_arrow,right_arrow,right_arrow,enter");
+        colorParmas.put("蓝色","alt,h,f,c,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,down_arrow,right_arrow,right_arrow,right_arrow,right_arrow,right_arrow,right_arrow,enter");
+
+        colorParmas.put("白色","alt,h,f,c,down_arrow,enter");
+        colorParmas.put("黑色","alt,h,f,c,right_arrow,enter");
+
+    }
+    public List<ScriptMessage> analyzeCommand(String command){
+        if("求和".equals(command)){
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_event.py");
+            String params = "alt,h,u,s";
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            return Arrays.asList(scriptMessage);
+
+        }else{
+            return analyzePPTCommand(command);
+        }
+    }
+
+    private List<ScriptMessage> analyzePPTCommand(String command){
+        String[] fontInfo =findFontSize(command);
+        String fontSize = fontInfo[0];
+        String fontSizeStr = fontInfo[1];
+
+        String color =findColor(command);
+        String italics =findItalics(command);
+        String bold =findBold(command);
+        String underline =findUnderline(command);
+
+        String fontCommand = command.replace(color==null?"":color,"")
+                .replace(italics==null?"":italics,"")
+                .replace(bold==null?"":bold,"")
+                .replace(underline==null?"":underline,"")
+                .replace(fontSizeStr==null?"":fontSizeStr,"");
+        Log.d(TAG,"fontCommand:"+fontCommand);
+
+        String font = findFont(fontCommand);
+
+        List<ScriptMessage> scriptMessages = new ArrayList<>();
+
+
+        Log.d(TAG,"****************PPT 命令消息******************");
+
+
+
+        if(color != null){
+            Log.d(TAG,"color:"+color);
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_click.py");
+            String params = colorParmas.get(color);
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+        }
+        if(italics != null){
+            Log.d(TAG,"italics:"+italics);
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_event.py");
+            String params = "ctrl,i";
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+        }
+        if(bold != null){
+            Log.d(TAG,"bold:"+bold);
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_event.py");
+            String params = "ctrl,b";
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+        }
+        if(underline != null){
+            Log.d(TAG,"underline:"+underline);
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_event.py");
+            String params = "ctrl,u";
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+        }
+
+        if(fontSize != null){
+            Log.d(TAG,"fontSize:"+fontSize);
+
+            //先选中字号
+            String script1 = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_click.py");
+            String params1 = "alt,h,f,s";
+            ScriptMessage scriptMessage1 = new ScriptMessage();
+            scriptMessage1.setScript(script1);
+            scriptMessage1.setParams(params1);
+            scriptMessages.add(scriptMessage1);
+
+            //再输入
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"text_input.py");
+            String params = fontSize;
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+
+            //最后回车
+            String script3 = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_click.py");
+            String params3 = "enter";
+            ScriptMessage scriptMessage3 = new ScriptMessage();
+            scriptMessage3.setScript(script3);
+            scriptMessage3.setParams(params3);
+            scriptMessages.add(scriptMessage3);
+        }
+
+        if(!TextUtils.isEmpty(font)){
+            Log.d(TAG,"font:"+font);
+            //先选中字体
+            String script1 = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_click.py");
+            String params1 = "alt,h,f,f";
+            ScriptMessage scriptMessage1 = new ScriptMessage();
+            scriptMessage1.setScript(script1);
+            scriptMessage1.setParams(params1);
+            scriptMessages.add(scriptMessage1);
+
+            //再输入
+            String script = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"text_input.py");
+            String params = font;
+            ScriptMessage scriptMessage = new ScriptMessage();
+            scriptMessage.setScript(script);
+            scriptMessage.setParams(params);
+            scriptMessages.add(scriptMessage);
+
+            //最后回车
+            String script3 = AssetsUtils.loadCommandScripts(CyberApplication.getContext(),"key_click.py");
+            String params3 = "enter";
+            ScriptMessage scriptMessage3 = new ScriptMessage();
+            scriptMessage3.setScript(script3);
+            scriptMessage3.setParams(params3);
+            scriptMessages.add(scriptMessage3);
+        }
+        Log.d(TAG,"***********************************");
+
+
+
+        return scriptMessages;
+    }
+
+    private String[] findFontSize(String command){
+        Matcher matcher = Pattern.compile("\\d+(号|好|后)").matcher(command);
+
+        String fontSizeStr = null;
+        String fontSize = null;
+        if (matcher.find( )) {
+            fontSizeStr = matcher.group();
+            fontSize = fontSizeStr.replaceAll("(号|好|后)","");
+        }
+        return new String[]{fontSize,fontSizeStr};
+    }
+
+    private String findColor(String command){
+        Matcher matcher = Pattern.compile(".{1}色").matcher(command);
+        String color = null;
+        if (matcher.find( )) {
+            color = matcher.group();
+        }
+        return color;
+    }
+
+    private String findItalics(String command){
+        Matcher matcher = Pattern.compile("斜体").matcher(command);
+        String italics = null;
+        if (matcher.find( )) {
+            italics = matcher.group();
+        }
+        return italics;
+    }
+
+    private String findBold(String command){
+        Matcher matcher = Pattern.compile("加粗|加出").matcher(command);
+        String bold = null;
+        if (matcher.find( )) {
+            bold = matcher.group();
+        }
+        return bold;
+    }
+
+    private String findUnderline(String command){
+        Matcher matcher = Pattern.compile("下.{1}线").matcher(command);
+        String underline = null;
+        if (matcher.find( )) {
+            underline = matcher.group();
+        }
+        return underline;
+    }
+
+    private String findFont(String fontCommand){
+        if(TextUtils.isEmpty(fontCommand)){
+            return null;
+        }
+        for(String font:fonts){
+            if(font.contains(fontCommand)){
+                return font;
+            }
+        }
+        return null;
+    }
+
+    /*
+     * 只cover了常见字体,更完整的做法是让电脑把所有字体下发过来,不想弄了。。。
+     */
+    private static String[] fonts = new String[]{
+            //方正字体
+            "方正榜书楷简体",
+            "方正粗圆简体",
+            "方正大黑简体",
+            "方正大雅宋简体",
+            "方正工业黑简体",
+            "方正姚体",
+            "方正兰亭黑简体",
+            //widows自带
+            "宋体",
+            "仿宋",
+            "新宋体",
+            "幼圆",
+            "楷体",
+            "隶书",
+            "黑体",
+            "微软雅黑",
+            //华文字体
+            "华文琥珀",
+            "华文新魏",
+            "华文行楷",
+            "华文隶书",
+            "华文彩云",
+            "华文宋体",
+            "华文细黑",
+            "华文楷体",
+            "华文中宋",
+            "华文仿宋"
+
+    };
+}

+ 53 - 0
app/src/main/java/com/elexlab/cybercontroller/services/FaceAnalyzer.java

@@ -0,0 +1,53 @@
+package com.elexlab.cybercontroller.services;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.huawei.hmf.tasks.OnFailureListener;
+import com.huawei.hmf.tasks.OnSuccessListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.mlsdk.MLAnalyzerFactory;
+import com.huawei.hms.mlsdk.common.MLFrame;
+import com.huawei.hms.mlsdk.face.MLFace;
+import com.huawei.hms.mlsdk.face.MLFaceAnalyzer;
+
+import java.util.List;
+
+public class FaceAnalyzer {
+    private final static String TAG = FaceAnalyzer.class.getSimpleName();
+
+    private MLFaceAnalyzer analyzer;
+
+    public FaceAnalyzer() {
+        init();
+    }
+
+    private void init(){
+
+        // 方式二:使用默认参数配置,集成Lite SDK时可以使用此配置方式。默认参数为:检测关键点、检测人脸轮廓、检测特征点、精度偏好模式、不开启人脸追踪。
+        analyzer = MLAnalyzerFactory.getInstance().getFaceAnalyzer();
+    }
+
+    public void detectFace(Bitmap bitmap){
+        MLFrame frame = MLFrame.fromBitmap(bitmap);
+        Task<List<MLFace>> task = analyzer.asyncAnalyseFrame(frame);
+        task.addOnSuccessListener(new OnSuccessListener<List<MLFace>>() {
+            @Override
+            public void onSuccess(List<MLFace> faces) {
+                // 检测成功。
+                if(faces !=null && faces.size()>0){
+                    Log.d(TAG,"有脸:"+faces.size());
+                }else{
+                    Log.d(TAG,"无脸");
+
+                }
+            }
+        }).addOnFailureListener(new OnFailureListener() {
+            @Override
+            public void onFailure(Exception e) {
+                // 检测失败。
+                e.printStackTrace();
+            }
+        });
+    }
+}

+ 89 - 0
app/src/main/java/com/elexlab/cybercontroller/services/FaceRecognizer.java

@@ -0,0 +1,89 @@
+package com.elexlab.cybercontroller.services;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.core.app.ActivityCompat;
+
+import com.elexlab.cybercontroller.ui.activities.LoginActivity;
+import com.elexlab.cybercontroller.utils.AssetsUtils;
+import com.huawei.hmf.tasks.OnSuccessListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.mlsdk.common.MLFrame;
+import com.huawei.hms.mlsdk.faceverify.MLFaceTemplateResult;
+import com.huawei.hms.mlsdk.faceverify.MLFaceVerificationAnalyzer;
+import com.huawei.hms.mlsdk.faceverify.MLFaceVerificationAnalyzerFactory;
+import com.huawei.hms.mlsdk.faceverify.MLFaceVerificationResult;
+import com.huawei.hms.mlsdk.livenessdetection.MLLivenessCapture;
+import com.huawei.hms.mlsdk.livenessdetection.MLLivenessCaptureResult;
+
+import java.util.List;
+
+public class FaceRecognizer {
+
+    public interface Callback{
+        void onSuccess(Bitmap pic);
+        void onFailure();
+    }
+    private final static String TAG = FaceRecognizer.class.getSimpleName();
+    private static final String[] PERMISSIONS = {Manifest.permission.CAMERA};
+
+    private static final int RC_CAMERA_AND_EXTERNAL_STORAGE = 0x01 << 8;
+
+    private Activity activity;
+    private MLFaceVerificationAnalyzer analyzer;
+
+
+    public FaceRecognizer(Activity activity) {
+        this.activity = activity;
+        initAnalyzer();
+    }
+
+    private void initAnalyzer(){
+        analyzer = MLFaceVerificationAnalyzerFactory.getInstance().getFaceVerificationAnalyzer();
+    }
+
+    private void setTempFace(Bitmap templateBitmap){
+        // 通过bitmap创建MLFrame
+        MLFrame templateFrame = MLFrame.fromBitmap(templateBitmap);
+        List<MLFaceTemplateResult> results = analyzer.setTemplateFace(templateFrame);
+    }
+
+    public void compareFace(Bitmap compareBitmap, Bitmap templateBitmap, Callback callback){
+        setTempFace(templateBitmap);
+        Task<List<MLFaceVerificationResult>> task
+                = analyzer.asyncAnalyseFrame(MLFrame.fromBitmap(compareBitmap));
+        task.addOnSuccessListener(new OnSuccessListener<List<MLFaceVerificationResult>>() {
+            @Override
+            public void onSuccess(List<MLFaceVerificationResult> mlFaceVerificationResults) {
+                for (MLFaceVerificationResult template : mlFaceVerificationResults) {
+                    Rect location = template.getFaceInfo().getFaceRect();
+                    float similarity = template.getSimilarity();
+                    Log.d(TAG,"face similarity:"+similarity);
+                    if(similarity>0.8){
+                        if(callback !=null){
+                            callback.onSuccess(null);
+                        }
+                    }else{
+                        if(callback !=null){
+                            callback.onFailure();
+                        }
+                    }
+
+                }
+            }
+        });
+    }
+
+
+
+
+}

+ 81 - 0
app/src/main/java/com/elexlab/cybercontroller/services/HideCamera.java

@@ -0,0 +1,81 @@
+package com.elexlab.cybercontroller.services;
+
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraDevice;
+import android.opengl.GLES11Ext;
+import android.view.SurfaceView;
+
+import java.io.IOException;
+
+public class HideCamera {
+    private Camera camera;
+    private SurfaceTexture surfaceTexture;
+    public HideCamera() {
+        init();
+    }
+
+    public interface Callback{
+        void onPictureTook(Bitmap bitmap);
+    }
+
+    private void init(){
+        camera = Camera.open(findFontCameraId());
+        Camera.Parameters parameters = camera.getParameters();
+        parameters.setPictureFormat(PixelFormat.JPEG);
+
+        parameters.setPreviewSize(320, 240);
+
+        parameters.setPictureSize(1920, 1080);
+
+        camera.setParameters(parameters);
+
+        surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+        try {
+            camera.setPreviewTexture(surfaceTexture);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        camera.startPreview();
+
+
+    }
+
+    public void takePicture(Callback callback){
+        camera.takePicture(null,null, new Camera.PictureCallback() {
+            @Override
+            public void onPictureTaken(byte[] data, Camera camera) {
+                Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
+                if(callback != null){
+                    callback.onPictureTook(bitmap);
+                }
+            }
+        });
+    }
+
+    public void closeCamera() {
+        camera.stopPreview();
+        camera.setPreviewCallbackWithBuffer(null);
+        camera.release();
+        camera = null;
+        surfaceTexture.release();
+    }
+
+    private int findFontCameraId(){
+        int numberOfCameras = Camera.getNumberOfCameras();// 获取摄像头个数
+        //遍历摄像头信息
+        for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) {
+            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+            Camera.getCameraInfo(cameraId, cameraInfo);
+            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//前置摄像头
+               return cameraId;
+            }
+        }
+        return 0;
+    }
+}

+ 119 - 0
app/src/main/java/com/elexlab/cybercontroller/services/SpeechRecognizer.java

@@ -0,0 +1,119 @@
+package com.elexlab.cybercontroller.services;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.huawei.hms.mlsdk.asr.MLAsrConstants;
+import com.huawei.hms.mlsdk.asr.MLAsrListener;
+import com.huawei.hms.mlsdk.asr.MLAsrRecognizer;
+
+public class SpeechRecognizer {
+    private final static String TAG = SpeechRecognizer.class.getSimpleName();
+    public interface Listener{
+        void onRecognizingResults(String result);
+        void onResults(String result);
+    }
+
+    private Context context;
+    private MLAsrRecognizer mSpeechRecognizer;
+    private String currentContent="";
+    private Listener listener;
+    public SpeechRecognizer(Context context) {
+        this.context = context;
+        init();
+    }
+
+
+    public String getCurrentContent() {
+        return currentContent;
+    }
+
+    private void init(){
+        mSpeechRecognizer = MLAsrRecognizer.createAsrRecognizer(context);
+        mSpeechRecognizer.setAsrListener(new SpeechRecognitionListener());
+    }
+
+    // 回调实现MLAsrListener接口,实现接口中的方法。
+    protected class SpeechRecognitionListener implements MLAsrListener {
+        @Override
+        public void onStartListening() {
+            // 录音器开始接收声音。
+        }
+
+        @Override
+        public void onStartingOfSpeech() {
+            // 用户开始讲话,即语音识别器检测到用户开始讲话。
+        }
+
+        @Override
+        public void onVoiceDataReceived(byte[] data, float energy, Bundle bundle) {
+            // 返回给用户原始的PCM音频流和音频能量,该接口并非运行在主线程中,返回结果需要在子线程中处理。
+        }
+
+        @Override
+        public void onRecognizingResults(Bundle partialResults) {
+            // 从MLAsrRecognizer接收到持续语音识别的文本,该接口并非运行在主线程中,返回结果需要在子线程中处理。
+            String transResult = partialResults.getString(MLAsrRecognizer.RESULTS_RECOGNIZING);
+            Log.d(TAG, "onRecognizingResults is " + transResult);
+            currentContent = transResult;
+            if(listener != null){
+                listener.onRecognizingResults(transResult);
+            }
+
+
+        }
+
+        @Override
+        public void onResults(Bundle results) {
+            // 语音识别的文本数据,该接口并非运行在主线程中,返回结果需要在子线程中处理。
+            String transResult = results.getString(MLAsrRecognizer.RESULTS_RECOGNIZED);
+            Log.d(TAG, "onResults is " + transResult);
+            currentContent = transResult;
+            if(listener != null){
+                listener.onResults(transResult);
+            }
+
+        }
+
+        @Override
+        public void onError(int error, String errorMessage) {
+            // 识别发生错误后调用该接口,该接口并非运行在主线程中,返回结果需要在子线程中处理。
+        }
+
+        @Override
+        public void onState(int state, Bundle params) {
+            // 通知应用状态发生改变,该接口并非运行在主线程中,返回结果需要在子线程中处理。
+        }
+    }
+
+    public void startRec(Listener listener){
+        this.listener = listener;
+        // 新建Intent,用于配置语音识别参数。
+        Intent mSpeechRecognizerIntent = new Intent(MLAsrConstants.ACTION_HMS_ASR_SPEECH);
+        // 通过Intent进行语音识别参数设置。
+        mSpeechRecognizerIntent
+                // 设置识别语言为英语,若不设置,则默认识别英语。支持设置:"zh-CN":中文;"en-US":英语;"fr-FR":法语;"es-ES":西班牙语;"de-DE":德语;"it-IT":意大利语;"ar": 阿拉伯语;"ru-RU":俄语;“th_TH”:泰语;“ms_MY”:马来语;“fil_PH”:菲律宾语。
+                .putExtra(MLAsrConstants.LANGUAGE, "zh-CN")
+                // 设置识别文本返回模式为边识别边出字,若不设置,默认为边识别边出字。支持设置:
+                // MLAsrConstants.FEATURE_WORDFLUX:通过onRecognizingResults接口,识别同时返回文字;
+                // MLAsrConstants.FEATURE_ALLINONE:识别完成后通过onResults接口返回文字。
+                .putExtra(MLAsrConstants.FEATURE, MLAsrConstants.FEATURE_WORDFLUX)
+                // 设置使用场景,MLAsrConstants.SCENES_SHOPPING:表示购物,仅支持中文,该场景对华为商品名识别进行了优化。
+                .putExtra(MLAsrConstants.SCENES, MLAsrConstants.SCENES_SHOPPING);
+        // 启动语音识别。
+        mSpeechRecognizer.startRecognizing(mSpeechRecognizerIntent);
+
+    }
+
+    public void refresh(){
+        this.currentContent = "";
+    }
+
+    public void destroy(){
+        if (mSpeechRecognizer!= null) {
+            mSpeechRecognizer.destroy();
+        }
+    }
+}

+ 58 - 0
app/src/main/java/com/elexlab/cybercontroller/services/TextRecognizer.java

@@ -0,0 +1,58 @@
+package com.elexlab.cybercontroller.services;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.huawei.hmf.tasks.OnFailureListener;
+import com.huawei.hmf.tasks.OnSuccessListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.mlsdk.MLAnalyzerFactory;
+import com.huawei.hms.mlsdk.common.MLFrame;
+import com.huawei.hms.mlsdk.text.MLLocalTextSetting;
+import com.huawei.hms.mlsdk.text.MLText;
+import com.huawei.hms.mlsdk.text.MLTextAnalyzer;
+
+public class TextRecognizer {
+    private final static String TAG = TextRecognizer.class.getSimpleName();
+    private MLTextAnalyzer analyzer;
+
+    public interface Callback{
+        void onRecognized(String text);
+    }
+    private Callback callback;
+
+    public TextRecognizer setCallback(Callback callback) {
+        this.callback = callback;
+        return this;
+    }
+
+    public TextRecognizer() {
+        init();
+    }
+
+    private void init(){
+        analyzer = MLAnalyzerFactory.getInstance().getRemoteTextAnalyzer();
+    }
+    public void recognize(Bitmap bitmap){
+        MLFrame frame = MLFrame.fromBitmap(bitmap);
+        Task<MLText> task = analyzer.asyncAnalyseFrame(frame);
+        task.addOnSuccessListener(new OnSuccessListener<MLText>() {
+            @Override
+            public void onSuccess(MLText text) {
+                // 识别成功处理。
+                Log.d(TAG,text.getStringValue());
+                if(callback != null){
+                    callback.onRecognized(text.getStringValue());
+                }
+            }
+        }).addOnFailureListener(new OnFailureListener() {
+            @Override
+            public void onFailure(Exception e) {
+                // 识别失败处理。
+                e.printStackTrace();
+                Log.e(TAG,"error");
+
+            }
+        });
+    }
+}

+ 56 - 0
app/src/main/java/com/elexlab/cybercontroller/services/Translator.java

@@ -0,0 +1,56 @@
+package com.elexlab.cybercontroller.services;
+
+import com.huawei.hmf.tasks.OnFailureListener;
+import com.huawei.hmf.tasks.OnSuccessListener;
+import com.huawei.hmf.tasks.Task;
+import com.huawei.hms.mlsdk.common.MLApplication;
+import com.huawei.hms.mlsdk.common.MLException;
+import com.huawei.hms.mlsdk.translate.MLTranslatorFactory;
+import com.huawei.hms.mlsdk.translate.cloud.MLRemoteTranslateSetting;
+import com.huawei.hms.mlsdk.translate.cloud.MLRemoteTranslator;
+
+public class Translator {
+    private MLRemoteTranslator mlRemoteTranslator;
+    public Translator(){
+        init();
+    }
+    private void init(){
+        // 使用自定义的参数配置创建文本翻译器。
+        MLRemoteTranslateSetting setting = new MLRemoteTranslateSetting
+                .Factory()
+                // 设置源语言的编码,使用ISO 639-1标准(中文繁体使用BCP-47标准)。此设置为可选项,如果不设置,将自动检测语种进行翻译。
+                .setSourceLangCode("en")
+                // 设置目标语言的编码,使用ISO 639-1标准(中文繁体使用BCP-47标准)。
+                .setTargetLangCode("zh")
+                .create();
+        mlRemoteTranslator = MLTranslatorFactory.getInstance().getRemoteTranslator(setting);
+    }
+
+    public void translate(String sourceText,OnSuccessListener<String> onSuccessListener){
+        final Task<String> task = mlRemoteTranslator.asyncTranslate(sourceText);
+        task.addOnSuccessListener(new OnSuccessListener<String>() {
+            @Override
+            public void onSuccess(String text) {
+                // 识别成功的处理逻辑。
+                if(onSuccessListener != null){
+                    onSuccessListener.onSuccess(text);
+                }
+            }
+        }).addOnFailureListener(new OnFailureListener() {
+            @Override
+            public void onFailure(Exception e) {
+                e.printStackTrace();
+                // 识别失败的处理逻辑。
+                try {
+                    MLException mlException = (MLException)e;
+                    // 获取错误码,开发者可以对错误码进行处理,根据错误码进行差异化的页面提示。
+                    int errorCode = mlException.getErrCode();
+                    // 获取报错信息,开发者可以结合错误码,快速定位问题。
+                    String errorMessage = mlException.getMessage();
+                } catch (Exception error) {
+                    // 转换错误处理。
+                }
+            }
+        });
+    }
+}

+ 250 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/activities/LoginActivity.java

@@ -0,0 +1,250 @@
+package com.elexlab.cybercontroller.ui.activities;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.elexlab.cybercontroller.MainActivity;
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.communication.BluetoothClient;
+import com.elexlab.cybercontroller.communication.BluetoothKeyboard;
+import com.elexlab.cybercontroller.services.FaceRecognizer;
+import com.elexlab.cybercontroller.ui.widget.AssetsAnimationImageView;
+import com.elexlab.cybercontroller.ui.widget.CoolDigitalClock;
+import com.elexlab.cybercontroller.utils.AssetsUtils;
+import com.elexlab.cybercontroller.utils.DeviceUtil;
+import com.elexlab.cybercontroller.utils.PermissionUtil;
+import com.huawei.hms.mlsdk.livenessdetection.MLLivenessCaptureResult;
+import com.huawei.hms.mlsdk.livenessdetection.MLLivenessDetectView;
+import com.huawei.hms.mlsdk.livenessdetection.OnMLLivenessDetectCallback;
+
+import java.sql.Time;
+
+import static com.huawei.hms.mlsdk.livenessdetection.MLLivenessDetectView.DETECT_MASK;
+
+public class LoginActivity extends Activity {
+
+    private BluetoothKeyboard bluetoothKeyboard;
+    private MLLivenessDetectView mlLivenessDetectView;
+    private FrameLayout mPreviewContainer;
+    private View rlScanFace;
+    private TextView tvInfo;
+    private View llMain;
+    private View llSleep;
+    private CoolDigitalClock digitalClock;
+    FaceRecognizer faceRecognizer;
+    AssetsAnimationImageView tvImageView;
+
+    public static void startMe(Activity activity, boolean directUnlock){
+        Intent intent = new Intent(activity, LoginActivity.class);
+        intent.putExtra("directUnlock",directUnlock);
+        activity.startActivity(intent);
+        activity.overridePendingTransition(R.anim.login_transition, R.anim.login_transition);
+        DeviceUtil.acquireWakeLock(activity,1000);
+    }
+
+
+    private Handler handler = new Handler();
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        PermissionUtil.checkAndRequestPermissions(this,new String[]{Manifest.permission.CAMERA});
+        setContentView(R.layout.activity_login);
+        mPreviewContainer = findViewById(R.id.surface_layout);
+        rlScanFace = findViewById(R.id.rlScanFace);
+        tvInfo = findViewById(R.id.tvInfo);
+        llMain = findViewById(R.id.llMain);
+        digitalClock = findViewById(R.id.digitalClock);
+        llSleep = findViewById(R.id.llSleep);
+
+        bluetoothKeyboard = new BluetoothKeyboard(this);
+        faceRecognizer = new FaceRecognizer(this);
+
+
+        //initLiveDetector(savedInstanceState);
+
+        tvImageView = findViewById(R.id.tvImageView);
+
+        tvImageView.setImages("tv");
+        tvImageView.setDeltaTime(50);
+        boolean directUnlock = getIntent().getBooleanExtra("directUnlock",false);
+
+        llMain.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                finish();
+                startMe(LoginActivity.this,true);
+            }
+        });
+        if(directUnlock){
+            startDetector(savedInstanceState);
+        }
+
+        BluetoothClient.getInstance().active();
+
+    }
+
+    private void startDetector(Bundle savedInstanceState){
+        digitalClock.setVisibility(View.INVISIBLE);
+        rlScanFace.setVisibility(View.VISIBLE);
+        llSleep.setVisibility(View.INVISIBLE);
+        tvImageView.start();
+
+        //Obtain MLLivenessDetectView
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+        int widthPixels = outMetrics.widthPixels;
+
+        mlLivenessDetectView = new MLLivenessDetectView.Builder()
+                .setContext(this)
+                //.setOptions(DETECT_MASK)
+                // set Rect of face frame relative to surface in layout
+                .setFaceFrameRect(new Rect(0, 0, widthPixels,dip2px(this,360) ))
+                .setDetectCallback(new OnMLLivenessDetectCallback() {
+                    @Override
+                    public void onCompleted(MLLivenessCaptureResult result) {
+                        if(result.isLive()){
+                            compareFace(result.getBitmap());
+                        }else{
+                            onRecFailure();
+                        }
+                    }
+
+                    @Override
+                    public void onError(int error) {
+                    }
+
+                    public void onInfo(int infoCode, Bundle bundle) {
+
+                    }
+
+                    @Override
+                    public void onStateChange(int state, Bundle bundle) {
+
+                    }
+                }).build();
+
+        mPreviewContainer.addView(mlLivenessDetectView);
+        mlLivenessDetectView.onCreate(savedInstanceState);
+    }
+
+
+
+    private void compareFace(Bitmap compareBitmap){
+        Bitmap templateBitmap = AssetsUtils.getImageFromAssetsFile(this,"admin.jpg");
+        faceRecognizer.compareFace(compareBitmap, templateBitmap, new FaceRecognizer.Callback() {
+            @Override
+            public void onSuccess(Bitmap pic) {
+                unLock();
+                onRecSuccess();
+
+            }
+
+            @Override
+            public void onFailure() {
+                onRecFailure();
+
+            }
+        });
+    }
+
+    private void onRecSuccess(){
+        rlScanFace.setVisibility(View.GONE);
+        tvImageView.stopAnim();
+        tvImageView.setImageBitmap(AssetsUtils.getImageFromAssetsFile(this,"tv_happy.png"));
+        tvInfo.setVisibility(View.VISIBLE);
+        tvInfo.setText("欢迎回来~");
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+                LoginActivity.this.overridePendingTransition(android.R.anim.fade_in,android.R.anim.fade_out);
+            }
+        },1000);
+
+    }
+
+    private void onRecFailure(){
+        rlScanFace.setVisibility(View.GONE);
+        tvInfo.setVisibility(View.VISIBLE);
+        tvInfo.setText("你不对劲!");
+    }
+
+    private void unLock(){
+        bluetoothKeyboard.sendKey("ESC");
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                bluetoothKeyboard.sendKey("1");
+                bluetoothKeyboard.sendKey("q");
+                bluetoothKeyboard.sendKey("a");
+                bluetoothKeyboard.sendKey("z");
+                bluetoothKeyboard.sendKey("@");
+                bluetoothKeyboard.sendKey("W");
+                bluetoothKeyboard.sendKey("S");
+                bluetoothKeyboard.sendKey("X");
+                bluetoothKeyboard.sendKey("Enter");
+            }
+        },500);
+
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if(mlLivenessDetectView != null){
+            mlLivenessDetectView.onDestroy();
+        }
+        digitalClock.destroy();
+
+    }
+
+    public static int dip2px(Context context, float dpValue) {
+        float scale = context.getResources().getDisplayMetrics().density;
+        return (int) (dpValue * scale + 0.5f);
+    }
+
+
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if(mlLivenessDetectView != null){
+            mlLivenessDetectView.onPause();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if(mlLivenessDetectView != null){
+            mlLivenessDetectView.onResume();
+        }
+    }
+
+
+}

+ 51 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/activities/TestActivity.java

@@ -0,0 +1,51 @@
+package com.elexlab.cybercontroller.ui.activities;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.communication.BluetoothKeyboard;
+import com.elexlab.cybercontroller.services.Translator;
+import com.elexlab.cybercontroller.utils.PermissionUtil;
+
+public class TestActivity extends Activity {
+    private BluetoothKeyboard bluetoothKeyboard;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        PermissionUtil.checkAndRequestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO});
+
+        setContentView(R.layout.activity_test);
+        View btTest = findViewById(R.id.btTest);
+        bluetoothKeyboard = new BluetoothKeyboard(this);
+
+        btTest.setOnClickListener((View view)->{
+            unLock();
+
+        });
+
+    }
+
+    private void unLock(){
+        bluetoothKeyboard.sendKey("ESC");
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                bluetoothKeyboard.sendKey("1");
+                bluetoothKeyboard.sendKey("q");
+                bluetoothKeyboard.sendKey("a");
+                bluetoothKeyboard.sendKey("z");
+                bluetoothKeyboard.sendKey("@");
+                bluetoothKeyboard.sendKey("W");
+                bluetoothKeyboard.sendKey("S");
+                bluetoothKeyboard.sendKey("X");
+                bluetoothKeyboard.sendKey("Enter");
+            }
+        },500);
+
+    }
+}

+ 76 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/AssetsAnimationImageView.java

@@ -0,0 +1,76 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.utils.AssetsUtils;
+
+import java.util.List;
+
+public class AssetsAnimationImageView extends ImageView {
+    private final static String TAG = AssetsAnimationImageView.class.getSimpleName();
+    public AssetsAnimationImageView(Context context) {
+        super(context);
+    }
+
+    public AssetsAnimationImageView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AssetsAnimationImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AssetsAnimationImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private Handler handler = new Handler();
+    private String path;
+    private List<String> animImgs;
+    private int index=0;
+    private int step=1;
+    private int deltaTime = 100;
+    public void setImages(String path){
+        this.path = path;
+        animImgs = AssetsUtils.listFiles(getContext(),path);
+
+    }
+    public void setDeltaTime(int deltaTime){
+        this.deltaTime = deltaTime;
+    }
+
+    private Runnable anim = new Runnable() {
+        @Override
+        public void run() {
+
+            Bitmap bitmap = AssetsUtils.getImageFromAssetsFile(getContext(),path+"/"+animImgs.get(index));
+            setImageBitmap(bitmap);
+
+            if((index==animImgs.size()-1&&step>0) || (index==0&&step<0)){
+                step=-step;
+            }
+            index+=step;
+
+            handler.postDelayed(this,deltaTime);
+        }
+    };
+
+    public void start(){
+        if(animImgs == null || animImgs.size()<=0){
+            Log.e(TAG,"no animImgs setted!");
+            return;
+        }
+        handler.post(anim);
+    }
+    public void stopAnim(){
+        handler.removeCallbacks(anim);
+    }
+}

+ 123 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/CoolDigitalClock.java

@@ -0,0 +1,123 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.services.ClockService;
+
+import java.util.Date;
+
+/**
+ * Created by BruceYoung on 16/2/3.
+ */
+public class CoolDigitalClock extends RelativeLayout {
+
+    private Context mContext;
+    private ClockService clockService;
+    public CoolDigitalClock(Context context) {
+        super(context);
+        mContext = context;
+        initView();
+    }
+
+    public CoolDigitalClock(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContext = context;
+        initView();
+    }
+
+    public CoolDigitalClock(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        initView();
+    }
+
+
+    private TextView tvBreaker;
+    private SevenPartDigitalTube timeHourHigh;
+    private SevenPartDigitalTube timeHourLow;
+    private SevenPartDigitalTube timeMinHigh;
+    private SevenPartDigitalTube timeMinLow;
+
+    private Handler handler = new Handler();
+
+
+    public void destroy(){
+        clockService.destroy();
+    }
+    private void initView(){
+        View view = LayoutInflater.from(mContext).inflate(R.layout.cool_digital_clock_layout,null);
+        tvBreaker = (TextView) view.findViewById(R.id.tvBreaker);
+        timeHourHigh = (SevenPartDigitalTube) view.findViewById(R.id.timeHourHigh);
+        timeHourLow = (SevenPartDigitalTube) view.findViewById(R.id.timeHourLow);
+        timeMinHigh = (SevenPartDigitalTube) view.findViewById(R.id.timeMinHigh);
+        timeMinLow = (SevenPartDigitalTube) view.findViewById(R.id.timeMinLow);
+        tvBreaker.setTextColor(0xffffffff);
+        addView(view);
+        setTimeToView();
+
+        clockService = new ClockService(mContext);
+        clockService.registClockObserver(new ClockService.ClockObserverInterf() {
+            @Override
+            public boolean dida(ClockService.Dida dida) {
+                if(dida!=null&&dida.isNewHour()||dida.isNewMin()){
+                    handler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            setTimeToView();
+                        }
+                    });
+                }
+                return true;
+            }
+        });
+
+        didaAnim();
+
+    }
+
+    private void didaAnim(){
+        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f,0.0f);
+        alphaAnimation.setDuration(500);
+        alphaAnimation.setRepeatCount(Animation.INFINITE);
+        alphaAnimation.setRepeatMode(Animation.REVERSE);
+        tvBreaker.setAnimation(alphaAnimation);
+        alphaAnimation.start();
+
+    }
+
+    private void setTimeToView(){
+        Date date = new Date();
+        int hour = date.getHours();
+        int min = date.getMinutes();
+
+        int timeHourHighValue = hour / 10;
+        int timeHourLowValue = hour % 10;
+        int timeMinHighValue = min / 10;
+        int timeMinLowValue = min % 10;
+
+        if(timeHourHigh.getValue()!=timeHourHighValue){
+            timeHourHigh.setValue(timeHourHighValue);
+        }
+        if(timeHourLowValue!=timeHourLow.getValue()){
+            timeHourLow.setValue(timeHourLowValue);
+        }
+
+        if(timeMinHighValue!=timeMinHigh.getValue()){
+            timeMinHigh.setValue(timeMinHighValue);
+        }
+        if(timeMinLowValue!=timeMinLow.getValue()){
+            timeMinLow.setValue(min % 10);
+        }
+    }
+}

+ 67 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/InfoBoxView.java

@@ -0,0 +1,67 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.text.method.ScrollingMovementMethod;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.elexlab.cybercontroller.R;
+
+public class InfoBoxView extends RelativeLayout {
+    public InfoBoxView(Context context) {
+        super(context);
+        initView();
+    }
+
+    public InfoBoxView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView();
+    }
+
+    public InfoBoxView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView();
+    }
+
+    public InfoBoxView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView();
+    }
+    private TextView tvTitle;
+    private TextView tvInfo;
+    private void initView(){
+        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_info_box_layout,null);
+        tvTitle = view.findViewById(R.id.tvTitle);
+        tvInfo = view.findViewById(R.id.tvInfo);
+        tvInfo.setMovementMethod(ScrollingMovementMethod.getInstance());
+        addView(view);
+    }
+
+    public void setTitle(String content){
+        this.tvTitle.setText(content);
+    }
+
+    public void setInfo(String content){
+        this.tvInfo.setText(content);
+    }
+
+    public void show(int animTime){
+        InfoBoxView.this.setVisibility(VISIBLE);
+        AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
+        alphaAnimation.setDuration(animTime);
+        this.setAnimation(alphaAnimation);
+        alphaAnimation.start();
+    }
+    public void dismiss(int animTime){
+        InfoBoxView.this.setVisibility(INVISIBLE);
+        AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
+        alphaAnimation.setDuration(animTime);
+        this.setAnimation(alphaAnimation);
+        alphaAnimation.start();
+    }
+}

+ 313 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/SevenPartDigitalTube.java

@@ -0,0 +1,313 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class SevenPartDigitalTube extends View {
+    private int value = 8;
+    private Context mContext;
+
+    public SevenPartDigitalTube(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    public SevenPartDigitalTube(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+    }
+
+    public SevenPartDigitalTube(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContext = context;
+    }
+
+
+
+    public void setValue(int value){
+        this.value = value;
+        invalidate();
+    }
+
+    public int getValue(){
+        return this.value;
+    }
+
+
+
+
+    Paint p;
+
+    private int rectangleWidth = 20;
+    private int rectangleLength = 80;
+    @Override
+    protected void onDraw(Canvas canvas) {
+
+        rectangleWidth = getWidth()/6;
+        rectangleLength = getWidth()*4/6;
+
+
+        Log.d("width","width:"+getWidth());
+        Log.d("height","height:"+getHeight());
+        p = new Paint();
+        //p.setColor(0xff00a5fc);
+        //p.setColor(0xff00daff);
+        p.setColor(0xffffffff);
+        p.setStyle(Paint.Style.FILL);//设置填满
+
+
+        switch (value){
+            case 0:{
+                drawTopLine(canvas);
+                drawLeftTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawLeftButtomLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 1:{
+                drawRightButtomLine(canvas);
+                drawRightTopLine(canvas);
+            }
+            break;
+            case 2:{
+                drawTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawLeftButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 3:{
+                drawTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 4:{
+                drawLeftTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawRightButtomLine(canvas);
+            }
+            break;
+            case 5:{
+                drawTopLine(canvas);
+                drawLeftTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 6:{
+                drawTopLine(canvas);
+                drawLeftTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawLeftButtomLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 7:{
+                drawTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawRightButtomLine(canvas);
+            }
+            break;
+            case 8:{
+                drawTopLine(canvas);
+                drawLeftTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawLeftButtomLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            case 9:{
+                drawTopLine(canvas);
+                drawLeftTopLine(canvas);
+                drawRightTopLine(canvas);
+                drawMiddleLine(canvas);
+                drawRightButtomLine(canvas);
+                drawButtomLine(canvas);
+            }
+            break;
+            default:break;
+
+        }
+
+    }
+
+
+    private void drawTopLine(Canvas canvas){
+
+//        Paint p = new Paint();
+//        p.setColor(Color.BLUE);
+//        p.setStyle(Paint.Style.FILL);//设置填满
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(0, 0);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleWidth, 0);
+        pathLeftAngle.lineTo(rectangleWidth, rectangleWidth);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+
+        canvas.drawRect(rectangleWidth, 0, rectangleWidth+rectangleLength, rectangleWidth, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(rectangleWidth+rectangleLength, 0);// 此点为多边形的起点
+        pathRightAngle.lineTo(2*rectangleWidth+rectangleLength, 0);
+        pathRightAngle.lineTo(rectangleWidth+rectangleLength, rectangleWidth);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+    }
+
+
+
+    public void drawLeftTopLine(Canvas canvas){
+//        Paint p = new Paint();
+//        p.setColor(Color.BLUE);
+//        p.setStyle(Paint.Style.FILL);//设置填满
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(0, 5);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleWidth,rectangleWidth+5);
+        pathLeftAngle.lineTo(0, rectangleWidth+5);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+
+        canvas.drawRect(0, rectangleWidth+5, rectangleWidth, rectangleWidth+rectangleLength+5, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(0, rectangleWidth+rectangleLength+5);// 此点为多边形的起点
+        pathRightAngle.lineTo(rectangleWidth, rectangleWidth+rectangleLength+5);
+        pathRightAngle.lineTo(rectangleWidth/2, rectangleWidth+rectangleLength+rectangleWidth/2+5);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+
+    }
+
+    public void drawRightTopLine(Canvas canvas){
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(2*rectangleWidth+rectangleLength, 5);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleWidth+rectangleLength,rectangleWidth+5);
+        pathLeftAngle.lineTo(2*rectangleWidth+rectangleLength, rectangleWidth+5);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+        canvas.drawRect(rectangleWidth+rectangleLength, rectangleWidth+5, 2*rectangleWidth+rectangleLength, rectangleWidth+rectangleLength+5, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(rectangleWidth+rectangleLength, rectangleWidth+rectangleLength+5);// 此点为多边形的起点
+        pathRightAngle.lineTo(2 * rectangleWidth + rectangleLength, rectangleWidth + rectangleLength + 5);
+        pathRightAngle.lineTo(rectangleWidth+rectangleLength+rectangleWidth/2, rectangleWidth+rectangleLength+rectangleWidth/2+5);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+
+    }
+
+    public void drawMiddleLine(Canvas canvas){
+
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(rectangleWidth + 3, rectangleWidth + rectangleLength + 5 + 3);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleWidth/2+3,rectangleWidth+rectangleLength+rectangleWidth/2+5+3);
+        pathLeftAngle.lineTo(rectangleWidth + 3, 2 * rectangleWidth + rectangleLength + 5 + 3);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+        canvas.drawRect(rectangleWidth+3, rectangleWidth+rectangleLength+5+3, rectangleWidth+rectangleLength-3, 2*rectangleWidth+rectangleLength+5+3, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(rectangleWidth+rectangleLength-3, rectangleWidth + rectangleLength + 5 + 3);// 此点为多边形的起点
+        pathRightAngle.lineTo(rectangleWidth+rectangleLength+rectangleWidth/2-3, rectangleWidth+rectangleLength+rectangleWidth/2+5+3);
+        pathRightAngle.lineTo(rectangleWidth+rectangleLength-3, 2*rectangleWidth+rectangleLength+5+3);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+
+    }
+
+
+    private void drawLeftButtomLine(Canvas canvas){
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(rectangleWidth/2,5+5+rectangleWidth+rectangleWidth/2+rectangleLength);// 此点为多边形的起点
+        pathLeftAngle.lineTo(0,5+5+rectangleWidth*2+rectangleLength);
+        pathLeftAngle.lineTo(rectangleWidth, 5+5+rectangleWidth*2+rectangleLength);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+        canvas.drawRect(0, 5+5+rectangleWidth*2+rectangleLength,rectangleWidth, rectangleLength+5+5+rectangleWidth*2+rectangleLength, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(0, rectangleLength+(5+5+rectangleWidth*2+rectangleLength));// 此点为多边形的起点
+        pathRightAngle.lineTo(rectangleWidth, rectangleLength+(5+5+rectangleWidth*2+rectangleLength));
+        pathRightAngle.lineTo(0, rectangleLength+(5+5+rectangleWidth*2+rectangleLength)+rectangleWidth);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+    }
+
+    private void drawRightButtomLine(Canvas canvas){
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(rectangleWidth/2+rectangleLength+rectangleWidth,5+5+rectangleWidth+rectangleWidth/2+rectangleLength);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleLength+rectangleWidth,5+5+rectangleWidth*2+rectangleLength);
+        pathLeftAngle.lineTo(rectangleLength+2*rectangleWidth, 5+5+rectangleWidth*2+rectangleLength);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+        canvas.drawRect(rectangleLength+rectangleWidth, 5+5+rectangleWidth*2+rectangleLength,rectangleLength+2*rectangleWidth, rectangleLength+(5+5+rectangleWidth*2+rectangleLength), p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(rectangleLength+rectangleWidth, rectangleLength+(5+5+rectangleWidth*2+rectangleLength));// 此点为多边形的起点
+        pathRightAngle.lineTo(rectangleLength+2*rectangleWidth, rectangleLength+(5+5+rectangleWidth*2+rectangleLength));
+        pathRightAngle.lineTo(rectangleLength+2*rectangleWidth, rectangleLength+(5+5+rectangleWidth*2+rectangleLength)+rectangleWidth);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+    }
+
+    private void drawButtomLine(Canvas canvas){
+
+//        Paint p = new Paint();
+//        p.setColor(Color.BLUE);
+//        p.setStyle(Paint.Style.FILL);//设置填满
+
+        int buttomY = 5+rectangleWidth*2+rectangleLength*2+rectangleWidth+5+3;//233
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathLeftAngle = new Path();
+        pathLeftAngle.moveTo(3, buttomY);// 此点为多边形的起点
+        pathLeftAngle.lineTo(rectangleWidth+3, buttomY);
+        pathLeftAngle.lineTo(rectangleWidth+3, buttomY-rectangleWidth);
+        pathLeftAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathLeftAngle, p);
+
+
+        canvas.drawRect(rectangleWidth+3,  buttomY-rectangleWidth, rectangleLength+rectangleWidth-3, buttomY, p);// 长方形
+
+        // 绘制这个三角形,你可以绘制任意多边形
+        Path pathRightAngle = new Path();
+        pathRightAngle.moveTo(rectangleLength+rectangleWidth-3,buttomY-rectangleWidth);// 此点为多边形的起点
+        pathRightAngle.lineTo(rectangleLength+rectangleWidth-3, buttomY);
+        pathRightAngle.lineTo(rectangleLength+rectangleWidth*2-3, buttomY);
+        pathRightAngle.close(); // 使这些点构成封闭的多边形
+        canvas.drawPath(pathRightAngle, p);
+    }
+}

+ 144 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/SpeechRecordView.java

@@ -0,0 +1,144 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.text.method.ScrollingMovementMethod;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.services.SpeechRecognizer;
+
+public class SpeechRecordView extends RelativeLayout {
+
+    public interface  SpeechListener{
+        void onRecognizeStart(int mode);
+        void onRecognizingResults(int mode, String result);
+        void onResults(int mode,String result);
+        void onRecognizeEnd(int mode,String result);
+
+    }
+    private final static String TAG = SpeechRecordView.class.getSimpleName();
+    private final static long TOUCH_INTERVAL_TIME = 300;
+    public final static long AUTO_DISMISS_TIME = 30*1000;
+
+    private int mode=0;
+    public SpeechRecordView(Context context) {
+        super(context);
+        initView();
+    }
+
+    public SpeechRecordView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView();
+    }
+
+    public SpeechRecordView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView();
+    }
+
+    public SpeechRecordView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView();
+    }
+
+
+
+    private View rlMicrophone;
+    private SpeechRecognizer speechRecognizer;
+    private SpeechListener listener;
+
+    public SpeechRecordView setListener(SpeechListener listener) {
+        this.listener = listener;
+        return this;
+    }
+
+    private void initView(){
+        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_speech_record,null);
+        rlMicrophone = view.findViewById(R.id.rlMicrophone);
+        addView(view);
+        view.setOnTouchListener(onTouchListener);
+        speechRecognizer = new SpeechRecognizer(getContext());
+
+    }
+
+
+
+    private Handler handler = new Handler();
+
+    private Runnable  recognizer = new Runnable(){
+
+        @Override
+        public void run() {
+            Log.d(TAG, "mode:"+mode);
+            if(SpeechRecordView.this.listener != null){
+                SpeechRecordView.this.listener.onRecognizeStart(mode);
+            }
+            speechRecognizer.startRec(new SpeechRecognizer.Listener() {
+                @Override
+                public void onRecognizingResults(String result) {
+                    if(SpeechRecordView.this.listener != null){
+                        SpeechRecordView.this.listener.onRecognizingResults(mode, result);
+                    }
+                }
+
+                @Override
+                public void onResults(String result) {
+                    if(SpeechRecordView.this.listener != null){
+                        SpeechRecordView.this.listener.onResults(mode,result);
+                    }
+                    speechRecognizer.refresh();
+
+                }
+            });
+        }
+    };
+
+    private long lastTouchTime=0;
+
+    private long holdStartTime = 0;
+    private OnTouchListener onTouchListener = new OnTouchListener() {
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if(event.getAction()==MotionEvent.ACTION_DOWN) {
+                holdStartTime = System.currentTimeMillis();
+                if (System.currentTimeMillis() - lastTouchTime < TOUCH_INTERVAL_TIME) {
+                    mode = 2;
+                } else {
+                    mode = 1;
+                    handler.postDelayed(recognizer,TOUCH_INTERVAL_TIME);
+                }
+                lastTouchTime = System.currentTimeMillis();
+            }else if(event.getAction()==MotionEvent.ACTION_UP){
+                long holdTime = System.currentTimeMillis()-holdStartTime;
+                if(holdTime > TOUCH_INTERVAL_TIME){
+                    if(SpeechRecordView.this.listener != null){
+                        SpeechRecordView.this.listener.onRecognizeEnd(mode, speechRecognizer.getCurrentContent());
+                    }
+                }
+                mode = 0;
+
+            }
+            return true;
+        }
+    };
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if(speechRecognizer != null){
+            speechRecognizer.destroy();
+        }
+    }
+}

+ 90 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/TouchboardView.java

@@ -0,0 +1,90 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+public class TouchboardView extends View {
+    private final static String TAG = TouchboardView.class.getSimpleName();
+    public interface TouchCallback{
+        void onSwitchWindow(int direction);
+        void onWindowTab();
+
+
+    }
+    private TouchCallback touchCallback;
+
+    public TouchboardView setTouchCallback(TouchCallback touchCallback) {
+        this.touchCallback = touchCallback;
+        return this;
+    }
+
+    public TouchboardView(Context context) {
+        super(context);
+        init();
+    }
+
+    public TouchboardView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public TouchboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public TouchboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private float xPoint;
+    private float yPoint;
+
+    private void init(){
+        setOnTouchListener(new OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                Log.d(TAG,"PointerCount:"+event.getPointerCount());
+                Log.d(TAG,"Action:"+event.getAction());
+                if(MotionEvent.ACTION_DOWN == event.getAction()){
+                    xPoint = event.getX(0);
+                    yPoint = event.getY(0);
+                } else if(MotionEvent.ACTION_MOVE == event.getAction()){
+
+                }else if(MotionEvent.ACTION_POINTER_2_UP == event.getAction()
+                        || MotionEvent.ACTION_POINTER_UP == event.getAction()){//tow finger up
+                    float endXPoint =  event.getX(0);
+                    float endYPoint =  event.getY(0);
+
+                    if(endXPoint-xPoint>100){
+                        Log.d(TAG,"need left");
+                        if(touchCallback != null){
+                            touchCallback.onSwitchWindow(0);
+                        }
+                    }else if(xPoint-endXPoint>100){
+                        Log.d(TAG,"need right");
+                        if(touchCallback != null){
+                            touchCallback.onSwitchWindow(1);
+                        }
+
+                    }else if(Math.abs(yPoint-endYPoint)>100){
+                        Log.d(TAG,"need win tab");
+                        if(touchCallback != null){
+                            touchCallback.onWindowTab();
+                        }
+                    }
+
+
+                }
+
+                return true;
+            }
+        });
+    }
+}

+ 134 - 0
app/src/main/java/com/elexlab/cybercontroller/ui/widget/TranslationView.java

@@ -0,0 +1,134 @@
+package com.elexlab.cybercontroller.ui.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.text.method.ScrollingMovementMethod;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.elexlab.cybercontroller.R;
+import com.elexlab.cybercontroller.services.ClockService;
+
+public class TranslationView extends RelativeLayout {
+    private final static String TAG = TranslationView.class.getSimpleName();
+    private final static long CLICK_INTERVAL_TIME = 300;
+    public final static long AUTO_DISMISS_TIME = 30*1000;
+    public TranslationView(Context context) {
+        super(context);
+        initView();
+    }
+
+    public TranslationView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView();
+    }
+
+    public TranslationView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView();
+    }
+
+    public TranslationView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView();
+    }
+    private TextView tvSource;
+    private TextView tvResult;
+    private long lastClickTime=0;
+    private Handler handler = new Handler(){
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            Log.d(TAG,"handleMessage:"+msg.what);
+            switch (msg.what){
+                case 1:{
+                    dismiss(1000);
+                    break;
+                }
+                default:break;
+            }
+        }
+    };
+
+    private OnClickListener viewOnClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if(System.currentTimeMillis()-lastClickTime<CLICK_INTERVAL_TIME){
+                Log.d(TAG,"double click");
+                startDismiss(0);
+            }else{
+                Log.d(TAG,"single click");
+                startDismiss(AUTO_DISMISS_TIME);
+            }
+            lastClickTime = System.currentTimeMillis();
+        }
+    };
+    private void initView(){
+        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_translation_layout,null);
+        tvSource = view.findViewById(R.id.tvSource);
+        tvResult = view.findViewById(R.id.tvResult);
+        tvResult.setMovementMethod(ScrollingMovementMethod.getInstance());
+        addView(view);
+        view.setOnClickListener(viewOnClickListener);
+        tvSource.setOnClickListener(viewOnClickListener);
+        tvResult.setOnClickListener(viewOnClickListener);
+
+        tvResult.setOnTouchListener((View v, MotionEvent event)->{
+            startDismiss(AUTO_DISMISS_TIME);
+            return false;
+        });
+
+    }
+
+    public void setContent(String source,String result){
+        float textSize = 20;
+        float textLen = source.length();
+        textSize = 100-0.5f*textLen;
+        if(textSize<20){
+            textSize=20;
+        }
+        tvSource.setTextSize(textSize);
+        tvResult.setTextSize(textSize);
+
+        tvSource.setText(source);
+        tvResult.setText(result);
+        tvResult.scrollTo(0,0);
+        show(100);
+        startDismiss(AUTO_DISMISS_TIME);
+    }
+
+    /**
+     * 开启消失计时
+     */
+    private void startDismiss(long delayTime){
+        handler.removeMessages(1);
+        handler.sendEmptyMessageDelayed(1,delayTime);//dismiss after 30s
+    }
+
+    public void show(int animTime){
+        if(this.getVisibility() == VISIBLE){
+            return;
+        }
+        this.setVisibility(VISIBLE);
+        AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
+        alphaAnimation.setDuration(animTime);
+        this.setAnimation(alphaAnimation);
+        alphaAnimation.start();
+    }
+    public void dismiss(int animTime){
+        this.setVisibility(GONE);
+        AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
+        alphaAnimation.setDuration(animTime);
+        this.setAnimation(alphaAnimation);
+        alphaAnimation.start();
+    }
+}

+ 68 - 0
app/src/main/java/com/elexlab/cybercontroller/utils/AssetsUtils.java

@@ -0,0 +1,68 @@
+package com.elexlab.cybercontroller.utils;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Arrays;
+import java.util.List;
+
+public class AssetsUtils {
+    public static Bitmap getImageFromAssetsFile(Context context, String fileName) {
+        Bitmap image = null;
+        AssetManager am = context.getResources().getAssets();
+        try {
+            InputStream is = am.open(fileName);
+            image = BitmapFactory.decodeStream(is);
+            is.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return image;
+    }
+
+    public static List<String> listFiles(Context context,String dir){
+        AssetManager am = context.getResources().getAssets();
+        try {
+            return Arrays.asList(am.list(dir));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static String loadCommandScripts(Context context, String fileName) {
+        Bitmap image = null;
+        AssetManager am = context.getResources().getAssets();
+        BufferedReader bufferedReader = null;
+        String content = "";
+
+        try {
+            InputStreamReader inputStreamReader = new InputStreamReader(am.open("command_scripts"+ File.separator +fileName));
+            bufferedReader = new BufferedReader(inputStreamReader);
+            String line;
+            while((line=bufferedReader.readLine())!=null){
+                content+=line+"\n";
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }finally {
+            if(bufferedReader != null){
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return content;
+    }
+}

+ 32 - 0
app/src/main/java/com/elexlab/cybercontroller/utils/DeviceUtil.java

@@ -0,0 +1,32 @@
+package com.elexlab.cybercontroller.utils;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class DeviceUtil {
+    public static PowerManager.WakeLock acquireWakeLock(@NonNull Context context, long timeout) {
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        if (pm == null)
+            return null;
+        PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
+                        PowerManager.FULL_WAKE_LOCK |
+                        PowerManager.ON_AFTER_RELEASE,
+                context.getClass().getName());
+        wakeLock.acquire(timeout);
+        return wakeLock;
+    }
+
+    public static final DisplayMetrics getDeiveSize(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+
+        return dm;
+    }
+}

+ 25 - 0
app/src/main/java/com/elexlab/cybercontroller/utils/PermissionUtil.java

@@ -0,0 +1,25 @@
+package com.elexlab.cybercontroller.utils;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.core.app.ActivityCompat;
+
+public class PermissionUtil {
+    public static void checkAndRequestPermissions(Activity activity, String[] PERMISSIONS){
+        for(String permission:PERMISSIONS){
+            if (!(ActivityCompat.checkSelfPermission(
+                    activity, permission)
+                    == PackageManager.PERMISSION_GRANTED)) {
+                ActivityCompat.requestPermissions(
+                        activity, PERMISSIONS, 10);
+                return;
+            }
+        }
+
+
+    }
+
+}

+ 68 - 0
app/src/main/java/com/elexlab/cybercontroller/utils/SharedPreferencesUtil.java

@@ -0,0 +1,68 @@
+package com.elexlab.cybercontroller.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.util.Map;
+
+public class SharedPreferencesUtil {
+    private final static String TAG = SharedPreferencesUtil.class.getSimpleName();
+    public static void setPreferences(Context context, String preference, Map<String,Object> keyValues){
+        SharedPreferences sharedPreferences = context.getSharedPreferences(preference, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPreferences.edit();//获取编辑
+        for(String key:keyValues.keySet()){
+            Object value = keyValues.get(key);
+            if(value instanceof String){
+                editor.putString(key, (String) value);
+            }else if(value instanceof Integer){
+                editor.putInt(key, (Integer) value);
+            }else if(value instanceof Long){
+                editor.putLong(key, (Long) value);
+            }else if(value instanceof Boolean){
+                editor.putBoolean(key, (Boolean) value);
+            }else if(value instanceof Float){
+                editor.putFloat(key, (Float) value);
+            }
+        }
+        editor.commit();
+    }
+
+    public static <T> void setPreference(Context context, String preference, String key, T value){
+        SharedPreferences sharedPreferences = context.getSharedPreferences(preference, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPreferences.edit();//获取编辑
+        if(value instanceof String){
+            editor.putString(key, (String) value);
+        }else if(value instanceof Integer){
+            editor.putInt(key, (Integer) value);
+        }else if(value instanceof Long){
+            editor.putLong(key, (Long) value);
+        }else if(value instanceof Boolean){
+            editor.putBoolean(key, (Boolean) value);
+        }else if(value instanceof Float){
+            editor.putFloat(key, (Float) value);
+        }
+        editor.commit();
+    }
+
+    public static <T> T getPreference(Context context,String preference,String key,T defValue){
+        SharedPreferences sharedPreferences = context.getSharedPreferences(preference, Context.MODE_PRIVATE);
+        Object value = null;
+        if(defValue instanceof String){
+            value = sharedPreferences.getString(key,(String)defValue);
+        }else if(defValue instanceof Integer){
+            value = sharedPreferences.getInt(key,(Integer)defValue);
+
+        }else if(defValue instanceof Long){
+            value = sharedPreferences.getLong(key,(Long)defValue);
+
+        }else if(defValue instanceof Boolean){
+            value = sharedPreferences.getBoolean(key,(Boolean)defValue);
+
+        }else if(defValue instanceof Float){
+            value = sharedPreferences.getFloat(key,(Float)defValue);
+
+        }
+        return (T) value;
+    }
+}

+ 9 - 0
app/src/main/res/anim/login_transition.xml

@@ -0,0 +1,9 @@
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+    <translate
+        android:duration="3000"
+        android:fromXDelta="0"
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:toXDelta="0" />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_mediumAnimTime" />
+</set>

+ 30 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

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

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="#99ffffff"
+      android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
+</vector>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

BIN
app/src/main/res/drawable/liveness_detection_frame.9.png


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


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

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <solid android:color="#ee000000" />
+    <stroke
+        android:width="0.5dp"
+        android:color="#fff" />
+    <corners android:radius="6dp" />
+</shape>

+ 107 - 0
app/src/main/res/layout/activity_login.xml

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:id="@+id/llMain"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_weight="1"
+            android:layout_height="match_parent">
+            <com.elexlab.cybercontroller.ui.widget.AssetsAnimationImageView
+                android:id="@+id/tvImageView"
+                android:layout_centerInParent="true"
+                android:src="@mipmap/tv_sleep"
+                android:layout_width="150dp"
+                android:layout_height="150dp"
+                android:layout_centerHorizontal="true"/>
+            <LinearLayout
+                android:id="@+id/llSleep"
+                android:layout_toRightOf="@id/tvImageView"
+                android:layout_alignParentRight="true"
+                android:layout_marginTop="100dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+                <TextView
+                    android:text="z"
+                    android:textColor="#9999"
+                    android:textSize="20dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                <TextView
+                    android:textColor="#9999"
+                    android:layout_marginLeft="10dp"
+                    android:text="z"
+                    android:textSize="40dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+                <TextView
+                    android:textColor="#9999"
+                    android:layout_marginLeft="10dp"
+                    android:text="z"
+                    android:textSize="60dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            </LinearLayout>
+
+        </RelativeLayout>
+        <RelativeLayout
+            android:id="@+id/preview_container"
+            android:layout_alignParentRight="true"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:minHeight="360dp">
+
+
+                android:layout_height="wrap_content">
+            <com.elexlab.cybercontroller.ui.widget.CoolDigitalClock
+                android:id="@+id/digitalClock"
+                android:scaleX="0.6"
+                android:scaleY="0.6"
+
+                android:layout_centerInParent="true"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+
+
+            <RelativeLayout
+                android:id="@+id/rlScanFace"
+                android:visibility="visible"
+                android:layout_centerInParent="true"
+                android:layout_width="300dp"
+                android:layout_height="match_parent">
+                <FrameLayout
+                    android:layout_centerInParent="true"
+                    android:id="@+id/surface_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+
+                </FrameLayout>
+
+                <ImageView
+                    android:id="@+id/imageview_scanbg"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_centerInParent="true"
+                    android:scaleType="fitXY"
+                    android:src="@drawable/mask" />
+            </RelativeLayout>
+
+            <TextView
+                android:id="@+id/tvInfo"
+                android:visibility="invisible"
+                android:text="欢迎回来"
+                android:textSize="60dp"
+                android:textAlignment="center"
+                android:textColor="#fff"
+                android:layout_centerInParent="true"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"/>
+
+        </RelativeLayout>
+
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 87 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+
+    <RelativeLayout
+        android:id="@+id/mainBoard"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.elexlab.cybercontroller.ui.widget.TouchboardView
+            android:id="@+id/touchboardView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+
+<!--        <RelativeLayout-->
+<!--            android:id="@+id/rlMicrophone"-->
+<!--            android:layout_alignParentBottom="true"-->
+<!--            android:layout_centerHorizontal="true"-->
+
+<!--            android:layout_width="80dp"-->
+<!--            android:layout_height="80dp">-->
+<!--            <ImageView-->
+<!--                android:layout_marginBottom="10dp"-->
+<!--                android:src="@mipmap/ic_microphone"-->
+<!--                android:layout_width="match_parent"-->
+<!--                android:layout_height="match_parent"/>-->
+<!--        </RelativeLayout>-->
+        <com.elexlab.cybercontroller.ui.widget.SpeechRecordView
+            android:visibility="visible"
+            android:id="@+id/speechRecordView"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+        <RelativeLayout
+            android:id="@+id/rlSettings"
+            android:padding="20dp"
+            android:layout_alignParentRight="true"
+            android:layout_centerHorizontal="true"
+            android:layout_width="80dp"
+            android:layout_height="80dp">
+            <ImageView
+                android:src="@drawable/ic_baseline_settings"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"/>
+        </RelativeLayout>
+
+        <com.elexlab.cybercontroller.ui.widget.InfoBoxView
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="10dp"
+            android:id="@+id/ivInfoBoxView"
+            android:adjustViewBounds="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+
+        <ImageView
+            android:id="@+id/ivPreview"
+            android:layout_width="480dp"
+            android:layout_height="match_parent"/>
+
+        <RelativeLayout
+            android:id="@+id/rlContainer"
+            android:padding="16dp"
+            android:background="#dd000000"
+            android:layout_centerInParent="true"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <com.elexlab.cybercontroller.ui.widget.TranslationView
+                android:id="@+id/tvTranslation"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        </RelativeLayout>
+
+
+
+    </RelativeLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 17 - 0
app/src/main/res/layout/activity_test.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <Button
+            android:id="@+id/btTest"
+            android:text="向电脑输入文本"
+            android:textSize="60dp"
+            android:layout_centerInParent="true"
+            android:layout_width="300dp"
+            android:layout_height="200dp"/>
+    </RelativeLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 41 - 0
app/src/main/res/layout/cool_digital_clock_layout.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.elexlab.cybercontroller.ui.widget.SevenPartDigitalTube
+        android:id="@+id/timeHourHigh"
+        android:layout_width="60dip"
+        android:layout_height="120dip" />
+    <com.elexlab.cybercontroller.ui.widget.SevenPartDigitalTube
+        android:id="@+id/timeHourLow"
+        android:layout_marginLeft="15dip"
+        android:layout_width="60dip"
+        android:layout_height="120dip" />
+    <TextView
+        android:id="@+id/tvBreaker"
+        android:layout_width="20dip"
+        android:layout_marginLeft="15dip"
+        android:layout_height="120dip"
+        android:gravity="center"
+        android:textSize="100sp"
+        android:text=":"/>
+
+    <com.elexlab.cybercontroller.ui.widget.SevenPartDigitalTube
+        android:id="@+id/timeMinHigh"
+        android:layout_marginLeft="15dip"
+        android:layout_width="60dip"
+        android:layout_height="120dip" />
+
+    <com.elexlab.cybercontroller.ui.widget.SevenPartDigitalTube
+        android:id="@+id/timeMinLow"
+        android:layout_marginLeft="15dip"
+        android:layout_width="60dip"
+        android:layout_height="120dip" />
+
+
+
+
+</LinearLayout>

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

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:padding="20dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:text="主机IP:"
+                android:textSize="20dp"
+                android:textColor="#fff"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+            <EditText
+                android:id="@+id/etHostIp"
+                android:textColorHint="#666"
+                android:textColor="#fff"
+                android:hint="比如:192.168.3.28"
+                android:padding="10dp"
+                android:background="@drawable/stroke_corner_background"
+                android:layout_marginLeft="10dp"
+                android:layout_width="match_parent"
+                android:layout_height="60dp"/>
+        </LinearLayout>
+        <LinearLayout
+            android:padding="20dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:text="主机端口:"
+                android:textSize="20dp"
+                android:textColor="#fff"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+            <EditText
+                android:id="@+id/etHostPort"
+                android:background="@drawable/stroke_corner_background"
+                android:padding="10dp"
+                android:layout_marginLeft="10dp"
+                android:text="2233"
+                android:textColor="#fff"
+                android:layout_width="match_parent"
+                android:layout_height="60dp"/>
+        </LinearLayout>
+
+        <RelativeLayout
+            android:padding="20dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <Button
+                android:id="@+id/btnCancel"
+                android:padding="10dp"
+                android:background="@drawable/stroke_corner_background"
+                android:text="取消"
+                android:textColor="#fff"
+                android:layout_width="100dp"
+                android:layout_height="60dp"/>
+            <Button
+                android:id="@+id/btnSave"
+                android:padding="10dp"
+                android:layout_alignParentRight="true"
+                android:background="@drawable/stroke_corner_background"
+                android:text="保存"
+                android:textColor="#fff"
+                android:layout_width="100dp"
+                android:layout_height="60dp"/>
+        </RelativeLayout>
+
+    </LinearLayout>
+</LinearLayout>

+ 28 - 0
app/src/main/res/layout/view_info_box_layout.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:background="@drawable/stroke_corner_background"
+        android:padding="12dp"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/tvTitle"
+            android:textSize="30dp"
+            android:textColor="#FF5151"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/tvInfo"
+            android:layout_marginTop="10dp"
+            android:textSize="20dp"
+            android:maxWidth="200dp"
+            android:textColor="#fff"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 18 - 0
app/src/main/res/layout/view_speech_record.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <RelativeLayout
+        android:id="@+id/rlMicrophone"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+
+        android:layout_width="80dp"
+        android:layout_height="80dp">
+        <ImageView
+            android:layout_marginBottom="10dp"
+            android:src="@mipmap/ic_microphone"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </RelativeLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 29 - 0
app/src/main/res/layout/view_translation_layout.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/tvSource"
+            android:text=""
+            android:textColor="#FF5151"
+            android:textSize="20dp"
+            android:maxLines="1"
+            android:ellipsize="end"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+        <TextView
+            android:id="@+id/tvResult"
+            android:text=""
+            android:textColor="#fff"
+            android:textSize="20dp"
+            android:scrollbars="vertical"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

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


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


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


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


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


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


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


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


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


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


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


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CyberController" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

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

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

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

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">CyberController</string>
+</resources>

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.CyberController" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/com/elexlab/cybercontroller/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.elexlab.cybercontroller;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 27 - 0
build.gradle

@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        maven { url 'https://developer.huawei.com/repo/' }
+    }
+    dependencies {
+        classpath "com.android.tools.build:gradle:4.1.3"
+        classpath 'com.huawei.agconnect:agcp:1.5.2.300'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        maven { url 'https://jitpack.io' }
+        maven {url 'https://developer.huawei.com/repo/'}
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 19 - 0
gradle.properties

@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true

BIN
gradle/wrapper/gradle-wrapper.jar


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

@@ -0,0 +1,6 @@
+#Thu Mar 17 17:33:27 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

+ 172 - 0
gradlew

@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"

+ 84 - 0
gradlew.bat

@@ -0,0 +1,84 @@
+@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 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=
+
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 2 - 0
settings.gradle

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