Ver Fonte

1.qrcode pay

slambb há 2 anos atrás
commit
59dfbbb79e
57 ficheiros alterados com 2396 adições e 0 exclusões
  1. 15 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 6 0
      .idea/compiler.xml
  4. 11 0
      .idea/encodings.xml
  5. 22 0
      .idea/gradle.xml
  6. 41 0
      .idea/inspectionProfiles/Project_Default.xml
  7. 6 0
      .idea/kotlinc.xml
  8. 9 0
      .idea/misc.xml
  9. 1 0
      app/.gitignore
  10. 66 0
      app/build.gradle.kts
  11. 21 0
      app/proguard-rules.pro
  12. 28 0
      app/src/main/AndroidManifest.xml
  13. 54 0
      app/src/main/java/com/example/myport/MainActivity.kt
  14. 11 0
      app/src/main/java/com/example/myport/ui/theme/Color.kt
  15. 70 0
      app/src/main/java/com/example/myport/ui/theme/Theme.kt
  16. 34 0
      app/src/main/java/com/example/myport/ui/theme/Type.kt
  17. 30 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  18. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  19. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  20. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  21. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  22. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  23. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  24. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  25. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  26. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  27. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  28. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  29. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  30. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  31. 10 0
      app/src/main/res/values/colors.xml
  32. 3 0
      app/src/main/res/values/strings.xml
  33. 5 0
      app/src/main/res/values/themes.xml
  34. 13 0
      app/src/main/res/xml/backup_rules.xml
  35. 19 0
      app/src/main/res/xml/data_extraction_rules.xml
  36. 6 0
      build.gradle.kts
  37. 23 0
      gradle.properties
  38. BIN
      gradle/wrapper/gradle-wrapper.jar
  39. 6 0
      gradle/wrapper/gradle-wrapper.properties
  40. 185 0
      gradlew
  41. 89 0
      gradlew.bat
  42. 1 0
      serialport/.gitignore
  43. 42 0
      serialport/build.gradle.kts
  44. 0 0
      serialport/consumer-rules.pro
  45. BIN
      serialport/libs/il2cpp/classes.jar
  46. 21 0
      serialport/proguard-rules.pro
  47. 18 0
      serialport/src/main/AndroidManifest.xml
  48. 175 0
      serialport/src/main/java/android_serialport_api/ComPort.java
  49. 97 0
      serialport/src/main/java/android_serialport_api/SerialPort.java
  50. 168 0
      serialport/src/main/java/com/example/serialport/UnityPlayerActivity.java
  51. 83 0
      serialport/src/main/java/com/example/serialport/serialport.java
  52. 59 0
      serialport/src/main/java/com/example/serialport/serialport_api.java
  53. 744 0
      serialport/src/main/java/dcutli/FileUtli.java
  54. BIN
      serialport/src/main/jniLibs/arm64-v8a/libserial_port.so
  55. BIN
      serialport/src/main/jniLibs/armeabi-v7a/libserial_port.so
  56. 1 0
      serialport/src/main/res/values/strings.xml
  57. 18 0
      settings.gradle.kts

+ 15 - 0
.gitignore

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

+ 3 - 0
.idea/.gitignore

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

+ 6 - 0
.idea/compiler.xml

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

+ 11 - 0
.idea/encodings.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/serialport/src/main/java/android_serialport_api/ComPort.java" charset="GBK" />
+    <file url="file://$PROJECT_DIR$/serialport/src/main/java/android_serialport_api/SerialPort.java" charset="GBK" />
+    <file url="file://$PROJECT_DIR$/serialport/src/main/java/com/example/serialport/serialport_api.java" charset="GBK" />
+    <file url="file://$PROJECT_DIR$/serialport/src/main/java/dcutli/FileUtli.java" charset="GBK" />
+    <file url="file://$PROJECT_DIR$/serialport/src/main/jniLibs" charset="GBK" />
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>

+ 22 - 0
.idea/gradle.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="$PROJECT_DIR$/../../NVPACK/gradle-2.9" />
+        <option name="gradleJvm" value="jbr-17" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+            <option value="$PROJECT_DIR$/serialport" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 41 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,41 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+    <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
+      <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/kotlinc.xml

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

+ 9 - 0
.idea/misc.xml

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

+ 1 - 0
app/.gitignore

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

+ 66 - 0
app/build.gradle.kts

@@ -0,0 +1,66 @@
+plugins {
+    id("com.android.application")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    namespace = "com.example.myport"
+    compileSdk = 33
+
+    defaultConfig {
+        applicationId = "com.example.myport"
+        minSdk = 21
+        targetSdk = 33
+        versionCode = 1
+        versionName = "1.0"
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        vectorDrawables {
+            useSupportLibrary = true
+        }
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+    buildFeatures {
+        compose = true
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion = "1.4.3"
+    }
+    packaging {
+        resources {
+            excludes += "/META-INF/{AL2.0,LGPL2.1}"
+        }
+    }
+}
+
+dependencies {
+
+    implementation("androidx.core:core-ktx:1.9.0")
+    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
+    implementation("androidx.activity:activity-compose:1.7.0")
+    implementation(platform("androidx.compose:compose-bom:2023.03.00"))
+    implementation("androidx.compose.ui:ui")
+    implementation("androidx.compose.ui:ui-graphics")
+    implementation("androidx.compose.ui:ui-tooling-preview")
+    implementation("androidx.compose.material3:material3")
+    implementation(project(mapOf("path" to ":serialport")))
+
+    debugImplementation("androidx.compose.ui:ui-tooling")
+    debugImplementation("androidx.compose.ui:ui-test-manifest")
+}

+ 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

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

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.MyPort"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.MyPort">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 54 - 0
app/src/main/java/com/example/myport/MainActivity.kt

@@ -0,0 +1,54 @@
+package com.example.myport
+
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.example.myport.ui.theme.MyPortTheme
+import com.example.serialport.serialport
+import com.example.serialport.serialport_api
+
+class MainActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        serialport_api.initSerialport();
+
+        setContent {
+            MyPortTheme {
+                // A surface container using the 'background' color from the theme
+                Surface(
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.background
+                ) {
+                    Greeting("Android")
+                }
+            }
+        }
+
+
+    }
+}
+
+@Composable
+fun Greeting(name: String, modifier: Modifier = Modifier) {
+    Text(
+        text = "Hello $name!",
+        modifier = modifier
+    )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun GreetingPreview() {
+    MyPortTheme {
+        Greeting("Android")
+    }
+}

+ 11 - 0
app/src/main/java/com/example/myport/ui/theme/Color.kt

@@ -0,0 +1,11 @@
+package com.example.myport.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)

+ 70 - 0
app/src/main/java/com/example/myport/ui/theme/Theme.kt

@@ -0,0 +1,70 @@
+package com.example.myport.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+    primary = Purple80,
+    secondary = PurpleGrey80,
+    tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+    primary = Purple40,
+    secondary = PurpleGrey40,
+    tertiary = Pink40
+
+    /* Other default colors to override
+    background = Color(0xFFFFFBFE),
+    surface = Color(0xFFFFFBFE),
+    onPrimary = Color.White,
+    onSecondary = Color.White,
+    onTertiary = Color.White,
+    onBackground = Color(0xFF1C1B1F),
+    onSurface = Color(0xFF1C1B1F),
+    */
+)
+
+@Composable
+fun MyPortTheme(
+    darkTheme: Boolean = isSystemInDarkTheme(),
+    // Dynamic color is available on Android 12+
+    dynamicColor: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    val colorScheme = when {
+        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+            val context = LocalContext.current
+            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        }
+
+        darkTheme -> DarkColorScheme
+        else -> LightColorScheme
+    }
+    val view = LocalView.current
+    if (!view.isInEditMode) {
+        SideEffect {
+            val window = (view.context as Activity).window
+            window.statusBarColor = colorScheme.primary.toArgb()
+            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+        }
+    }
+
+    MaterialTheme(
+        colorScheme = colorScheme,
+        typography = Typography,
+        content = content
+    )
+}

+ 34 - 0
app/src/main/java/com/example/myport/ui/theme/Type.kt

@@ -0,0 +1,34 @@
+package com.example.myport.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+    bodyLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        letterSpacing = 0.5.sp
+    )
+    /* Other default text styles to override
+    titleLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 22.sp,
+        lineHeight = 28.sp,
+        letterSpacing = 0.sp
+    ),
+    labelSmall = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Medium,
+        fontSize = 11.sp,
+        lineHeight = 16.sp,
+        letterSpacing = 0.5.sp
+    )
+    */
+)

+ 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:viewportHeight="108"
+    android:viewportWidth="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:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</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:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>

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

@@ -0,0 +1,6 @@
+<?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" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

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

@@ -0,0 +1,6 @@
+<?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" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

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


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


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


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


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


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


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


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


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


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


+ 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">MyPort</string>
+</resources>

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

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <style name="Theme.MyPort" parent="android:Theme.Material.Light.NoActionBar" />
+</resources>

+ 13 - 0
app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 6 - 0
build.gradle.kts

@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id("com.android.application") version "8.1.0" apply false
+    id("org.jetbrains.kotlin.android") version "1.8.10" apply false
+    id("com.android.library") version "8.1.0" apply false
+}

+ 23 - 0
gradle.properties

@@ -0,0 +1,23 @@
+# 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
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar


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

@@ -0,0 +1,6 @@
+#Thu Sep 28 22:51:00 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
serialport/.gitignore

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

+ 42 - 0
serialport/build.gradle.kts

@@ -0,0 +1,42 @@
+plugins {
+    id("com.android.library")
+}
+
+android {
+    namespace = "com.example.serialport"
+    compileSdk = 33
+
+    defaultConfig {
+        minSdk = 21
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles("consumer-rules.pro")
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = 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.6.1")
+    implementation("com.google.android.material:material:1.8.0")
+    /**
+     *     1)在unity的安装目录下件,找到一个名为classes.jar的文件目录为 D:\Unityxxx\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Development\Classes
+     *     2)然后将classes.jar粘贴到mysdk模块的libs目录下(需要将工程切换到project视图)额外说明:在il2cpp目录下也有一个名称一样的classes.jar文件,其目录为
+     *     D:\Unityxxx\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Development\Classes
+     */
+    compileOnly(files("libs\\il2cpp\\classes.jar"))
+    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+}

+ 0 - 0
serialport/consumer-rules.pro


BIN
serialport/libs/il2cpp/classes.jar


+ 21 - 0
serialport/proguard-rules.pro

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

+ 18 - 0
serialport/src/main/AndroidManifest.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application
+        android:allowBackup="true"
+        android:supportsRtl="true">
+        <activity
+            android:name=".serialport"
+            android:exported="true">
+<!--            <intent-filter>-->
+<!--                <action android:name="android.intent.action.MAIN" />-->
+<!--                <category android:name="android.intent.category.LAUNCHER" />-->
+<!--            </intent-filter>-->
+            <meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
+        </activity>
+    </application>
+
+</manifest>

+ 175 - 0
serialport/src/main/java/android_serialport_api/ComPort.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright 2009 Cedric Priscal
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+
+package android_serialport_api;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidParameterException;
+
+//20201014 添加注释,移动辅助函数到工具类中
+
+public  class ComPort {
+	private static String TAG = "ComPort";
+
+	protected SerialPort mSerialPort;
+	protected OutputStream mOutputStream;
+	private InputStream mInputStream;
+	private ReadThread mReadThread;
+
+	public String mComname;
+	int boardRate = 115200;
+
+	public interface DataHandler  {
+		void onDataReceived(String comName, byte[] buffer, int size);
+	}
+
+	DataHandler dataHandler;
+
+	private class ReadThread extends Thread {
+		byte[] buffer = new byte[128];
+		@Override
+		public void run() {
+			super.run();
+			while(!isInterrupted()) {
+				int size=0;
+				try {
+					if (mInputStream == null)
+					    return;
+
+					if( mInputStream!= null)
+					size = mInputStream.read(buffer);
+
+					if (size > 0) {
+						onDataReceived(buffer, size);
+					}
+				} catch (IOException e) {
+					e.printStackTrace();
+					return;
+				}
+			}
+			Log.i("Comport", "Com read thread exit.");
+		}//end of run
+	}//end of class ReadThread
+
+	public ComPort()
+	{
+	}
+
+	//防止切换activity后无法处理数据
+	public void setHandler(DataHandler handler)
+	{
+		dataHandler = handler;
+	}
+
+	boolean isOpen = false;
+	//串口使用3步骤 1.open 2.sendData 3.close 收到的数据会调用DataHandler
+	public boolean open(String com_name, int boardRate, DataHandler handler) {
+		dataHandler = handler;
+		mComname = com_name;
+		this.boardRate = boardRate;
+
+		//防止重复打开
+		if( isOpen ){
+			Log.e("Comport", "this comport has opened.Don't open again or you should close before open.");
+			return false;
+		}
+
+		try {
+			if (mSerialPort == null) {
+				mSerialPort = new SerialPort();
+				boolean bOpen = mSerialPort.openPort(new File(mComname), boardRate, 0);
+				if(bOpen== false)
+					return bOpen;
+			}
+
+			if( mOutputStream == null)
+				mOutputStream = mSerialPort.getOutputStream();
+
+			if( mInputStream == null )
+				mInputStream = mSerialPort.getInputStream();
+
+			/* Create a receiving thread */
+			if( mReadThread == null){
+				mReadThread = new ReadThread();
+				mReadThread.start();
+			}
+			isOpen = true;
+		} catch (SecurityException e) {
+			e.printStackTrace();
+		} catch (InvalidParameterException e) {
+			e.printStackTrace();
+		}
+
+		return true;
+	}
+
+	public void close() {
+		isOpen = false;
+
+		try {
+			if( mOutputStream != null){
+				mOutputStream.close();
+				mOutputStream = null;
+			}
+
+			if( mInputStream != null) {
+				mInputStream.close();
+				mInputStream = null;
+			}
+		}catch (IOException es)
+		{
+
+		}
+
+		if (mReadThread != null){
+			mReadThread.interrupt();
+			mReadThread = null;
+		}
+
+		if (mSerialPort != null) {
+			mSerialPort.close();
+			mSerialPort = null;
+		}
+	}
+
+	public void sendData(byte[] buffer, int size)
+	{
+		synchronized(this){
+			try {
+				//System.out.println("send to com" + bytes2HexString( buffer, size));
+				if (mOutputStream != null) {
+					mOutputStream.write(buffer,0, size);
+					mOutputStream.flush();
+				} else {
+					return;
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+				return;
+			}
+		}
+	}
+
+	private void onDataReceived(byte[] buffer, int size) {
+		if( dataHandler !=null )
+			dataHandler.onDataReceived(mComname, buffer, size );
+	}
+}

+ 97 - 0
serialport/src/main/java/android_serialport_api/SerialPort.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2009 Cedric Priscal
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+
+package android_serialport_api;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class SerialPort {
+
+	private static final String TAG = "SerialPort";
+
+	/*
+	 * Do not remove or rename the field mFd: it is used by native method close();
+	 */
+	private FileDescriptor mFd;
+	private FileInputStream mFileInputStream;
+	private FileOutputStream mFileOutputStream;
+
+	public SerialPort()  {
+	}
+
+	public boolean openPort(File device, int baudrate, int flags) {
+		if( device.exists() == false ){
+			Log.e(TAG, "串口不存在,打开失败");
+			return false;
+		}
+
+		// Check access permission
+		if (!device.canRead() || !device.canWrite()) {
+			try {
+				/* Missing read/write permission, trying to chmod the file */
+				Process su;
+				su = Runtime.getRuntime().exec("/system/bin/su");
+				String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
+						+ "exit\n";
+				su.getOutputStream().write(cmd.getBytes());
+				if ((su.waitFor() != 0)
+						|| !device.canRead()
+						|| !device.canWrite()) {
+
+					Log.e(TAG, "修改权限失败。");
+					return false;
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+				Log.e(TAG, "修改权限失败。");
+				return false;
+			}
+		}
+
+		mFd = open(device.getAbsolutePath(), baudrate, flags);
+		if (mFd == null) {
+			Log.e(TAG, "native open returns null");
+			return false;
+		}
+
+		mFileInputStream = new FileInputStream(mFd);
+		mFileOutputStream = new FileOutputStream(mFd);
+		return  true;
+	}
+
+	// Getters and setters
+	public InputStream getInputStream() {
+		return mFileInputStream;
+	}
+
+	public OutputStream getOutputStream() {
+		return mFileOutputStream;
+	}
+
+	// JNI
+	private native static FileDescriptor open(String path, int baudrate, int flags);
+	public native void close();
+	static {
+		System.loadLibrary("serial_port");
+	}
+}

+ 168 - 0
serialport/src/main/java/com/example/serialport/UnityPlayerActivity.java

@@ -0,0 +1,168 @@
+package com.example.serialport;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.os.Process;
+
+import com.unity3d.player.IUnityPlayerLifecycleEvents;
+import com.unity3d.player.MultiWindowSupport;
+import com.unity3d.player.UnityPlayer;
+
+public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
+{
+    protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
+
+    // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
+    // The command line arguments are passed as a string, separated by spaces
+    // UnityPlayerActivity calls this from 'onCreate'
+    // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
+    // See https://docs.unity3d.com/Manual/CommandLineArguments.html
+    // @param cmdLine the current command line arguments, may be null
+    // @return the modified command line string or null
+    protected String updateUnityCommandLineArguments(String cmdLine)
+    {
+        return cmdLine;
+    }
+
+    // Setup activity layout
+    @Override protected void onCreate(Bundle savedInstanceState)
+    {
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        super.onCreate(savedInstanceState);
+
+        String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
+        getIntent().putExtra("unity", cmdLine);
+
+        mUnityPlayer = new UnityPlayer(this, this);
+        setContentView(mUnityPlayer);
+        mUnityPlayer.requestFocus();
+    }
+
+    // When Unity player unloaded move task to background
+    @Override public void onUnityPlayerUnloaded() {
+        moveTaskToBack(true);
+    }
+
+    // Callback before Unity player process is killed
+    @Override public void onUnityPlayerQuitted() {
+    }
+
+    @Override protected void onNewIntent(Intent intent)
+    {
+        // To support deep linking, we need to make sure that the client can get access to
+        // the last sent intent. The clients access this through a JNI api that allows them
+        // to get the intent set on launch. To update that after launch we have to manually
+        // replace the intent with the one caught here.
+        setIntent(intent);
+        mUnityPlayer.newIntent(intent);
+    }
+
+    // Quit Unity
+    @Override protected void onDestroy ()
+    {
+        mUnityPlayer.destroy();
+        super.onDestroy();
+    }
+
+    // If the activity is in multi window mode or resizing the activity is allowed we will use
+    // onStart/onStop (the visibility callbacks) to determine when to pause/resume.
+    // Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
+    // existing behavior.
+    @Override protected void onStop()
+    {
+        super.onStop();
+
+        if (!MultiWindowSupport.getAllowResizableWindow(this))
+            return;
+
+        mUnityPlayer.pause();
+    }
+
+    @Override protected void onStart()
+    {
+        super.onStart();
+
+        if (!MultiWindowSupport.getAllowResizableWindow(this))
+            return;
+
+        mUnityPlayer.resume();
+    }
+
+    // Pause Unity
+    @Override protected void onPause()
+    {
+        super.onPause();
+
+        MultiWindowSupport.saveMultiWindowMode(this);
+
+        if (MultiWindowSupport.getAllowResizableWindow(this))
+            return;
+
+        mUnityPlayer.pause();
+    }
+
+    // Resume Unity
+    @Override protected void onResume()
+    {
+        super.onResume();
+
+        if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
+            return;
+
+        mUnityPlayer.resume();
+    }
+
+    // Low Memory Unity
+    @Override public void onLowMemory()
+    {
+        super.onLowMemory();
+        mUnityPlayer.lowMemory();
+    }
+
+    // Trim Memory Unity
+    @Override public void onTrimMemory(int level)
+    {
+        super.onTrimMemory(level);
+        if (level == TRIM_MEMORY_RUNNING_CRITICAL)
+        {
+            mUnityPlayer.lowMemory();
+        }
+    }
+
+    // This ensures the layout will be correct.
+    @Override public void onConfigurationChanged(Configuration newConfig)
+    {
+        super.onConfigurationChanged(newConfig);
+        mUnityPlayer.configurationChanged(newConfig);
+    }
+
+    // Notify Unity of the focus change.
+    @Override public void onWindowFocusChanged(boolean hasFocus)
+    {
+        super.onWindowFocusChanged(hasFocus);
+        mUnityPlayer.windowFocusChanged(hasFocus);
+    }
+
+    // For some reason the multiple keyevent type is not supported by the ndk.
+    // Force event injection by overriding dispatchKeyEvent().
+    @Override public boolean dispatchKeyEvent(KeyEvent event)
+    {
+        if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
+            return mUnityPlayer.injectEvent(event);
+        return super.dispatchKeyEvent(event);
+    }
+
+    // Pass any events not handled by (unfocused) views straight to UnityPlayer
+    @Override public boolean onKeyUp(int keyCode, KeyEvent event)     { return mUnityPlayer.injectEvent(event); }
+    @Override public boolean onKeyDown(int keyCode, KeyEvent event)   { return mUnityPlayer.injectEvent(event); }
+    @Override public boolean onTouchEvent(MotionEvent event)          { return mUnityPlayer.injectEvent(event); }
+    /*API12*/ public boolean onGenericMotionEvent(MotionEvent event)  { return mUnityPlayer.injectEvent(event); }
+}

+ 83 - 0
serialport/src/main/java/com/example/serialport/serialport.java

@@ -0,0 +1,83 @@
+package com.example.serialport;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.unity3d.player.UnityPlayer;
+
+import android_serialport_api.ComPort;
+import dcutli.FileUtli;
+
+
+/**
+ * Unityxxx\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\-unity3d\player下的UnityPlayerActivity直接拖入安卓工程MainActivity(继承于UnityPlayerActivity)的目录下。
+ */
+public class serialport extends UnityPlayerActivity {
+    private static String TAG = "SerialPort";
+
+    static final ComPort com = new ComPort();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+
+    //unity调用Android
+    public void UnityCallAndroid() {
+
+        Toast.makeText(this, "unity调用android成功", Toast.LENGTH_LONG).show();
+
+        AndroidCallUnity();
+    }
+
+    //android调用unity
+    public void AndroidCallUnity() {
+
+        //第1个参数为Unity场景中用于接收android消息的对象名称
+        //第2个参数为对象上的脚本的一个成员方法名称(脚本名称不限制)
+        //第3个参数为unity方法的参数
+        UnityPlayer.UnitySendMessage("SerialPortObj", "UnityMethod", "This is args.");
+    }
+
+
+    //初始化一次串口操作
+    public static boolean initSerialPort(String comName, int boardRate) {
+
+        Log.i(TAG, "初始化串口!comName:" + comName + ",boardRate:" + boardRate);
+        boolean bOpen = com.open(comName, boardRate, new ComPort.DataHandler() {
+            @Override
+            public void onDataReceived(String comName, byte[] buffer, int size) {
+                //这里需要根据业务逻辑手动拼包拆包检查,完整包再处理
+                //目前业务逻辑通过Unity去处理
+                String comdata = FileUtli.bytes2HexString(buffer, size);
+//                    Log.e(TAG,"收到串口数据:"+comdata);
+                UnityPlayer.UnitySendMessage("SerialPortObj", "onSerialPortReceived", comdata);
+            }
+        });
+
+        if (bOpen) {
+            Log.i(TAG, "串口打开" + "成功");
+        } else {
+            Log.e(TAG, "串口打开" + "失败");
+        }
+
+        return bOpen;
+
+    }
+
+    //关闭
+    public static void closeSerialPort() {
+        com.close();
+    }
+
+    //发送数据
+    public static void sendSerialPort(byte[] buffer,int size) {
+        //Log.i(TAG,"sendSerialPort:"+ size);
+        com.sendData(buffer, size);
+    }
+
+}

+ 59 - 0
serialport/src/main/java/com/example/serialport/serialport_api.java

@@ -0,0 +1,59 @@
+package com.example.serialport;
+
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.TextView;
+
+import android_serialport_api.ComPort;
+import dcutli.FileUtli;
+
+public class serialport_api {
+
+    //初始化一次串口操作
+    public static void initSerialport(){
+        Log.i("1。initSerialport","初始化串口!");
+        final ComPort com = new ComPort();
+        boolean bOpen = com.open("/dev/ttyS7", 9600, new ComPort.DataHandler() {
+            @Override
+            public void onDataReceived(String comName, byte[] buffer, int size) {
+                //这里需要根据业务逻辑手动拼包拆包检查,完整包再处理
+                //demo就直接输出了
+                if(true){
+                    String comdata = FileUtli.bytes2HexString( buffer, size);
+                    Log.e("收到串口数据:", comdata);
+                }
+
+                if(mHandler != null){
+                    Message msg = Message.obtain();
+                    msg.what = 100;
+                    msg.obj = buffer;
+                    msg.arg1 = size;
+                    mHandler.sendMessage( msg );
+                }
+            }
+        });
+
+        if( bOpen ){
+            Log.i("串口打开","成功");
+        }else{
+            Log.e("串口打开","失败");
+        }
+
+    }
+
+    public static Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+                switch (msg.what){
+                    case 100:{
+                        byte []data = (byte[])msg.obj;
+                        int len = msg.arg1;
+                        String comdata = FileUtli.bytes2HexString( data, len);
+                        Log.i("收到串口数据 handle:", comdata);
+                    }
+                    break;
+                }
+        }
+    };
+}

+ 744 - 0
serialport/src/main/java/dcutli/FileUtli.java

@@ -0,0 +1,744 @@
+package dcutli;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+
+public class FileUtli {
+
+    //获取主板名称
+    public static String getBoardName(){
+        return  getSystemProperty("ro.board.platform","unknown");
+    }
+
+    public static int dpToPx(Context context, int dps) {
+        return Math.round(context.getResources().getDisplayMetrics().density * dps);
+    }
+
+    //https://www.cnblogs.com/yongdaimi/p/13214479.html
+    //将指定的某一个比特位置0、置1、取反
+    /**
+     * Set the specified bit to 1
+     *
+     * @param originByte Raw byte value
+     * @param bitIndex   bit index (From 0~7)
+     * @return Final byte value
+     */
+    public static byte setSpecifiedBitTo1(byte originByte, int bitIndex) {
+        return originByte |= (1 << bitIndex);
+    }
+
+    /**
+     * Set the specified bit to 0
+     *
+     * @param originByte Raw byte value
+     * @param bitIndex   bit index (From 0~7)
+     * @return Final byte value
+     */
+    public static byte setSpecifiedBitTo0(byte originByte, int bitIndex) {
+        return originByte &= ~(1 << bitIndex);
+    }
+
+    /**
+     * Invert the specified bit
+     *
+     * @param originByte Raw byte value
+     * @param bitIndex   bit index (From 0~7)
+     * @return Final byte value
+     */
+    public static byte setSpecifiedBitToReverse(byte originByte, int bitIndex) {
+        return originByte ^= (1 << bitIndex);
+    }
+
+    /**
+     * Get the value of the specified bit
+     *
+     * @param originByte Raw byte value
+     * @param bitIndex   bit index (From 0~7)
+     * @return Final byte value
+     */
+    public static byte getSpecifiedBitValue(byte originByte, int bitIndex) {
+        return (byte) ((originByte) >> (bitIndex) & 1);
+    }
+
+    public static void CreateIfNotExist(String path)
+    {
+        File file = new File(path);
+        if (!file.exists()) {//如果文件夹不存在
+            file.mkdir();//创建文件夹
+        }
+        file = null;
+    }
+
+    public static int random(int min, int max){
+        if( min == max)
+            return 0;
+
+        int nRes = min + (int)(System.currentTimeMillis()%(max-min));
+
+        return nRes;
+    }
+
+    public static boolean isEthNetworkOk(Context context) {
+        ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        NetworkInfo mobNetInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET);
+        if (mobNetInfo != null) {
+            return mobNetInfo.isConnected();
+        }
+        return false;
+    }
+
+    public static boolean isWIFINetworkOk(Context context) {
+        ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        NetworkInfo mobNetInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        if (mobNetInfo != null) {
+            return mobNetInfo.isConnected();
+        }
+        return false;
+    }
+
+    public static boolean isMobileNetworkOk(Context context) {
+        ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo mobNetInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+        if (mobNetInfo != null) {
+            return mobNetInfo.isConnected();
+        }
+        return false;
+    }
+
+    //来源https://www.sharezer.com/archives/1314
+    public static String execRootCmd(String cmd) {
+        String result = "";
+        DataOutputStream dos = null;
+        DataInputStream dis = null;
+
+        try {
+            Process p = Runtime.getRuntime().exec("su");// 经过Root处理的android系统即有su命令
+            dos = new DataOutputStream(p.getOutputStream());
+            dis = new DataInputStream(p.getInputStream());
+
+            dos.writeBytes(cmd + "\n");
+            dos.flush();
+            dos.writeBytes("exit\n");
+            dos.flush();
+            String line = null;
+
+            BufferedReader br = new BufferedReader(new InputStreamReader(dis));
+            while ((line = br.readLine()) != null) {
+                result += line;
+                result += "\n";
+            }
+            p.waitFor();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (dos != null) {
+                try {
+                    dos.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (dis != null) {
+                try {
+                    dis.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return result;
+    }
+
+    public static boolean isExist(String string) {
+        File mFile = new File(string);
+        return mFile.exists();
+    }
+
+    public static String getIPv4(String device){
+        try {
+            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
+                NetworkInterface intf = en.nextElement();
+                if (device.equals(intf.getDisplayName()) == false) { //判断网口是否在使用,判断是否时我们获取的网口
+                    continue;
+                }
+
+                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
+                    InetAddress inetAddress = enumIpAddr.nextElement();
+                    if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
+                        return  inetAddress.getHostAddress().toString();
+                    }
+                }
+            }
+        } catch (Exception e) {
+             Log.e("getIPv4", "获取IP信息出错。");
+        }
+
+        return null;
+    }
+
+    private static String getSystemProperty(String property, String defaultValue) {
+        try {
+            Class clazz = Class.forName("android.os.SystemProperties");
+            Method getter = clazz.getDeclaredMethod("get", String.class);
+            String value = (String)getter.invoke(clazz.newInstance(), property);
+            if (!TextUtils.isEmpty(value)) {
+                return value;
+            }
+        } catch (Exception var6) {
+            Log.e("FileUtli", "Unable to read system properties" + var6.toString());
+        }
+
+        return defaultValue;
+    }
+
+    public static void deleteFile(String path)
+    {
+        File file = new File(path);
+        if (file.isFile() && file.exists()) {
+            file.delete();
+        }
+        file = null;
+    }
+
+    //传入绝对路径
+    public static String getFileNameWithoutSuffix(String path) {
+        if(TextUtils.isEmpty(path)){
+            return "";
+        }
+        int start = path.lastIndexOf("/");
+        if (start != -1 ) {
+            String fname = path.substring(start + 1);
+            String prefix=fname.substring(fname.lastIndexOf("."));
+            int num=prefix.length();//得到后缀名长度
+            String fileOtherName=fname.substring(0, fname.length()-num);//得到文件名。去掉了后缀
+            return fileOtherName;
+        } else {
+            return "";
+        }
+    }
+
+    private static String getAppDataFilePath(Context context) {
+        String cachePath;
+        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+                || !Environment.isExternalStorageRemovable()) {
+            //cachePath = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
+            cachePath = context.getFilesDir().getAbsolutePath();
+//            cachePath = context.getExternalCacheDir().getPath();//也可以这么写,只是返回的路径不一样,具体打log看
+        } else {
+            cachePath = context.getFilesDir().getAbsolutePath();
+//            cachePath = context.getCacheDir().getPath();//也可以这么写,只是返回的路径不一样,具体打log看
+        }
+        return cachePath;
+    }
+
+    //从assets 文件夹中获取文件并读取数据
+    public static String getFromAssets(Context context, String fileName){
+        String result = "";
+        try {
+            InputStream in = context.getResources().getAssets().open(fileName);
+//获取文件的字节数
+            int lenght = in.available();
+//创建byte数组
+            byte[]  buffer = new byte[lenght];
+//将文件中的数据读到byte数组中
+            in.read(buffer);
+            //result = EncodingUtils.getString(buffer, ENCODING);
+            result= new String(buffer);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public static String fmtTime(long tm){
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        Date curDate = new Date(tm);
+        String curTime = formatter.format(curDate);
+        return curTime;
+    }
+
+    public static byte[] getFromAssetsBin(Context context, String fileName){
+        try {
+            InputStream in = context.getResources().getAssets().open(fileName);
+//获取文件的字节数
+            int lenght = in.available();
+//创建byte数组
+            byte[]  buffer = new byte[lenght];
+//将文件中的数据读到byte数组中
+            in.read(buffer);
+            //result = EncodingUtils.getString(buffer, ENCODING);
+            return buffer;//= new String(buffer);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    //获取默认桌面 //https://blog.csdn.net/Programming2012/article/details/41983043
+    //用于获取的默认桌面,部分机型可能返回结果只有“android” 本问题在7.1上第一次启动稳定重现
+    public static String getHomeLauncher(Context context)
+    {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
+        String currentHomePackage = resolveInfo.activityInfo.packageName;
+        return currentHomePackage;
+    }
+
+    public static String getAppName(Context context, String packageName){
+        String str = packageName;
+        PackageInfo info = null;
+        PackageManager pm = context.getPackageManager();
+        try {
+            info = pm.getPackageInfo(str,PackageManager.GET_ACTIVITIES);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+
+        if( info == null)
+            return  null;
+        else
+            return info.applicationInfo.loadLabel(pm).toString();
+    }
+
+
+
+    /**
+     * 检查指定的服务是否正在运行
+     * @param mcontext
+     * @param serviceName   "当前包名."+"服务name"
+     * @return
+     */
+    public static boolean serviceIsRun(Context mcontext, String serviceName){
+        boolean serviceIsRun =false;
+        ActivityManager activityManager = (ActivityManager) mcontext
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.RunningServiceInfo> runningServices = activityManager.getRunningServices(100);
+        for (int i = 0; i < runningServices.size(); i++) {
+            String s = runningServices.get(i).service.getClassName();
+            if (s.equals(serviceName)) {
+                serviceIsRun =true;
+            }
+        }
+        return serviceIsRun;
+    }
+
+    //检查包是否存在
+    public static boolean isAppExist(Context context, String packname) {
+        PackageInfo packageInfo = null;
+        try {
+            packageInfo = context.getPackageManager().getPackageInfo(packname, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            //e.printStackTrace();
+            return false;
+        }
+        return packageInfo != null;
+    }
+
+
+    public static boolean isAppInstalled(Context context, String packageName){
+        PackageManager manager = context.getPackageManager();
+        List<PackageInfo> pkgList = manager.getInstalledPackages(0);
+        for (int i = 0; i < pkgList.size(); i++) {
+            PackageInfo pI = pkgList.get(i);
+            if (pI.packageName.equalsIgnoreCase(packageName ))
+                return true;
+        }
+        return false;
+    }
+
+    public static StringBuffer readFileByBytes(String fileName) {
+        File file = new File(fileName);
+        StringBuffer sb = new StringBuffer();
+
+        if (file.isFile() && file.exists()) { //判断文件是否存在
+
+            byte[] tempbytes = new byte[1024];
+            int byteread = 0;
+            try {
+                InputStream in = new FileInputStream(file);
+                //ReadFromFile1.showAvailableBytes(in);
+                // 读入多个字节到字节数组中,byteread为一次读入的字节数
+                while ((byteread = in.read(tempbytes)) != -1) {
+                    //  System.out.write(tempbytes, 0, byteread);
+                    String str = new String(tempbytes, 0, byteread);
+                    sb.append(str);
+                }
+            } catch (FileNotFoundException e) {
+                //read file failed.
+                return sb;
+            } catch (IOException dd) {
+
+            }
+
+        } else {
+            return sb;
+        }
+
+        return sb;
+    }
+
+    /**
+     * 32位MD5加密
+     * @param content -- 待加密内容
+     * @return
+     */
+    public static String md5Decode32(String content) {
+        byte[] hash;
+        try {
+            hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("NoSuchAlgorithmException",e);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("UnsupportedEncodingException", e);
+        }
+        //对生成的16字节数组进行补零操作
+        StringBuilder hex = new StringBuilder(hash.length * 2);
+        for (byte b : hash) {
+            if ((b & 0xFF) < 0x10){
+                hex.append("0");
+            }
+            hex.append(Integer.toHexString(b & 0xFF));
+        }
+        return hex.toString();
+    }
+
+    //根据摄像头测距-返回单位毫米-工作条件1080P摄像头 //参数2:摄像头分辨率参考值1920.因为调的时候是用这个分辨率调的。其他分辨率不保证准确,请自行调整
+    /*public static int WidthToDistance(int faceW, int resolutionW){
+        float distance = 0.0f;
+        if( faceW >=700 )
+        {
+            distance = 0.3f - (faceW-700.0f)/1000.0f;
+        }else if( faceW <700 && faceW >= 500){
+            distance = 0.3f - (faceW - 700.0f)/1000.0f;
+        }else if(faceW < 500 && faceW>= 200){
+            //200 = 1m //500 = 0.5
+            distance = 0.5f -(faceW-500.0f)/600.0f;
+        }
+        else if(faceW <200 && faceW>= 150){
+            distance = 1.0f - (faceW-200.0f)/100.0f;
+        }else if(faceW<150){
+            distance = 1.5f - (faceW - 150.0f)/50.0f;
+        }
+
+        return  (int)(distance*1000/1920*resolutionW);
+    }*/
+
+    //根据距离补偿温度的函数
+    /*
+    public static float DistanceTempBuchang(int dis_in_mm ){
+        float distance = 0.0f;
+
+        if(dis_in_mm>=900){
+            distance = 0.5f + (dis_in_mm-700.0f)/500.0f;
+        }
+        else if( dis_in_mm <900 && dis_in_mm >= 800 )
+        {
+            distance = 0.4f + (dis_in_mm-700.0f)/500.0f;
+        }
+        else if( dis_in_mm <800 && dis_in_mm >= 700 )
+        {
+            distance = 0.3f + (dis_in_mm-600.0f)/400.0f;
+        }else if(dis_in_mm <700 && dis_in_mm >= 600){
+            distance = 0.2f + (dis_in_mm-600.0f)/300.0f;
+        }
+        else if( dis_in_mm <600 && dis_in_mm >= 500){
+            distance = 0.1f + (dis_in_mm-500.0f)/400.0f;
+        }else if(dis_in_mm < 500 && dis_in_mm>= 400){
+            //200 = 1m //500 = 0.5
+            distance = -0.2f +(dis_in_mm-500.0f)/300.0f;
+        }
+        else if(dis_in_mm <400 && dis_in_mm>= 300){
+            distance = -0.4f + (dis_in_mm-400.0f)/100.0f;
+        }else if(dis_in_mm<300 && dis_in_mm>= 200){
+            distance = -0.5f +(dis_in_mm - 300.0f)/100.0f;
+        }else if(dis_in_mm<200 && dis_in_mm>= 100){
+            distance = -0.7f +(dis_in_mm - 200.0f)/100.0f;
+        }
+
+        return (distance);
+    }*/
+
+    public static Bitmap adjustPhotoRotation(Bitmap bm, final int orientationDegree) {
+        Matrix m = new Matrix();
+        m.setRotate(orientationDegree, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
+        try {
+            Bitmap bm1 = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
+            return bm1;
+        } catch (OutOfMemoryError ex) {
+        }
+        return null;
+    }
+
+    public static String bytes2HexString(byte[] b, int len) {
+        String ret = "";
+        for (int i = 0; i < len; i++) {
+            String hex = Integer.toHexString(b[ i ] & 0xFF);
+            if (hex.length() == 1) {
+                hex = '0' + hex;
+            }
+            ret += hex.toUpperCase();
+        }
+        return ret;
+    }
+
+    public static String getUsbStoragePath() {
+        String usbPath = null;
+        String usbBasePath = "/mnt/usb_storage/";
+        String ApkPath = null;
+        File file = new File(usbBasePath);
+
+        try {
+            if (file.exists() && file.isDirectory()) {
+                File[] files = file.listFiles();
+                if (files.length > 0) {
+                    usbPath = files[0].getAbsolutePath();
+                   // this.LOGD("steve : get file path " + usbPath);
+                    if (usbPath.contains("USB_DISK")) {
+                      //  this.LOGD("steve : open " + usbPath);
+                        File usbFile = new File(usbPath);
+                        if (usbFile.exists() && usbFile.isDirectory()) {
+                            File[] usbFiles = usbFile.listFiles();
+                            if (usbFiles.length > 0) {
+                                usbPath = usbFiles[0].getAbsolutePath();
+                                //this.LOGD("steve : usbPath " + usbPath);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception var9) {
+            var9.printStackTrace();
+        }
+
+        return usbPath;
+    }
+
+    public static Bitmap bitmapZoomByScale(Bitmap srcBitmap, float scaleWidth, float scaleHeight) {
+        int width = srcBitmap.getWidth();
+        int height = srcBitmap.getHeight();
+        Matrix matrix = new Matrix();
+        matrix.postScale(scaleWidth, scaleHeight);
+        Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true);
+        if(bitmap != null) {
+            return bitmap;
+        }else {
+            return srcBitmap;
+        }
+    }
+
+    public static String ReadFileB64(String filePath){
+        File file = new File(filePath);
+        if (file.isFile() && file.exists()) { //判断文件是否存在
+
+            StringBuffer sb = new StringBuffer();
+            byte[] tempbytes = new byte[1024];
+            int byteread = 0;
+            try {
+                InputStream in = new FileInputStream(file);
+                //ReadFromFile1.showAvailableBytes(in);
+                // 读入多个字节到字节数组中,byteread为一次读入的字节数
+                while ((byteread = in.read(tempbytes)) != -1) {
+                    //  System.out.write(tempbytes, 0, byteread);
+                    String str = new String(tempbytes, 0, byteread);
+                    sb.append(str);
+                }
+            } catch (FileNotFoundException e) {
+                //read file failed.
+                return  null;
+            } catch (IOException dd) {
+                return  null;
+            }
+
+            String jsonString = new String(sb);
+            jsonString =new String(Base64.decode(jsonString,Base64.DEFAULT));
+            return jsonString;
+        }
+
+        return  null;
+    }
+
+    public static void simpleWriteFile(String path, byte []data){
+        try {
+            FileOutputStream fos = null;
+            fos = new FileOutputStream(new File(path));
+            fos.write(data, 0, data.length);
+            fos.flush();
+            fos.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return;
+        }
+    }
+
+    public static byte[] simpleReadFile(String path){
+        File f= new File(path );
+        FileInputStream fileInputStream = null;
+        byte[] buffer =null;
+        try {
+            fileInputStream = new FileInputStream(f);
+            buffer = new byte[(int)f.length()];
+            int len = fileInputStream.read(buffer);
+            // 关闭输入流
+            fileInputStream.close();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return buffer;
+    }
+
+    public static  void WriteFileB64(String filePath, String fileContent){
+
+        String encodedString = Base64.encodeToString(fileContent.getBytes(), Base64.DEFAULT);
+
+        FileOutputStream outSTr = null;
+        try {
+            outSTr = new FileOutputStream(new File(filePath));
+            BufferedOutputStream Buff  = new BufferedOutputStream(outSTr);
+            Buff.write(encodedString.getBytes());
+            Buff.flush();
+            Buff.close();
+            outSTr.close();
+            outSTr = null;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String byteToStr(byte[] buffer) {
+        try {
+            int length = 0;
+            for (int i = 0; i < buffer.length; ++i) {
+                if (buffer[i] == 0) {
+                    length = i;
+                    break;
+                }
+            }
+            return new String(buffer, 0, length, "UTF-8");
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+
+    public static byte[] hexStringToBytes(String hexString) {
+        if (hexString == null || hexString.equals("")) {
+            return null;
+        }
+        hexString = hexString.toUpperCase();
+        int length = hexString.length() / 2;
+        char[] hexChars = hexString.toCharArray();
+        byte[] d = new byte[length];
+        for (int i = 0; i < length; i++) {
+            int pos = i * 2;
+            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
+
+        }
+        return d;
+    }
+
+    public static byte charToByte(char c) {
+        return (byte) "0123456789ABCDEF".indexOf(c);
+    }
+
+    /**
+     * 方法1,使用for循环
+     * @param list
+     * 将被转换为byte[]的List<Byte>
+     * @return
+     * 转换成的byte数组
+     */
+    //第二个参数是转换长度
+    public static byte[] listTobyte1(LinkedList<Byte> list, int cast_byte) {
+        if (list == null || list.size() < 0)
+            return null;
+        byte[] bytes = new byte[cast_byte];
+        int i = 0;
+        Iterator<Byte> iterator = list.iterator();
+        while (iterator.hasNext() && i<cast_byte) {
+            bytes[i] = iterator.next();
+            i++;
+        }
+        return bytes;
+    }
+
+    public static Bitmap nv21ToBitmap(byte[] nv21, int width, int height) {
+        Bitmap bitmap = null;
+        try {
+            YuvImage image = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
+            bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
+            stream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return bitmap;
+    }
+    //原文链接:https://blog.csdn.net/qq1137830424/article/details/81980673
+
+    public static NetworkInfo getConnectedType(Context context) {
+        if (context == null)
+            return null;
+
+        ConnectivityManager mConnectivityManager = (ConnectivityManager) context
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
+        if (mNetworkInfo != null && mNetworkInfo.isAvailable()) {
+            return mNetworkInfo;
+        }
+
+        return null;
+    }
+}

BIN
serialport/src/main/jniLibs/arm64-v8a/libserial_port.so


BIN
serialport/src/main/jniLibs/armeabi-v7a/libserial_port.so


+ 1 - 0
serialport/src/main/res/values/strings.xml

@@ -0,0 +1 @@
+<resources></resources>

+ 18 - 0
settings.gradle.kts

@@ -0,0 +1,18 @@
+pluginManagement {
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.name = "MyPort"
+include(":app")
+include(":serialport")