王育民 5 жил өмнө
commit
372563e1a4
49 өөрчлөгдсөн 2036 нэмэгдсэн , 0 устгасан
  1. 1 0
      app/.gitignore
  2. 29 0
      app/build.gradle
  3. 21 0
      app/proguard-rules.pro
  4. 26 0
      app/src/androidTest/java/cn/minbb/serial/ExampleInstrumentedTest.java
  5. 24 0
      app/src/main/AndroidManifest.xml
  6. 12 0
      app/src/main/java/cn/minbb/serial/App.java
  7. 173 0
      app/src/main/java/cn/minbb/serial/CrashHandler.java
  8. 205 0
      app/src/main/java/cn/minbb/serial/activities/MainActivity.java
  9. 58 0
      app/src/main/java/cn/minbb/serial/utils/FileUtil.java
  10. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  11. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  12. 76 0
      app/src/main/res/layout/activity_main.xml
  13. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  14. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  15. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  16. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  17. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  18. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  19. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  20. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  21. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  22. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  23. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  24. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  25. 6 0
      app/src/main/res/values/colors.xml
  26. 3 0
      app/src/main/res/values/strings.xml
  27. 11 0
      app/src/main/res/values/styles.xml
  28. 17 0
      app/src/test/java/cn/minbb/serial/ExampleUnitTest.java
  29. 1 0
      serialport/.gitignore
  30. 45 0
      serialport/build.gradle
  31. 21 0
      serialport/proguard-rules.pro
  32. 26 0
      serialport/src/androidTest/java/cn/minbb/serialport/ExampleInstrumentedTest.java
  33. 8 0
      serialport/src/main/AndroidManifest.xml
  34. 9 0
      serialport/src/main/cpp/CMakeLists.txt
  35. 167 0
      serialport/src/main/cpp/SerialPort.c
  36. 30 0
      serialport/src/main/cpp/SerialPort.h
  37. 46 0
      serialport/src/main/java/cn/minbb/serialport/Device.java
  38. 51 0
      serialport/src/main/java/cn/minbb/serialport/Driver.java
  39. 47 0
      serialport/src/main/java/cn/minbb/serialport/SerialPort.java
  40. 66 0
      serialport/src/main/java/cn/minbb/serialport/SerialPortFinder.java
  41. 250 0
      serialport/src/main/java/cn/minbb/serialport/SerialPortManager.java
  42. 18 0
      serialport/src/main/java/cn/minbb/serialport/listener/OnOpenSerialPortListener.java
  43. 21 0
      serialport/src/main/java/cn/minbb/serialport/listener/OnSerialPortDataListener.java
  44. 75 0
      serialport/src/main/java/cn/minbb/serialport/thread/SerialPortReadThread.java
  45. 11 0
      serialport/src/main/java/cn/minbb/serialport/utils/ByteUtil.java
  46. 138 0
      serialport/src/main/java/cn/minbb/serialport/utils/DataUtil.java
  47. 110 0
      serialport/src/main/java/cn/minbb/serialport/utils/HexUtil.java
  48. 3 0
      serialport/src/main/res/values/strings.xml
  49. 17 0
      serialport/src/test/java/cn/minbb/serialport/ExampleUnitTest.java

+ 1 - 0
app/.gitignore

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

+ 29 - 0
app/build.gradle

@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "cn.minbb.serialport"
+        minSdkVersion 16
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    implementation project(':serialport')
+}

+ 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/cn/minbb/serial/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package cn.minbb.serial;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.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.getTargetContext();
+
+        assertEquals("cn.minbb.serialport", appContext.getPackageName());
+    }
+}

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

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="cn.minbb.serial">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:name=".App"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="cn.minbb.serial.activities.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 12 - 0
app/src/main/java/cn/minbb/serial/App.java

@@ -0,0 +1,12 @@
+package cn.minbb.serial;
+
+import android.app.Application;
+
+public class App extends Application {
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        CrashHandler.getInstance().init(this);
+    }
+}

+ 173 - 0
app/src/main/java/cn/minbb/serial/CrashHandler.java

@@ -0,0 +1,173 @@
+package cn.minbb.serial;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Looper;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+public class CrashHandler implements Thread.UncaughtExceptionHandler {
+
+    private static final String TAG = "CrashHandler";
+    private Context mContext;
+    private static final String SDCARD_ROOT = Environment.getExternalStorageDirectory().toString();
+    private static CrashHandler mInstance = new CrashHandler();
+
+    private CrashHandler() {
+    }
+
+    /**
+     * 单例模式,保证只有一个CrashHandler实例存在
+     *
+     * @return
+     */
+    public static CrashHandler getInstance() {
+        return mInstance;
+    }
+
+    /**
+     * 异常发生时,系统回调的函数,我们在这里处理一些操作
+     */
+    @Override
+    public void uncaughtException(Thread thread, Throwable ex) {
+        // 将一些信息保存到 SDCard 中
+        saveInfoToSD(mContext, ex);
+        // 提示用户程序即将退出
+        showToast(mContext, "很抱歉,程序遭遇异常,即将退出!");
+//        try {
+//            thread.sleep(2000);
+//        } catch (InterruptedException e) {
+//            e.printStackTrace();
+//        }
+        // 完美退出程序方法
+        // android.os.Process.killProcess(android.os.Process.myPid());
+        // System.exit(1);
+    }
+
+    /**
+     * 为我们的应用程序设置自定义Crash处理
+     */
+    public void init(Context context) {
+        mContext = context;
+        Thread.setDefaultUncaughtExceptionHandler(this);
+    }
+
+    /**
+     * 显示提示信息,需要在线程中显示Toast
+     *
+     * @param context
+     * @param msg
+     */
+    private void showToast(final Context context, final String msg) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Looper.prepare();
+                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
+                Looper.loop();
+            }
+        }).start();
+    }
+
+    /**
+     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
+     *
+     * @param context
+     * @return
+     */
+    private HashMap<String, String> obtainSimpleInfo(Context context) {
+        HashMap<String, String> map = new HashMap<>();
+        PackageManager mPackageManager = context.getPackageManager();
+        PackageInfo mPackageInfo = null;
+        try {
+            mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        map.put("versionName", mPackageInfo.versionName);
+        map.put("versionCode", "" + mPackageInfo.versionCode);
+        map.put("MODEL", "" + Build.MODEL);
+        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
+        map.put("PRODUCT", "" + Build.PRODUCT);
+        return map;
+    }
+
+    /**
+     * 获取系统未捕捉的错误信息
+     *
+     * @param throwable
+     * @return
+     */
+    private String obtainExceptionInfo(Throwable throwable) {
+        StringWriter mStringWriter = new StringWriter();
+        PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+        throwable.printStackTrace(mPrintWriter);
+        mPrintWriter.close();
+        Log.e(TAG, mStringWriter.toString());
+        return mStringWriter.toString();
+    }
+
+    /**
+     * 保存获取的软件信息,设备信息和出错信息保存在 SDCard 中
+     *
+     * @param context
+     * @param ex
+     * @return
+     */
+    private String saveInfoToSD(Context context, Throwable ex) {
+        String fileName = null;
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            sb.append(key).append(" = ").append(value).append("\n");
+        }
+        sb.append(obtainExceptionInfo(ex));
+
+        // if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+        // File dir = new File(SDCARD_ROOT + File.separator + "crash" + File.separator);
+        File dir = new File("/sdcard/crash");
+        if (!dir.exists()) {
+            dir.mkdir();
+        }
+        try {
+            fileName = "/sdcard/crash" + System.currentTimeMillis() + ".log";
+            FileOutputStream fos = new FileOutputStream(fileName);
+            fos.write(sb.toString().getBytes());
+            fos.flush();
+            fos.close();
+        } catch (Exception e) {
+            showToast(context, "写异常日志时出现错误");
+        }
+        // showToast(context, sb.toString());
+        // }
+        return fileName;
+    }
+
+    /**
+     * 将毫秒数转换成yyyy-MM-dd-HH-mm-ss的格式
+     *
+     * @param milliseconds
+     * @return
+     */
+    private String parseTime(long milliseconds) {
+        System.setProperty("memberInfo.timezone", "Asia/Shanghai");
+        TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
+        TimeZone.setDefault(tz);
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return format.format(new Date(milliseconds));
+    }
+}

+ 205 - 0
app/src/main/java/cn/minbb/serial/activities/MainActivity.java

@@ -0,0 +1,205 @@
+package cn.minbb.serial.activities;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+
+import cn.minbb.serial.R;
+import cn.minbb.serialport.SerialPortManager;
+import cn.minbb.serialport.listener.OnOpenSerialPortListener;
+import cn.minbb.serialport.listener.OnSerialPortDataListener;
+import cn.minbb.serialport.utils.ByteUtil;
+
+public class MainActivity extends AppCompatActivity {
+
+    private String TAG = "MainActivity";
+    private Button openSerial, closeSerial;
+    private EditText editText;
+    private TextView textView;
+    private SerialPortManager serialPortManager;
+    private static String data;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        openSerial = findViewById(R.id.open_serial_port);
+        closeSerial = findViewById(R.id.close_serial_port);
+        closeSerial.setEnabled(false);
+        editText = findViewById(R.id.edit_text);
+        textView = findViewById(R.id.text_view);
+
+//        serialPortUtil = new SerialPortUtil(getApplicationContext());
+//        serialPortUtil.setOnDataReceiveListener(new SerialPortUtil.OnDataReceiveListener() {
+//            @Override
+//            public void onDataReceive(final String data, final int size) {
+//                runOnUiThread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        setText(data, size);
+//                        try {
+//                            serialPortUtil.sendSerialPort(data.getBytes(Charset.forName("UTF-8")));
+//                        } catch (IOException e) {
+//                            e.printStackTrace();
+//                            Toast.makeText(getApplicationContext(), "发送失败 = " + e.getMessage(), Toast.LENGTH_SHORT).show();
+//                        }
+//                    }
+//                });
+//            }
+//        });
+
+        serialPortManager = new SerialPortManager();
+        serialPortManager.setOnOpenSerialPortListener(new OnOpenSerialPortListener() {
+            @Override
+            public void onSuccess(File device) {
+                Toast.makeText(getApplicationContext(), "串口打开成功", Toast.LENGTH_SHORT).show();
+            }
+
+            @Override
+            public void onFail(File device, Status status) {
+                Toast.makeText(getApplicationContext(), "串口打开失败", Toast.LENGTH_SHORT).show();
+            }
+        });
+        serialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() {
+            @Override
+            public void onDataReceived(byte[] bytes, String data) {
+                MainActivity.data = data;
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        byte[] bytes = ByteUtil.addBytes(MainActivity.data.getBytes(), new byte[64]);
+//                        for (int i = bytes.length; i < 64; i++) {
+//                            bytes[i] = 0x0;
+//                        }
+                        setText(MainActivity.data, MainActivity.data.length());
+                        if (serialPortManager.sendBytes(bytes)) {
+                            Toast.makeText(getApplicationContext(), "发送成功 = " + MainActivity.data, Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(getApplicationContext(), "发送失败", Toast.LENGTH_SHORT).show();
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void onDataSent(byte[] bytes) {
+            }
+        });
+//        new SerialPortHelper(new SerialPortHelper.OnDataReceivedListener() {
+//            @Override
+//            public void onDataReceived(String data) {
+//                MainActivity.data = data;
+//                runOnUiThread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        byte[] bytes = ByteUtil.addBytes(MainActivity.data.getBytes(), new byte[64]);
+////                        for (int i = bytes.length; i < 64; i++) {
+////                            bytes[i] = 0x0;
+////                        }
+//                        setText(MainActivity.data, MainActivity.data.length());
+//                        if (serialPortManager.sendBytes(bytes)) {
+//                            Toast.makeText(getApplicationContext(), "发送成功 = " + MainActivity.data, Toast.LENGTH_SHORT).show();
+//                        } else {
+//                            Toast.makeText(getApplicationContext(), "发送失败", Toast.LENGTH_SHORT).show();
+//                        }
+//                    }
+//                });
+//            }
+//        }).getStringData(serialPortManager);
+        //////////////////////////////////////////////
+//        serialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() {
+//            @Override
+//            public void onDataReceived(final byte[] bytes) {
+//                runOnUiThread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        setText(new String(bytes), bytes.length);
+//                    }
+//                });
+//            }
+//
+//            @Override
+//            public void onDataSent(final byte[] bytes) {
+//                runOnUiThread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        setText(new String(bytes), bytes.length);
+//                    }
+//                });
+//            }
+//        });
+//        serialPortManager.openSerialPort(new File("/dev/ttyS1"), 115200);
+    }
+
+    private void setText(String data, int size) {
+        String string = "data = " + data + ": " + size + "\n" + textView.getText();
+        textView.setText(string);
+    }
+
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.open_serial_port:
+//                if (serialPortUtil.serialPortStatus) {
+//                    Toast.makeText(getApplicationContext(), "串口已经打开", Toast.LENGTH_SHORT).show();
+//                } else {
+//                    if (null != serialPortUtil.openSerialPort()) {
+//                        openSerial.setEnabled(false);
+//                        closeSerial.setEnabled(true);
+//                    }
+//                }
+                if (serialPortManager.openSerialPort(new File("/dev/ttyS1"), 115200)) {
+                    Toast.makeText(getApplicationContext(), "串口打开成功", Toast.LENGTH_SHORT).show();
+                    openSerial.setEnabled(false);
+                    closeSerial.setEnabled(true);
+                } else {
+                    Toast.makeText(getApplicationContext(), "串口打开失败", Toast.LENGTH_SHORT).show();
+                }
+                break;
+            case R.id.close_serial_port:
+//                if (serialPortUtil.serialPortStatus) {
+//                    serialPortUtil.closeSerialPort();
+//                    openSerial.setEnabled(true);
+//                    closeSerial.setEnabled(false);
+//                }
+//                Toast.makeText(getApplicationContext(), "串口已关闭", Toast.LENGTH_SHORT).show();
+                serialPortManager.closeSerialPort();
+                openSerial.setEnabled(true);
+                closeSerial.setEnabled(false);
+                break;
+            case R.id.send:
+                byte[] reportID = {0x0};
+                byte[] data = ByteUtil.addBytes(reportID, editText.getText().toString().getBytes());
+                boolean sendBytes = serialPortManager.sendBytes(data);
+                if (sendBytes) {
+                    Toast.makeText(getApplicationContext(), "发送成功", Toast.LENGTH_SHORT).show();
+                } else {
+                    Toast.makeText(getApplicationContext(), "发送失败", Toast.LENGTH_SHORT).show();
+                }
+//                if (serialPortUtil.serialPortStatus) {
+//                    try {
+//                        serialPortUtil.sendSerialPort(editText.getText().toString());
+//                    } catch (IOException e) {
+//                        e.printStackTrace();
+//                        Log.e(TAG, "sendSerialPort: 串口数据发送失败:" + e.toString());
+//                        Toast.makeText(getApplicationContext(), "数据发送失败:" + e.toString(), Toast.LENGTH_SHORT).show();
+//                    }
+//                } else {
+//                    Toast.makeText(getApplicationContext(), "请先打开串口", Toast.LENGTH_SHORT).show();
+//                }
+                break;
+            case R.id.clearSend:
+                editText.setText("");
+                break;
+            case R.id.clearText:
+                textView.setText("");
+                break;
+        }
+    }
+}

+ 58 - 0
app/src/main/java/cn/minbb/serial/utils/FileUtil.java

@@ -0,0 +1,58 @@
+package cn.minbb.serial.utils;
+
+import android.os.Environment;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class FileUtil {
+
+    // 路径为 /sdcard/[appName]/log.txt
+    private static final String appName = "A";
+    private static File file;
+
+    static {
+        file = new File(Environment.getExternalStorageDirectory(), appName);
+        if (!file.exists()) {
+            file.mkdirs();
+        }
+        file = new File(file, "log.txt");
+    }
+
+    /**
+     * 将文本追加写入到文件
+     */
+    public static void setAppendFile(String value) {
+        try {
+            FileWriter fw = new FileWriter(file, true);
+            BufferedWriter bw = new BufferedWriter(fw);
+            PrintWriter printWriter = new PrintWriter(bw);
+            printWriter.print(SimpleDateFormat.getDateTimeInstance().format(new Date()) + ":\n");
+            printWriter.println(value);
+            printWriter.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 将异常信息写入到文件
+     */
+    public static void setAppendFile(Throwable ex) {
+        // Throwable ex
+        try {
+            FileWriter fw = new FileWriter(file, true);
+            BufferedWriter bw = new BufferedWriter(fw);
+            PrintWriter printWriter = new PrintWriter(bw);
+            printWriter.println(SimpleDateFormat.getDateTimeInstance().format(new Date()) + ":\n");
+            ex.printStackTrace(printWriter);
+            printWriter.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

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

@@ -0,0 +1,34 @@
+<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:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                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="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</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="#008577"
+        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>

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

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.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"
+    android:padding="16dp"
+    tools:context=".activities.MainActivity">
+
+    <Button
+        android:id="@+id/open_serial_port"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onClick"
+        android:text="打开串口"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/close_serial_port"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="onClick"
+        android:text="关闭串口"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/open_serial_port" />
+
+    <EditText
+        android:id="@+id/edit_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/close_serial_port" />
+
+    <Button
+        android:id="@+id/send"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:onClick="onClick"
+        android:text="发送"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/edit_text" />
+
+    <Button
+        android:id="@+id/clearSend"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:onClick="onClick"
+        android:text="清除发送区"
+        app:layout_constraintEnd_toStartOf="@id/clearText"
+        app:layout_constraintTop_toBottomOf="@id/edit_text" />
+
+    <Button
+        android:id="@+id/clearText"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:onClick="onClick"
+        android:text="清除接收区"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/edit_text" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/send">
+
+        <TextView
+            android:id="@+id/text_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </ScrollView>
+
+</android.support.constraint.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-xxxhdpi/ic_launcher.png


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


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

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+</resources>

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

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

+ 11 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>

+ 17 - 0
app/src/test/java/cn/minbb/serial/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package cn.minbb.serial;
+
+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);
+    }
+}

+ 1 - 0
serialport/.gitignore

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

+ 45 - 0
serialport/build.gradle

@@ -0,0 +1,45 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+    buildToolsVersion '28.0.3'
+
+    defaultConfig {
+        minSdkVersion 16
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        externalNativeBuild {
+            cmake {
+                arguments '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    externalNativeBuild {
+        cmake {
+            path "src/main/cpp/CMakeLists.txt"
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+}

+ 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

+ 26 - 0
serialport/src/androidTest/java/cn/minbb/serialport/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package cn.minbb.serialport;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.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.getTargetContext();
+
+        assertEquals("cn.minbb.serialport.test", appContext.getPackageName());
+    }
+}

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

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="cn.minbb.serialport">
+
+    <application
+        android:allowBackup="true"
+        android:label="@string/app_name"
+        android:supportsRtl="true" />
+</manifest>

+ 9 - 0
serialport/src/main/cpp/CMakeLists.txt

@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+add_library(SerialPort SHARED
+            SerialPort.c)
+
+# Include libraries needed for libserial_port lib
+target_link_libraries(SerialPort
+                      android
+                      log)

+ 167 - 0
serialport/src/main/cpp/SerialPort.c

@@ -0,0 +1,167 @@
+/*
+ * Copyright 2009-2011 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.
+ */
+
+#include <termios.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <jni.h>
+
+#include "SerialPort.h"
+
+#include "android/log.h"
+static const char *TAG="serial_port";
+#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
+#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
+#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
+
+static speed_t getBaudrate(jint baudrate)
+{
+	switch(baudrate) {
+	case 0: return B0;
+	case 50: return B50;
+	case 75: return B75;
+	case 110: return B110;
+	case 134: return B134;
+	case 150: return B150;
+	case 200: return B200;
+	case 300: return B300;
+	case 600: return B600;
+	case 1200: return B1200;
+	case 1800: return B1800;
+	case 2400: return B2400;
+	case 4800: return B4800;
+	case 9600: return B9600;
+	case 19200: return B19200;
+	case 38400: return B38400;
+	case 57600: return B57600;
+	case 115200: return B115200;
+	case 230400: return B230400;
+	case 460800: return B460800;
+	case 500000: return B500000;
+	case 576000: return B576000;
+	case 921600: return B921600;
+	case 1000000: return B1000000;
+	case 1152000: return B1152000;
+	case 1500000: return B1500000;
+	case 2000000: return B2000000;
+	case 2500000: return B2500000;
+	case 3000000: return B3000000;
+	case 3500000: return B3500000;
+	case 4000000: return B4000000;
+	default: return -1;
+	}
+}
+
+/*
+ * Class:     android_serialport_SerialPort
+ * Method:    open
+ * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
+ */
+JNIEXPORT jobject JNICALL Java_cn_minbb_serialport_SerialPort_open
+  (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
+{
+	int fd;
+	speed_t speed;
+	jobject mFileDescriptor;
+
+	/* Check arguments */
+	{
+		speed = getBaudrate(baudrate);
+		if (speed == -1) {
+			/* TODO: throw an exception */
+			LOGE("Invalid baudrate");
+			return NULL;
+		}
+	}
+
+	/* Opening device */
+	{
+		jboolean iscopy;
+		const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
+		LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
+		fd = open(path_utf, O_RDWR | flags);
+		LOGD("open() fd = %d", fd);
+		(*env)->ReleaseStringUTFChars(env, path, path_utf);
+		if (fd == -1)
+		{
+			/* Throw an exception */
+			LOGE("Cannot open port");
+			/* TODO: throw an exception */
+			return NULL;
+		}
+	}
+
+	/* Configure device */
+	{
+		struct termios cfg;
+		LOGD("Configuring serial port");
+		if (tcgetattr(fd, &cfg))
+		{
+			LOGE("tcgetattr() failed");
+			close(fd);
+			/* TODO: throw an exception */
+			return NULL;
+		}
+
+		cfmakeraw(&cfg);
+		cfsetispeed(&cfg, speed);
+		cfsetospeed(&cfg, speed);
+
+		if (tcsetattr(fd, TCSANOW, &cfg))
+		{
+			LOGE("tcsetattr() failed");
+			close(fd);
+			/* TODO: throw an exception */
+			return NULL;
+		}
+	}
+
+	/* Create a corresponding file descriptor */
+	{
+		jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
+		jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
+		jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
+		mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
+		(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
+	}
+
+	return mFileDescriptor;
+}
+
+/*
+ * Class:     cedric_serial_SerialPort
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_cn_minbb_serialport_SerialPort_close
+  (JNIEnv *env, jobject thiz)
+{
+	jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
+	jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
+
+	jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
+	jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
+
+	jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
+	jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
+
+	LOGD("close(fd = %d)", descriptor);
+	close(descriptor);
+}
+

+ 30 - 0
serialport/src/main/cpp/SerialPort.h

@@ -0,0 +1,30 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class android_serialport_api_SerialPort */
+
+#ifndef _Included_qingwei_kong_serialportlibrary_SerialPort
+#define _Included_qingwei_kong_serialportlibrary_SerialPort
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Class:     android_serialport_api_SerialPort
+ * Method:    open
+ * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
+ */
+JNIEXPORT jobject JNICALL Java_com_kongqw_serialportlibrary_SerialPort_open
+  (JNIEnv *, jclass, jstring, jint, jint);
+
+/*
+ * Class:     android_serialport_api_SerialPort
+ * Method:    close
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_com_kongqw_serialportlibrary_SerialPort_close
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 46 - 0
serialport/src/main/java/cn/minbb/serialport/Device.java

@@ -0,0 +1,46 @@
+package cn.minbb.serialport;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Device
+ */
+public class Device implements Serializable{
+
+    private static final String TAG = Device.class.getSimpleName();
+
+    private String name;
+    private String root;
+    private File file;
+
+    public Device(String name, String root, File file) {
+        this.name = name;
+        this.root = root;
+        this.file = file;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getRoot() {
+        return root;
+    }
+
+    public void setRoot(String root) {
+        this.root = root;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File path) {
+        this.file = file;
+    }
+}

+ 51 - 0
serialport/src/main/java/cn/minbb/serialport/Driver.java

@@ -0,0 +1,51 @@
+package cn.minbb.serialport;
+
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * Driver
+ */
+public class Driver {
+
+    private static final String TAG = Driver.class.getSimpleName();
+
+    private String mDriverName;
+    private String mDeviceRoot;
+
+    public Driver(String name, String root) {
+        mDriverName = name;
+        mDeviceRoot = root;
+    }
+
+    public ArrayList<File> getDevices() {
+        ArrayList<File> devices = new ArrayList<>();
+        File dev = new File("/dev");
+
+        if (!dev.exists()) {
+            Log.i(TAG, "getDevices: " + dev.getAbsolutePath() + " 不存在");
+            return devices;
+        }
+        if (!dev.canRead()) {
+            Log.i(TAG, "getDevices: " + dev.getAbsolutePath() + " 没有读取权限");
+            return devices;
+        }
+
+        File[] files = dev.listFiles();
+
+        int i;
+        for (i = 0; i < files.length; i++) {
+            if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
+                Log.d(TAG, "Found new device: " + files[i]);
+                devices.add(files[i]);
+            }
+        }
+        return devices;
+    }
+
+    public String getName() {
+        return mDriverName;
+    }
+}

+ 47 - 0
serialport/src/main/java/cn/minbb/serialport/SerialPort.java

@@ -0,0 +1,47 @@
+package cn.minbb.serialport;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class SerialPort {
+
+    static {
+        System.loadLibrary("SerialPort");
+    }
+
+    private static final String TAG = SerialPort.class.getSimpleName();
+
+    /**
+     * 文件设置最高权限 777 可读 可写 可执行
+     *
+     * @param file 文件
+     * @return 权限修改是否成功
+     */
+    boolean chmod777(File file) {
+        if (null == file || !file.exists()) {
+            // 文件不存在
+            return false;
+        }
+        try {
+            // 获取ROOT权限
+            Process su = Runtime.getRuntime().exec("/system/bin/su");
+            // 修改文件属性为 [可读 可写 可执行]
+            String cmd = "chmod 777 " + file.getAbsolutePath() + "\n" + "exit\n";
+            su.getOutputStream().write(cmd.getBytes());
+            if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) {
+                return true;
+            }
+        } catch (IOException | InterruptedException e) {
+            // 没有ROOT权限
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    // 打开串口
+    protected native FileDescriptor open(String path, int baudRate, int flags);
+
+    // 关闭串口
+    protected native void close();
+}

+ 66 - 0
serialport/src/main/java/cn/minbb/serialport/SerialPortFinder.java

@@ -0,0 +1,66 @@
+package cn.minbb.serialport;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+
+public class SerialPortFinder {
+
+    private static final String TAG = SerialPortFinder.class.getSimpleName();
+    private static final String DRIVERS_PATH = "/proc/tty/drivers";
+    private static final String SERIAL_FIELD = "serial";
+
+    public SerialPortFinder() {
+        File file = new File(DRIVERS_PATH);
+        boolean b = file.canRead();
+        Log.i(TAG, "SerialPortFinder: file.canRead() = " + b);
+    }
+
+    /**
+     * 获取 Drivers
+     *
+     * @return Drivers
+     * @throws IOException IOException
+     */
+    private ArrayList<Driver> getDrivers() throws IOException {
+        ArrayList<Driver> drivers = new ArrayList<>();
+        LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH));
+        String readLine;
+        while ((readLine = lineNumberReader.readLine()) != null) {
+            String driverName = readLine.substring(0, 0x15).trim();
+            String[] fields = readLine.split(" +");
+            if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) {
+                Log.d(TAG, "Found new driver " + driverName + " on " + fields[fields.length - 4]);
+                drivers.add(new Driver(driverName, fields[fields.length - 4]));
+            }
+        }
+        return drivers;
+    }
+
+    /**
+     * 获取串口
+     *
+     * @return 串口
+     */
+    public ArrayList<Device> getDevices() {
+        ArrayList<Device> devices = new ArrayList<>();
+        try {
+            ArrayList<Driver> drivers = getDrivers();
+            for (Driver driver : drivers) {
+                String driverName = driver.getName();
+                ArrayList<File> driverDevices = driver.getDevices();
+                for (File file : driverDevices) {
+                    String devicesName = file.getName();
+                    devices.add(new Device(devicesName, driverName, file));
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return devices;
+    }
+}

+ 250 - 0
serialport/src/main/java/cn/minbb/serialport/SerialPortManager.java

@@ -0,0 +1,250 @@
+package cn.minbb.serialport;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import cn.minbb.serialport.listener.OnOpenSerialPortListener;
+import cn.minbb.serialport.listener.OnSerialPortDataListener;
+import cn.minbb.serialport.thread.SerialPortReadThread;
+
+/**
+ * SerialPortManager
+ */
+public class SerialPortManager extends SerialPort {
+
+    private static final String TAG = SerialPortManager.class.getSimpleName();
+    private FileInputStream mFileInputStream;
+    private FileOutputStream mFileOutputStream;
+    private FileDescriptor mFd;
+    private OnOpenSerialPortListener mOnOpenSerialPortListener;
+    private OnSerialPortDataListener mOnSerialPortDataListener;
+
+    private HandlerThread mSendingHandlerThread;
+    private Handler mSendingHandler;
+    private SerialPortReadThread mSerialPortReadThread;
+
+    /**
+     * 打开串口
+     *
+     * @param device   串口设备
+     * @param baudRate 波特率
+     * @return 打开是否成功
+     */
+    public boolean openSerialPort(File device, int baudRate) {
+        Log.i(TAG, "openSerialPort: " + String.format("打开串口 %s  波特率 %s", device.getPath(), baudRate));
+        // 校验串口权限
+        if (!device.canRead() || !device.canWrite()) {
+            boolean chmod777 = chmod777(device);
+            if (!chmod777) {
+                Log.i(TAG, "openSerialPort: 没有读写权限");
+                if (null != mOnOpenSerialPortListener) {
+                    mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.NO_READ_WRITE_PERMISSION);
+                }
+                return false;
+            }
+        }
+
+        try {
+            mFd = open(device.getAbsolutePath(), baudRate, 0);
+            mFileInputStream = new FileInputStream(mFd);
+            mFileOutputStream = new FileOutputStream(mFd);
+            Log.i(TAG, "openSerialPort: 串口已经打开 " + mFd);
+            if (null != mOnOpenSerialPortListener) {
+                mOnOpenSerialPortListener.onSuccess(device);
+            }
+            // 开启发送消息的线程
+            startSendThread();
+            // 开启接收消息的线程
+            startReadThread();
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            if (null != mOnOpenSerialPortListener) {
+                mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.OPEN_FAIL);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 关闭串口
+     */
+    public void closeSerialPort() {
+        if (null != mFd) {
+            close();
+            mFd = null;
+        }
+        // 停止发送消息的线程
+        stopSendThread();
+        // 停止接收消息的线程
+        stopReadThread();
+
+        if (null != mFileInputStream) {
+            try {
+                mFileInputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            mFileInputStream = null;
+        }
+
+        if (null != mFileOutputStream) {
+            try {
+                mFileOutputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            mFileOutputStream = null;
+        }
+
+        mOnOpenSerialPortListener = null;
+        mOnSerialPortDataListener = null;
+    }
+
+    /**
+     * 添加打开串口监听
+     *
+     * @param listener listener
+     * @return SerialPortManager
+     */
+    public SerialPortManager setOnOpenSerialPortListener(OnOpenSerialPortListener listener) {
+        mOnOpenSerialPortListener = listener;
+        return this;
+    }
+
+    /**
+     * 添加数据通信监听
+     *
+     * @param listener listener
+     * @return SerialPortManager
+     */
+    public SerialPortManager setOnSerialPortDataListener(OnSerialPortDataListener listener) {
+        mOnSerialPortDataListener = listener;
+        return this;
+    }
+
+    /**
+     * 开启发送消息的线程
+     */
+    private void startSendThread() {
+        // 开启发送消息的线程
+        mSendingHandlerThread = new HandlerThread("mSendingHandlerThread");
+        mSendingHandlerThread.start();
+        // Handler
+        mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                byte[] sendBytes = (byte[]) msg.obj;
+
+                if (null != mFileOutputStream && null != sendBytes && 0 < sendBytes.length) {
+                    try {
+                        mFileOutputStream.write(sendBytes);
+                        if (null != mOnSerialPortDataListener) {
+                            mOnSerialPortDataListener.onDataSent(sendBytes);
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * 停止发送消息线程
+     */
+    private void stopSendThread() {
+        mSendingHandler = null;
+        if (null != mSendingHandlerThread) {
+            mSendingHandlerThread.interrupt();
+            mSendingHandlerThread.quit();
+            mSendingHandlerThread = null;
+        }
+    }
+
+    /**
+     * 开启接收消息的线程
+     */
+    private static String data = "";
+    private static boolean over = false;
+    private final long TIMEOUT = 500;  // 超时时间
+
+    private void startReadThread() {
+        mSerialPortReadThread = new SerialPortReadThread(mFileInputStream) {
+            @Override
+            public void onDataReceived(final byte[] bytes) {
+                // if (null != mOnSerialPortDataListener) {
+                //     mOnSerialPortDataListener.onDataReceived(bytes);
+                // }
+                // 收到数据,计时开始
+                if (data.trim().isEmpty()) {
+                    new Timer().schedule(new TimerTask() {
+                        @Override
+                        public void run() {
+                            String s = data.trim();
+                            if (!s.isEmpty()) {
+                                over = false;
+                                mOnSerialPortDataListener.onDataReceived(bytes, s);
+                                data = "";
+                            }
+                        }
+                    }, TIMEOUT);
+                }
+                int blockSize = bytes.length;
+                byte[] byteBlock = new byte[blockSize];
+                for (int i = 0; i < blockSize; i++) {
+                    if (bytes[i] == '*') {
+                        over = true;
+                        break;
+                    }
+                    byteBlock[i] = bytes[i];
+                }
+                String block = new String(byteBlock);
+                data += block;
+                // 收到数据结束符
+                if (over && !data.isEmpty()) {
+                    over = false;
+                    mOnSerialPortDataListener.onDataReceived(bytes, data.trim());
+                    data = "";
+                }
+            }
+        };
+        mSerialPortReadThread.start();
+    }
+
+    /**
+     * 停止接收消息的线程
+     */
+    private void stopReadThread() {
+        if (null != mSerialPortReadThread) {
+            mSerialPortReadThread.release();
+        }
+    }
+
+    /**
+     * 发送数据
+     *
+     * @param sendBytes 发送数据
+     * @return 发送是否成功
+     */
+    public boolean sendBytes(byte[] sendBytes) {
+        if (null != mFd && null != mFileInputStream && null != mFileOutputStream) {
+            if (null != mSendingHandler) {
+                Message message = Message.obtain();
+                message.obj = sendBytes;
+                return mSendingHandler.sendMessage(message);
+            }
+        }
+        return false;
+    }
+}

+ 18 - 0
serialport/src/main/java/cn/minbb/serialport/listener/OnOpenSerialPortListener.java

@@ -0,0 +1,18 @@
+package cn.minbb.serialport.listener;
+
+import java.io.File;
+
+/**
+ * 打开串口监听
+ */
+public interface OnOpenSerialPortListener {
+
+    void onSuccess(File device);
+
+    void onFail(File device, Status status);
+
+    enum Status {
+        NO_READ_WRITE_PERMISSION,
+        OPEN_FAIL
+    }
+}

+ 21 - 0
serialport/src/main/java/cn/minbb/serialport/listener/OnSerialPortDataListener.java

@@ -0,0 +1,21 @@
+package cn.minbb.serialport.listener;
+
+/**
+ * 串口消息监听
+ */
+public interface OnSerialPortDataListener {
+
+    /**
+     * 数据接收
+     *
+     * @param bytes 接收到的数据
+     */
+    void onDataReceived(byte[] bytes, String data);
+
+    /**
+     * 数据发送
+     *
+     * @param bytes 发送的数据
+     */
+    void onDataSent(byte[] bytes);
+}

+ 75 - 0
serialport/src/main/java/cn/minbb/serialport/thread/SerialPortReadThread.java

@@ -0,0 +1,75 @@
+package cn.minbb.serialport.thread;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 串口消息读取线程
+ */
+public abstract class SerialPortReadThread extends Thread {
+
+    public abstract void onDataReceived(byte[] bytes);
+
+    private static final String TAG = SerialPortReadThread.class.getSimpleName();
+    private InputStream mInputStream;
+    private byte[] mReadBuffer;
+
+    public SerialPortReadThread(InputStream inputStream) {
+        mInputStream = inputStream;
+        mReadBuffer = new byte[1024];
+    }
+
+    @Override
+    public void run() {
+        super.run();
+
+        while (!isInterrupted()) {
+            try {
+                if (null == mInputStream) {
+                    return;
+                }
+
+                Log.i(TAG, "run: ");
+                int size = mInputStream.read(mReadBuffer);
+
+                if (-1 == size || 0 >= size) {
+                    return;
+                }
+
+                byte[] readBytes = new byte[size];
+
+                System.arraycopy(mReadBuffer, 0, readBytes, 0, size);
+
+                Log.i(TAG, "run: readBytes = " + new String(readBytes));
+                onDataReceived(readBytes);
+
+            } catch (IOException e) {
+                e.printStackTrace();
+                return;
+            }
+        }
+    }
+
+    @Override
+    public synchronized void start() {
+        super.start();
+    }
+
+    /**
+     * 关闭线程 释放资源
+     */
+    public void release() {
+        interrupt();
+
+        if (null != mInputStream) {
+            try {
+                mInputStream.close();
+                mInputStream = null;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 11 - 0
serialport/src/main/java/cn/minbb/serialport/utils/ByteUtil.java

@@ -0,0 +1,11 @@
+package cn.minbb.serialport.utils;
+
+public class ByteUtil {
+
+    public static byte[] addBytes(byte[] data1, byte[] data2) {
+        byte[] data3 = new byte[data1.length + data2.length];
+        System.arraycopy(data1, 0, data3, 0, data1.length);
+        System.arraycopy(data2, 0, data3, data1.length, data2.length);
+        return data3;
+    }
+}

+ 138 - 0
serialport/src/main/java/cn/minbb/serialport/utils/DataUtil.java

@@ -0,0 +1,138 @@
+package cn.minbb.serialport.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 串口数据转换工具类
+ */
+public class DataUtil {
+    //-------------------------------------------------------
+    // 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数
+    public static int isOdd(int num) {
+        return num & 1;
+    }
+
+    //-------------------------------------------------------
+    // Hex字符串转int
+    public static int HexToInt(String inHex) {
+        return Integer.parseInt(inHex, 16);
+    }
+
+    public static String IntToHex(int intHex){
+        return Integer.toHexString(intHex);
+    }
+
+    //-------------------------------------------------------
+    // Hex字符串转byte
+    public static byte HexToByte(String inHex) {
+        return (byte) Integer.parseInt(inHex, 16);
+    }
+
+    //-------------------------------------------------------
+    // 1字节转2个Hex字符
+    public static String Byte2Hex(Byte inByte) {
+        return String.format("%02x", new Object[]{inByte}).toUpperCase();
+    }
+
+    //-------------------------------------------------------
+    // 字节数组转转hex字符串
+    public static String ByteArrToHex(byte[] inBytArr) {
+        StringBuilder strBuilder = new StringBuilder();
+        for (byte valueOf : inBytArr) {
+            strBuilder.append(Byte2Hex(Byte.valueOf(valueOf)));
+            strBuilder.append(" ");
+        }
+        return strBuilder.toString();
+    }
+
+    //-------------------------------------------------------
+    // 字节数组转转hex字符串,可选长度
+    public static String ByteArrToHex(byte[] inBytArr, int offset, int byteCount) {
+        StringBuilder strBuilder = new StringBuilder();
+        int j = byteCount;
+        for (int i = offset; i < j; i++) {
+            strBuilder.append(Byte2Hex(Byte.valueOf(inBytArr[i])));
+        }
+        return strBuilder.toString();
+    }
+
+    //-------------------------------------------------------
+    // 转hex字符串转字节数组
+    public static byte[] HexToByteArr(String inHex) {
+        byte[] result;
+        int hexlen = inHex.length();
+        if (isOdd(hexlen) == 1) {
+            hexlen++;
+            result = new byte[(hexlen / 2)];
+            inHex = "0" + inHex;
+        } else {
+            result = new byte[(hexlen / 2)];
+        }
+        int j = 0;
+        for (int i = 0; i < hexlen; i += 2) {
+            result[j] = HexToByte(inHex.substring(i, i + 2));
+            j++;
+        }
+        return result;
+    }
+
+    /**
+     * 按照指定长度切割字符串
+     *
+     * @param inputString 需要切割的源字符串
+     * @param length      指定的长度
+     * @return
+     */
+    public static List<String> getDivLines(String inputString, int length) {
+        List<String> divList = new ArrayList<>();
+        int remainder = (inputString.length()) % length;
+        // 一共要分割成几段
+        int number = (int) Math.floor((inputString.length()) / length);
+        for (int index = 0; index < number; index++) {
+            String childStr = inputString.substring(index * length, (index + 1) * length);
+            divList.add(childStr);
+        }
+        if (remainder > 0) {
+            String cStr = inputString.substring(number * length, inputString.length());
+            divList.add(cStr);
+        }
+        return divList;
+    }
+
+    /**
+     * 计算长度,两个字节长度
+     *
+     * @param val value
+     * @return 结果
+     */
+    public static String twoByte(String val) {
+        if (val.length() > 4) {
+            val = val.substring(0, 4);
+        } else {
+            int l = 4 - val.length();
+            for (int i = 0; i < l; i++) {
+                val = "0" + val;
+            }
+        }
+        return val;
+    }
+
+    /**
+     * 校验和
+     *
+     * @param cmd 指令
+     * @return 结果
+     */
+    public static String sum(String cmd) {
+        List<String> cmdList = DataUtil.getDivLines(cmd, 2);
+        int sumInt = 0;
+        for (String c : cmdList) {
+            sumInt += DataUtil.HexToInt(c);
+        }
+        String sum = DataUtil.IntToHex(sumInt);
+        sum = DataUtil.twoByte(sum);
+        cmd += sum;
+        return cmd.toUpperCase();
+    }
+}

+ 110 - 0
serialport/src/main/java/cn/minbb/serialport/utils/HexUtil.java

@@ -0,0 +1,110 @@
+package cn.minbb.serialport.utils;
+
+public class HexUtil {
+
+    private static final char[] DIGITS_LOWER;
+    private static final char[] DIGITS_UPPER;
+
+    static {
+        char[] arrayOfChar1 = new char[16];
+        arrayOfChar1[0] = 48;
+        arrayOfChar1[1] = 49;
+        arrayOfChar1[2] = 50;
+        arrayOfChar1[3] = 51;
+        arrayOfChar1[4] = 52;
+        arrayOfChar1[5] = 53;
+        arrayOfChar1[6] = 54;
+        arrayOfChar1[7] = 55;
+        arrayOfChar1[8] = 56;
+        arrayOfChar1[9] = 57;
+        arrayOfChar1[10] = 97;
+        arrayOfChar1[11] = 98;
+        arrayOfChar1[12] = 99;
+        arrayOfChar1[13] = 100;
+        arrayOfChar1[14] = 101;
+        arrayOfChar1[15] = 102;
+        DIGITS_LOWER = arrayOfChar1;
+        char[] arrayOfChar2 = new char[16];
+        arrayOfChar2[0] = 48;
+        arrayOfChar2[1] = 49;
+        arrayOfChar2[2] = 50;
+        arrayOfChar2[3] = 51;
+        arrayOfChar2[4] = 52;
+        arrayOfChar2[5] = 53;
+        arrayOfChar2[6] = 54;
+        arrayOfChar2[7] = 55;
+        arrayOfChar2[8] = 56;
+        arrayOfChar2[9] = 57;
+        arrayOfChar2[10] = 65;
+        arrayOfChar2[11] = 66;
+        arrayOfChar2[12] = 67;
+        arrayOfChar2[13] = 68;
+        arrayOfChar2[14] = 69;
+        arrayOfChar2[15] = 70;
+        DIGITS_UPPER = arrayOfChar2;
+    }
+
+    public static byte[] decodeHex(char[] paramArrayOfChar) {
+        int i = paramArrayOfChar.length;
+        if ((i & 0x1) != 0)
+            throw new RuntimeException("Odd number of characters.");
+        byte[] arrayOfByte = new byte[i >> 1];
+        int j = 0;
+        int k = 0;
+        while (k < i) {
+            int l = toDigit(paramArrayOfChar[k], k) << 4;
+            int i1 = k + 1;
+            int i2 = l | toDigit(paramArrayOfChar[i1], i1);
+            k = i1 + 1;
+            arrayOfByte[j] = (byte) (i2 & 0xFF);
+            ++j;
+        }
+        return arrayOfByte;
+    }
+
+    public static char[] encodeHex(byte[] paramArrayOfByte) {
+        return encodeHex(paramArrayOfByte, true);
+    }
+
+    public static char[] encodeHex(byte[] paramArrayOfByte, boolean paramBoolean) {
+        if (paramBoolean) ;
+        for (char[] arrayOfChar = DIGITS_LOWER; ; arrayOfChar = DIGITS_UPPER)
+            return encodeHex(paramArrayOfByte, arrayOfChar);
+    }
+
+    protected static char[] encodeHex(byte[] paramArrayOfByte, char[] paramArrayOfChar) {
+        int i = paramArrayOfByte.length;
+        char[] arrayOfChar = new char[i << 1];
+        int j = 0;
+        int k = 0;
+        while (j < i) {
+            int l = k + 1;
+            arrayOfChar[k] = paramArrayOfChar[((0xF0 & paramArrayOfByte[j]) >>> 4)];
+            k = l + 1;
+            arrayOfChar[l] = paramArrayOfChar[(0xF & paramArrayOfByte[j])];
+            ++j;
+        }
+        return arrayOfChar;
+    }
+
+    public static String encodeHexStr(byte[] paramArrayOfByte) {
+        return encodeHexStr(paramArrayOfByte, true);
+    }
+
+    public static String encodeHexStr(byte[] paramArrayOfByte, boolean paramBoolean) {
+        if (paramBoolean) ;
+        for (char[] arrayOfChar = DIGITS_LOWER; ; arrayOfChar = DIGITS_UPPER)
+            return encodeHexStr(paramArrayOfByte, arrayOfChar);
+    }
+
+    protected static String encodeHexStr(byte[] paramArrayOfByte, char[] paramArrayOfChar) {
+        return new String(encodeHex(paramArrayOfByte, paramArrayOfChar));
+    }
+
+    protected static int toDigit(char paramChar, int paramInt) {
+        int i = Character.digit(paramChar, 16);
+        if (i == -1)
+            throw new RuntimeException("Illegal hexadecimal character " + paramChar + " at index " + paramInt);
+        return i;
+    }
+}

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

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

+ 17 - 0
serialport/src/test/java/cn/minbb/serialport/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package cn.minbb.serialport;
+
+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);
+    }
+}