diff --git a/.idea/misc.xml b/.idea/misc.xml
index 3a6be18..9a451dd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -143,6 +143,8 @@
+
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1215_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1215_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1215_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1356_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1356_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1356_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1836_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1836_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_1836_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2116_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2116_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2116_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2399_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2399_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_2399_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_418_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_418_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_418_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_854_timing.txt b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_854_timing.txt
new file mode 100644
index 0000000..9708df4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/generate_cxx_metadata_854_timing.txt
@@ -0,0 +1,2 @@
+# C/C++ build system timings
+
diff --git a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/metadata_generation_record.json b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/metadata_generation_record.json
index 0dfbfff..274c5c4 100644
--- a/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/metadata_generation_record.json
+++ b/OpenCV/build/intermediates/cxx/Debug/3w3p486t/meta/x86/metadata_generation_record.json
@@ -11,7 +11,7 @@
"fieldsDescending": {}
},
"memoizedSize": -1,
- "memoizedHashCode": 163502203
+ "memoizedHashCode": -1078900285
},
{
"level_": 0,
@@ -25,7 +25,7 @@
"fieldsDescending": {}
},
"memoizedSize": -1,
- "memoizedHashCode": -2106990726
+ "memoizedHashCode": 945574082
},
{
"level_": 0,
@@ -39,6 +39,6 @@
"fieldsDescending": {}
},
"memoizedSize": -1,
- "memoizedHashCode": -44608632
+ "memoizedHashCode": -1287011120
}
]
\ No newline at end of file
diff --git a/OpenCV/build/intermediates/cxx/create_cxx_tasks_1055_timing.txt b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1055_timing.txt
new file mode 100644
index 0000000..7a1061f
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1055_timing.txt
@@ -0,0 +1,5 @@
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 25ms
+create_cxx_tasks completed in 27ms
+
diff --git a/OpenCV/build/intermediates/cxx/create_cxx_tasks_1356_timing.txt b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1356_timing.txt
new file mode 100644
index 0000000..4187b32
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1356_timing.txt
@@ -0,0 +1,10 @@
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 32ms
+create_cxx_tasks completed in 32ms
+
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 18ms
+create_cxx_tasks completed in 19ms
+
diff --git a/OpenCV/build/intermediates/cxx/create_cxx_tasks_1970_timing.txt b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1970_timing.txt
new file mode 100644
index 0000000..cdbb923
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/create_cxx_tasks_1970_timing.txt
@@ -0,0 +1,10 @@
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 20ms
+create_cxx_tasks completed in 21ms
+
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 19ms
+create_cxx_tasks completed in 19ms
+
diff --git a/OpenCV/build/intermediates/cxx/create_cxx_tasks_418_timing.txt b/OpenCV/build/intermediates/cxx/create_cxx_tasks_418_timing.txt
index 3663ad8..79bcd7b 100644
--- a/OpenCV/build/intermediates/cxx/create_cxx_tasks_418_timing.txt
+++ b/OpenCV/build/intermediates/cxx/create_cxx_tasks_418_timing.txt
@@ -13,3 +13,8 @@ create_cxx_tasks
create-initial-cxx-model 52ms
create_cxx_tasks completed in 53ms
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 32ms
+create_cxx_tasks completed in 35ms
+
diff --git a/OpenCV/build/intermediates/cxx/create_cxx_tasks_695_timing.txt b/OpenCV/build/intermediates/cxx/create_cxx_tasks_695_timing.txt
new file mode 100644
index 0000000..05b71f4
--- /dev/null
+++ b/OpenCV/build/intermediates/cxx/create_cxx_tasks_695_timing.txt
@@ -0,0 +1,5 @@
+# C/C++ build system timings
+create_cxx_tasks
+ create-initial-cxx-model 47ms
+create_cxx_tasks completed in 48ms
+
diff --git a/app/src/main/java/com/qtagger/Activity/AccountList.java b/app/src/main/java/com/qtagger/Activity/AccountList.java
index a32681f..5170947 100644
--- a/app/src/main/java/com/qtagger/Activity/AccountList.java
+++ b/app/src/main/java/com/qtagger/Activity/AccountList.java
@@ -4,6 +4,8 @@ import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.utils.widget.ImageFilterButton;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
@@ -16,6 +18,7 @@ import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
+import com.qtagger.Data.TestEnum;
import com.qtagger.Database.AccountData;
import com.qtagger.R;
import com.qtagger.util.DataManager;
@@ -105,8 +108,13 @@ public class AccountList extends AppCompatActivity {
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- dataManager.dbHelper.deleteAccount(account);
- linearLayout.removeView(tableLayout);
+ new AlertDialog.Builder(AccountList.this).setTitle("해당 계정을 삭제하시겠습니까?").setPositiveButton("확인", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dataManager.dbHelper.deleteAccount(account);
+ linearLayout.removeView(tableLayout);
+ }
+ }).setNegativeButton("취소", null).show();
}
});
diff --git a/app/src/main/java/com/qtagger/Activity/CameraActivity.java b/app/src/main/java/com/qtagger/Activity/CameraActivity.java
index 65df520..867ade4 100644
--- a/app/src/main/java/com/qtagger/Activity/CameraActivity.java
+++ b/app/src/main/java/com/qtagger/Activity/CameraActivity.java
@@ -33,6 +33,7 @@ import android.widget.Toast;
import com.googlecode.tesseract.android.ResultIterator;
import com.googlecode.tesseract.android.TessBaseAPI;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.qtagger.CustomPalette.CameraSurfaceView;
import com.qtagger.Data.TestEnum;
import com.qtagger.Database.ImageFile;
@@ -80,6 +81,8 @@ public class CameraActivity extends AppCompatActivity {
Bitmap resultBitmap,mOrigin;
String filename, time,pkgData;
String pkgNo = "K042022B000009";
+ private UsbSerialPort usbSerialPort;
+ //private SerialService service;
@@ -291,10 +294,21 @@ public class CameraActivity extends AppCompatActivity {
String imgTitle = null;
if(dbTestType.contains("Surface")) {
- imgTitle = "표면방사선량 값 : 0.04 μSv/hr";
+ if(pkgData.isEmpty()) {
+ imgTitle = "표면방사선량 값 : 0.04 μSv/hr";
+ }
+ else
+ {
+ imgTitle = "표면방사선량 값 : " + pkgData + " μSv/hr";
+ }
}
else if(dbTestType.contains("M1")){
- imgTitle = "1m방사선량 값 : 0.04 μSv/hr";
+ if(pkgData.isEmpty()) {
+ imgTitle = "1m방사선량 값 : 0.04 μSv/hr";
+ }
+ else{
+ imgTitle = "1m방사선량 값 : "+pkgData+" μSv/hr";
+ }
}
else if(dbTestType.compareTo(TestType.Weight.name()) == 0){
if(pkgData.isEmpty())
diff --git a/app/src/main/java/com/qtagger/Database/DBHelper.java b/app/src/main/java/com/qtagger/Database/DBHelper.java
index b517c0d..df20f15 100644
--- a/app/src/main/java/com/qtagger/Database/DBHelper.java
+++ b/app/src/main/java/com/qtagger/Database/DBHelper.java
@@ -150,7 +150,9 @@ public class DBHelper extends SQLiteOpenHelper {
}
public void deleteAccount(AccountData ac){
+ db = getWritableDatabase();
db.execSQL("Delete from Account where id = '"+ac.getId()+"'");
+ db.close();
}
public void insertFile(ImageFile f){
diff --git a/app/src/main/java/com/qtagger/MainActivity.java b/app/src/main/java/com/qtagger/MainActivity.java
index e33b697..ac3b455 100644
--- a/app/src/main/java/com/qtagger/MainActivity.java
+++ b/app/src/main/java/com/qtagger/MainActivity.java
@@ -53,7 +53,6 @@ public class MainActivity extends AppCompatActivity {
CommonHelper.CheckCameraPermission(this);
CommonHelper.FolderClear();
dataManager.dbHelper = new DBHelper(getApplicationContext());
- //dataManager.nuclideDataMap = CommonHelper.getAllNuclideData();
//테스트용도 나중에 삭제
findViewById(R.id.imageView).setOnClickListener(v -> {
diff --git a/app/src/main/java/com/qtagger/test.java b/app/src/main/java/com/qtagger/test.java
index f21307c..5ccb5e5 100644
--- a/app/src/main/java/com/qtagger/test.java
+++ b/app/src/main/java/com/qtagger/test.java
@@ -8,8 +8,12 @@ import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
+import android.app.PendingIntent;
import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -17,26 +21,49 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
import android.widget.Toast;
+import android.widget.ToggleButton;
import com.googlecode.tesseract.android.ResultIterator;
import com.googlecode.tesseract.android.TessBaseAPI;
+import com.hoho.android.usbserial.driver.UsbSerialDriver;
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.qtagger.Activity.SyncActivity;
+import com.qtagger.Data.CustomProber;
import com.qtagger.Data.TestEnum;
import com.qtagger.Database.PKG;
+import com.qtagger.util.Constants;
import com.qtagger.util.DataManager;
import com.qtagger.util.OracleQueryStr;
+import com.qtagger.util.PositionData;
+import com.qtagger.util.SerialListener;
+import com.qtagger.util.SerialService;
+import com.qtagger.util.SerialSocket;
+import com.qtagger.util.TextUtil;
import org.json.JSONObject;
import org.opencv.android.OpenCVLoader;
@@ -68,14 +95,32 @@ import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
-public class test extends AppCompatActivity {
+public class test extends AppCompatActivity implements SerialListener {
static {
System.loadLibrary("native-lib");
}
+ private enum Connected { False, Pending, True }
+ private Connected connected = Connected.False;
+ //private final BroadcastReceiver broadcastReceiver
+ private boolean controlLinesEnabled = false;
+ private boolean hexEnabled = true;
+ public boolean modeCheck = false;
+ public boolean bStart = false;
+ private boolean pendingNewline = false;
+ private int deviceId, portNum, baudRate;
+ private String newline = TextUtil.newline_crlf;
+ private UsbSerialPort usbSerialPort;
+ private SerialService service;
+ private ControlLines controlLines;
+ private PositionData positionData;
+
+
public Context mContext;
+ TextView receiveText;
ListView list1;
ImageView imageView;
private DataManager dataManager;
@@ -92,6 +137,7 @@ public class test extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
+ receiveText = findViewById(R.id.receiveText);
mContext = this;
dataManager = DataManager.getInstance();
list1 = findViewById(R.id.list_test);
@@ -250,6 +296,214 @@ public class test extends AppCompatActivity {
});
}
+
+ private void connect() {
+ connect(null);
+ }
+
+ private void connect(Boolean permissionGranted) {
+ UsbDevice device = null;
+ UsbManager usbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE);
+ for(UsbDevice v : usbManager.getDeviceList().values())
+ if(v.getDeviceId() == deviceId)
+ device = v;
+ if(device == null) {
+ status("connection failed: device not found");
+ return;
+ }
+ UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device);
+ if(driver == null) {
+ driver = CustomProber.getCustomProber().probeDevice(device);
+ }
+ if(driver == null) {
+ status("connection failed: no driver for device");
+ return;
+ }
+ if(driver.getPorts().size() < portNum) {
+ status("connection failed: not enough ports at device");
+ return;
+ }
+ usbSerialPort = driver.getPorts().get(portNum);
+ UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice());
+ if(usbConnection == null && permissionGranted == null && !usbManager.hasPermission(driver.getDevice())) {
+ PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(test.this, 0, new Intent(Constants.INTENT_ACTION_GRANT_USB), 0);
+ usbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
+ return;
+ }
+ if(usbConnection == null) {
+ if (!usbManager.hasPermission(driver.getDevice()))
+ status("connection failed: permission denied");
+ else
+ status("connection failed: open failed");
+ return;
+ }
+
+ connected = Connected.Pending;
+ try {
+ usbSerialPort.open(usbConnection);
+ usbSerialPort.setParameters(baudRate, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
+ SerialSocket socket = new SerialSocket(this.getApplicationContext(), usbConnection, usbSerialPort);
+ service.connect(socket);
+ // usb connect is not asynchronous. connect-success and connect-error are returned immediately from socket.connect
+ // for consistency to bluetooth/bluetooth-LE app use same SerialListener and SerialService classes
+ onSerialConnect();
+ } catch (Exception e) {
+ onSerialConnectError(e);
+ }
+ }
+
+ @Override
+ public void onSerialConnect() {
+ status("connected");
+ connected = Connected.True;
+ if(controlLinesEnabled) {
+ controlLines.start();
+ }
+ }
+
+ @Override
+ public void onSerialConnectError(Exception e) {
+ status("connection failed: " + e.getMessage());
+ disconnect();
+ }
+
+ @Override
+ public void onSerialRead(byte[] data) {
+ receive(data);
+ }
+
+ @Override
+ public void onSerialIoError(Exception e) {
+ status("connection lost: " + e.getMessage());
+ disconnect();
+ }
+
+ @Override
+ public void onPointerCaptureChanged(boolean hasCapture) {
+ super.onPointerCaptureChanged(hasCapture);
+ }
+ private void disconnect() {
+ connected = Connected.False;
+ controlLines.stop();
+ service.disconnect();
+ usbSerialPort = null;
+ }
+
+ void status(String str) {
+ SpannableStringBuilder spn = new SpannableStringBuilder(str+'\n');
+ spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.black)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ //receiveText.append(spn);
+ }
+
+ private void receive(byte[] data) {
+ if(hexEnabled) {
+ if(resultStr.length() > 0) {
+ resultStr += " ";
+ }
+ resultStr += TextUtil.toHexString(data);
+
+ if(modeCheck) {
+ String tempStr = resultStr.replaceAll(" ", "");
+
+ Log.i("jjhtest length", tempStr.length()+"");
+ if(tempStr.length() == 72) {
+ String value = tempStr.substring(42, 50);
+ Long i = Long.parseLong(value, 16);
+ Float f = Float.intBitsToFloat(i.intValue());
+ double df = f;
+ df = df / 10;
+
+ Log.i("jjhtest", String.format("%.5f", df) + " μSv/hr");
+ receiveText.setText(String.format("%.5f", df) + " μSv/hr");
+ positionData.setCurrentValue(String.format("%.5f", df));
+ bStart = true;
+
+ }
+ }
+ } else {
+ String msg = new String(data);
+ if(newline.equals(TextUtil.newline_crlf) && msg.length() > 0) {
+ // don't show CR as ^M if directly before LF
+ msg = msg.replace(TextUtil.newline_crlf, TextUtil.newline_lf);
+ // special handling if CR and LF come in separate fragments
+ if (pendingNewline && msg.charAt(0) == '\n') {
+ Editable edt = receiveText.getEditableText();
+ if (edt != null && edt.length() > 1)
+ edt.replace(edt.length() - 2, edt.length(), "");
+ }
+ pendingNewline = msg.charAt(msg.length() - 1) == '\r';
+ }
+ receiveText.append(TextUtil.toCaretString(msg, newline.length() != 0));
+ }
+ }
+
+ class ControlLines {
+ private static final int refreshInterval = 200; // msec
+
+ private final Handler mainLooper;
+ private final Runnable runnable;
+ //private final LinearLayout frame;
+ private final ToggleButton rtsBtn;
+
+ ControlLines(View view) {
+ mainLooper = new Handler(Looper.getMainLooper());
+ runnable = this::run; // w/o explicit Runnable, a new lambda would be created on each postDelayed, which would not be found again by removeCallbacks
+
+ rtsBtn = view.findViewById(R.id.rtsBtn);
+ rtsBtn.setOnClickListener(this::toggle);
+ //dtrBtn.setOnClickListener(this::toggle);
+ }
+
+ private void toggle(View v) {
+ ToggleButton btn = (ToggleButton) v;
+ if (connected != Connected.True) {
+ btn.setChecked(!btn.isChecked());
+ Toast.makeText(test.this, "not connected", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ String ctrl = "";
+ try {
+ if (btn.equals(rtsBtn)) { ctrl = "RTS"; usbSerialPort.setRTS(btn.isChecked()); }
+ if (btn.equals(rtsBtn)) { ctrl = "DTR"; usbSerialPort.setDTR(btn.isChecked()); }
+ } catch (IOException e) {
+ status("set" + ctrl + " failed: " + e.getMessage());
+ }
+ }
+
+ private void run() {
+ if (connected != Connected.True)
+ return;
+ try {
+ EnumSet controlLines = usbSerialPort.getControlLines();
+ rtsBtn.setChecked(controlLines.contains(UsbSerialPort.ControlLine.RTS));
+ mainLooper.postDelayed(runnable, refreshInterval);
+ //send("AA000E0040FF00000002015DFFAA");
+ } catch (IOException e) {
+ status("getControlLines() failed: " + e.getMessage() + " -> stopped control line refresh");
+ }
+ }
+
+ void start() {
+
+ if (connected != Connected.True)
+ return;
+ try {
+ EnumSet controlLines = usbSerialPort.getSupportedControlLines();
+ if (!controlLines.contains(UsbSerialPort.ControlLine.RTS)) rtsBtn.setVisibility(View.INVISIBLE);
+ run();
+ } catch (IOException e) {
+ Toast.makeText(test.this, "getSupportedControlLines() failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ void stop() {
+ mainLooper.removeCallbacks(runnable);
+ rtsBtn.setChecked(false);
+ }
+ }
+
+
+
private class AsyncTess extends AsyncTask {
@Override
protected String doInBackground(Bitmap... mRelativeParams) {
diff --git a/app/src/main/java/com/qtagger/util/Constants.java b/app/src/main/java/com/qtagger/util/Constants.java
new file mode 100644
index 0000000..a200144
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/Constants.java
@@ -0,0 +1,16 @@
+package com.qtagger.util;
+
+public class Constants {
+
+ // values have to be globally unique
+ public static final String INTENT_ACTION_GRANT_USB = com.qtagger.BuildConfig.APPLICATION_ID + ".GRANT_USB";
+ static final String INTENT_ACTION_DISCONNECT = com.qtagger.BuildConfig.APPLICATION_ID + ".Disconnect";
+ static final String NOTIFICATION_CHANNEL = com.qtagger.BuildConfig.APPLICATION_ID + ".Channel";
+ static final String INTENT_CLASS_MAIN_ACTIVITY = com.qtagger.BuildConfig.APPLICATION_ID + ".MainActivity";
+
+ // values have to be unique within each app
+ static final int NOTIFY_MANAGER_START_FOREGROUND_SERVICE = 1001;
+
+ private Constants() {}
+}
+
diff --git a/app/src/main/java/com/qtagger/util/PositionData.java b/app/src/main/java/com/qtagger/util/PositionData.java
new file mode 100644
index 0000000..42eee56
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/PositionData.java
@@ -0,0 +1,166 @@
+package com.qtagger.util;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+public class PositionData implements Serializable {
+
+ private String title;
+
+ private HashMap resultBitmapPath;
+ private HashMap resultValue;
+ private HashMap resultDatetime;
+
+ private String front;
+ private String back;
+ private String left;
+ private String right;
+
+ private String currentTypeMain;
+ private String currentTypeSub;
+ private String
+ currentBitmapPath;
+ private String currentValue;
+ private String currentDatetime;
+
+ public PositionData(){
+
+ resultBitmapPath = new HashMap();
+ resultValue = new HashMap();
+ resultDatetime = new HashMap();
+
+ title = null;
+ currentTypeMain = null;
+ currentTypeSub = null;
+ currentBitmapPath = null;
+ }
+
+ public PositionData(String title){
+
+ resultBitmapPath = new HashMap();
+ resultValue = new HashMap();
+ resultDatetime = new HashMap();
+
+ this.title = title;
+ currentTypeMain = null;
+ currentTypeSub = null;
+ currentBitmapPath = null;
+ }
+
+ public void init() {
+ resultBitmapPath = new HashMap();
+ resultValue = new HashMap();
+ resultDatetime = new HashMap();
+
+ currentTypeMain = null;
+ currentTypeSub = null;
+ currentBitmapPath = null;
+
+ }
+
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getFront() {
+ return front;
+ }
+
+ public void setFront(String front) {
+ this.front = front;
+ }
+
+ public String getBack() {
+ return back;
+ }
+
+ public void setBack(String back) {
+ this.back = back;
+ }
+
+ public String getLeft() {
+ return left;
+ }
+
+ public void setLeft(String left) {
+ this.left = left;
+ }
+
+ public String getRight() {
+ return right;
+ }
+
+ public void setRight(String right) {
+ this.right = right;
+ }
+
+
+ public String getCurrentTypeMain() {
+ return currentTypeMain;
+ }
+
+ public void setCurrentTypeMain(String currentTypeMain) {
+ this.currentTypeMain = currentTypeMain;
+ }
+
+ public String getCurrentTypeSub() {
+ return currentTypeSub;
+ }
+
+ public void setCurrentTypeSub(String currentTypeSub) {
+ this.currentTypeSub = currentTypeSub;
+ }
+
+ public String getCurrentBitmapPath() {
+ return currentBitmapPath;
+ }
+
+ public void setCurrentBitmapPath(String currentBitmapPath) {
+ this.currentBitmapPath = currentBitmapPath;
+ }
+
+ public String getCurrentValue() {
+ return currentValue;
+ }
+
+ public void setCurrentValue(String currentValue) {
+ this.currentValue = currentValue;
+ }
+
+ public String getCurrentDatetime() {
+ return currentDatetime;
+ }
+
+ public void setCurrentDatetime(String currentDatetime) {
+ this.currentDatetime = currentDatetime;
+ }
+
+ public HashMap getResultValue() {
+ return resultValue;
+ }
+
+ public void setResultValue(HashMap resultValue) {
+ this.resultValue = resultValue;
+ }
+
+ public HashMap getResultBitmapPath() {
+ return resultBitmapPath;
+ }
+
+ public void setResultBitmapPath(HashMap resultBitmapPath) {
+ this.resultBitmapPath = resultBitmapPath;
+ }
+
+ public HashMap getResultDatetime() {
+ return resultDatetime;
+ }
+
+ public void setResultDatetime(HashMap resultDatetime) {
+ this.resultDatetime = resultDatetime;
+ }
+}
diff --git a/app/src/main/java/com/qtagger/util/SerialListener.java b/app/src/main/java/com/qtagger/util/SerialListener.java
new file mode 100644
index 0000000..667467e
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/SerialListener.java
@@ -0,0 +1,8 @@
+package com.qtagger.util;
+
+public interface SerialListener {
+ void onSerialConnect ();
+ void onSerialConnectError (Exception e);
+ void onSerialRead (byte[] data);
+ void onSerialIoError (Exception e);
+}
diff --git a/app/src/main/java/com/qtagger/util/SerialService.java b/app/src/main/java/com/qtagger/util/SerialService.java
new file mode 100644
index 0000000..95ba416
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/SerialService.java
@@ -0,0 +1,251 @@
+package com.qtagger.util;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+
+import com.qtagger.R;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class SerialService extends Service implements SerialListener {
+
+ class SerialBinder extends Binder {
+ SerialService getService() { return SerialService.this; }
+ }
+
+ private enum QueueType {Connect, ConnectError, Read, IoError}
+
+ private class QueueItem {
+ QueueType type;
+ byte[] data;
+ Exception e;
+
+ QueueItem(QueueType type, byte[] data, Exception e) { this.type=type; this.data=data; this.e=e; }
+ }
+
+ private final Handler mainLooper;
+ private final IBinder binder;
+ private final Queue queue1, queue2;
+
+ private SerialSocket socket;
+ private SerialListener listener;
+ private boolean connected;
+
+ /**
+ * Lifecylce
+ */
+ public SerialService() {
+ mainLooper = new Handler(Looper.getMainLooper());
+ binder = new SerialBinder();
+ queue1 = new LinkedList<>();
+ queue2 = new LinkedList<>();
+ }
+
+ @Override
+ public void onDestroy() {
+ cancelNotification();
+ disconnect();
+ super.onDestroy();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ /**
+ * Api
+ */
+ public void connect(SerialSocket socket) throws IOException {
+ socket.connect(this);
+ this.socket = socket;
+ connected = true;
+ }
+
+ public void disconnect() {
+ connected = false; // ignore data,errors while disconnecting
+ cancelNotification();
+ if(socket != null) {
+ socket.disconnect();
+ socket = null;
+ }
+ }
+
+ public void write(byte[] data) throws IOException {
+ if(!connected)
+ throw new IOException("not connected");
+ socket.write(data);
+ }
+
+ public void attach(SerialListener listener) {
+ if(Looper.getMainLooper().getThread() != Thread.currentThread())
+ throw new IllegalArgumentException("not in main thread");
+ cancelNotification();
+ // use synchronized() to prevent new items in queue2
+ // new items will not be added to queue1 because mainLooper.post and attach() run in main thread
+ synchronized (this) {
+ this.listener = listener;
+ }
+ for(QueueItem item : queue1) {
+ switch(item.type) {
+ case Connect: listener.onSerialConnect (); break;
+ case ConnectError: listener.onSerialConnectError (item.e); break;
+ case Read: listener.onSerialRead (item.data); break;
+ case IoError: listener.onSerialIoError (item.e); break;
+ }
+ }
+ for(QueueItem item : queue2) {
+ switch(item.type) {
+ case Connect: listener.onSerialConnect (); break;
+ case ConnectError: listener.onSerialConnectError (item.e); break;
+ case Read: listener.onSerialRead (item.data); break;
+ case IoError: listener.onSerialIoError (item.e); break;
+ }
+ }
+ queue1.clear();
+ queue2.clear();
+ }
+
+ public void detach() {
+ if(connected)
+ createNotification();
+ // items already in event queue (posted before detach() to mainLooper) will end up in queue1
+ // items occurring later, will be moved directly to queue2
+ // detach() and mainLooper.post run in the main thread, so all items are caught
+ listener = null;
+ }
+
+ private void createNotification() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel nc = new NotificationChannel(Constants.NOTIFICATION_CHANNEL, "Background service", NotificationManager.IMPORTANCE_LOW);
+ nc.setShowBadge(false);
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.createNotificationChannel(nc);
+ }
+ Intent disconnectIntent = new Intent()
+ .setAction(Constants.INTENT_ACTION_DISCONNECT);
+ Intent restartIntent = new Intent()
+ .setClassName(this, Constants.INTENT_CLASS_MAIN_ACTIVITY)
+ .setAction(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER);
+ PendingIntent disconnectPendingIntent = PendingIntent.getBroadcast(this, 1, disconnectIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent restartPendingIntent = PendingIntent.getActivity(this, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL)
+ .setSmallIcon(R.drawable.logo)
+ .setColor(getResources().getColor(R.color.white))
+ .setContentTitle(getResources().getString(R.string.app_name))
+ .setContentText(socket != null ? "Connected to "+socket.getName() : "Background Service")
+ .setContentIntent(restartPendingIntent)
+ .setOngoing(true)
+ .addAction(new NotificationCompat.Action(android.R.drawable.ic_menu_close_clear_cancel, "Disconnect", disconnectPendingIntent));
+ // @drawable/ic_notification created with Android Studio -> New -> Image Asset using @color/colorPrimaryDark as background color
+ // Android < API 21 does not support vectorDrawables in notifications, so both drawables used here, are created as .png instead of .xml
+ Notification notification = builder.build();
+ startForeground(Constants.NOTIFY_MANAGER_START_FOREGROUND_SERVICE, notification);
+ }
+
+ private void cancelNotification() {
+ stopForeground(true);
+ }
+
+ /**
+ * SerialListener
+ */
+ public void onSerialConnect() {
+ if(connected) {
+ synchronized (this) {
+ if (listener != null) {
+ mainLooper.post(() -> {
+ if (listener != null) {
+ listener.onSerialConnect();
+ } else {
+ queue1.add(new QueueItem(QueueType.Connect, null, null));
+ }
+ });
+ } else {
+ queue2.add(new QueueItem(QueueType.Connect, null, null));
+ }
+ }
+ }
+ }
+
+ public void onSerialConnectError(Exception e) {
+ if(connected) {
+ synchronized (this) {
+ if (listener != null) {
+ mainLooper.post(() -> {
+ if (listener != null) {
+ listener.onSerialConnectError(e);
+ } else {
+ queue1.add(new QueueItem(QueueType.ConnectError, null, e));
+ cancelNotification();
+ disconnect();
+ }
+ });
+ } else {
+ queue2.add(new QueueItem(QueueType.ConnectError, null, e));
+ cancelNotification();
+ disconnect();
+ }
+ }
+ }
+ }
+
+ public void onSerialRead(byte[] data) {
+ if(connected) {
+ synchronized (this) {
+ if (listener != null) {
+ mainLooper.post(() -> {
+ if (listener != null) {
+ listener.onSerialRead(data);
+ } else {
+ queue1.add(new QueueItem(QueueType.Read, data, null));
+ }
+ });
+ } else {
+ queue2.add(new QueueItem(QueueType.Read, data, null));
+ }
+ }
+ }
+ }
+
+ public void onSerialIoError(Exception e) {
+ if(connected) {
+ synchronized (this) {
+ if (listener != null) {
+ mainLooper.post(() -> {
+ if (listener != null) {
+ listener.onSerialIoError(e);
+ } else {
+ queue1.add(new QueueItem(QueueType.IoError, null, e));
+ cancelNotification();
+ disconnect();
+ }
+ });
+ } else {
+ queue2.add(new QueueItem(QueueType.IoError, null, e));
+ cancelNotification();
+ disconnect();
+ }
+ }
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/com/qtagger/util/SerialSocket.java b/app/src/main/java/com/qtagger/util/SerialSocket.java
new file mode 100644
index 0000000..7da553c
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/SerialSocket.java
@@ -0,0 +1,102 @@
+package com.qtagger.util;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDeviceConnection;
+
+import com.hoho.android.usbserial.driver.UsbSerialPort;
+import com.hoho.android.usbserial.util.SerialInputOutputManager;
+
+import java.io.IOException;
+import java.security.InvalidParameterException;
+import java.util.concurrent.Executors;
+
+public class SerialSocket implements SerialInputOutputManager.Listener {
+
+ private static final int WRITE_WAIT_MILLIS = 2000; // 0 blocked infinitely on unprogrammed arduino
+
+ private final BroadcastReceiver disconnectBroadcastReceiver;
+
+ private Context context;
+ private SerialListener listener;
+ private UsbDeviceConnection connection;
+ private UsbSerialPort serialPort;
+ private SerialInputOutputManager ioManager;
+
+ public SerialSocket(Context context, UsbDeviceConnection connection, UsbSerialPort serialPort) {
+ if(context instanceof Activity)
+ throw new InvalidParameterException("expected non UI context");
+ this.context = context;
+ this.connection = connection;
+ this.serialPort = serialPort;
+ disconnectBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (listener != null)
+ listener.onSerialIoError(new IOException("background disconnect"));
+ disconnect(); // disconnect now, else would be queued until UI re-attached
+ }
+ };
+ }
+
+ String getName() { return serialPort.getDriver().getClass().getSimpleName().replace("SerialDriver",""); }
+
+ void connect(SerialListener listener) throws IOException {
+ this.listener = listener;
+ context.registerReceiver(disconnectBroadcastReceiver, new IntentFilter(Constants.INTENT_ACTION_DISCONNECT));
+ serialPort.setDTR(true); // for arduino, ...
+ serialPort.setRTS(true);
+ ioManager = new SerialInputOutputManager(serialPort, this);
+ Executors.newSingleThreadExecutor().submit(ioManager);
+ }
+
+ void disconnect() {
+ listener = null; // ignore remaining data and errors
+ if (ioManager != null) {
+ ioManager.setListener(null);
+ ioManager.stop();
+ ioManager = null;
+ }
+ if (serialPort != null) {
+ try {
+ serialPort.setDTR(false);
+ serialPort.setRTS(false);
+ } catch (Exception ignored) {
+ }
+ try {
+ serialPort.close();
+ } catch (Exception ignored) {
+ }
+ serialPort = null;
+ }
+ if(connection != null) {
+ connection.close();
+ connection = null;
+ }
+ try {
+ context.unregisterReceiver(disconnectBroadcastReceiver);
+ } catch (Exception ignored) {
+ }
+ }
+
+ void write(byte[] data) throws IOException {
+ if(serialPort == null)
+ throw new IOException("not connected");
+ serialPort.write(data, WRITE_WAIT_MILLIS);
+ }
+
+ @Override
+ public void onNewData(byte[] data) {
+ if(listener != null)
+ listener.onSerialRead(data);
+ }
+
+ @Override
+ public void onRunError(Exception e) {
+ if (listener != null)
+ listener.onSerialIoError(e);
+ }
+}
diff --git a/app/src/main/java/com/qtagger/util/TextUtil.java b/app/src/main/java/com/qtagger/util/TextUtil.java
new file mode 100644
index 0000000..2c3c68d
--- /dev/null
+++ b/app/src/main/java/com/qtagger/util/TextUtil.java
@@ -0,0 +1,157 @@
+package com.qtagger.util;
+
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+import android.text.style.BackgroundColorSpan;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+
+import java.io.ByteArrayOutputStream;
+
+public final class TextUtil {
+
+ @ColorInt
+ static int caretBackground = 0xff666666;
+
+ public final static String newline_crlf = "\r\n";
+ public final static String newline_lf = "\n";
+
+ static byte[] fromHexString(final CharSequence s) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ byte b = 0;
+ int nibble = 0;
+ for(int pos = 0; pos='0' && c<='9') { nibble++; b *= 16; b += c-'0'; }
+ if(c>='A' && c<='F') { nibble++; b *= 16; b += c-'A'+10; }
+ if(c>='a' && c<='f') { nibble++; b *= 16; b += c-'a'+10; }
+ }
+ if(nibble>0)
+ buf.write(b);
+ return buf.toByteArray();
+ }
+
+ public static String toHexString(final byte[] buf) {
+ return toHexString(buf, 0, buf.length);
+ }
+
+ static String toHexString(final byte[] buf, int begin, int end) {
+ StringBuilder sb = new StringBuilder(3*(end-begin));
+ toHexString(sb, buf, begin, end);
+ return sb.toString();
+ }
+
+ static void toHexString(StringBuilder sb, final byte[] buf) {
+ toHexString(sb, buf, 0, buf.length);
+ }
+
+ static void toHexString(StringBuilder sb, final byte[] buf, int begin, int end) {
+ for(int pos=begin; pos0)
+ sb.append(' ');
+ int c;
+ c = (buf[pos]&0xff) / 16;
+ if(c >= 10) c += 'A'-10;
+ else c += '0';
+ sb.append((char)c);
+ c = (buf[pos]&0xff) % 16;
+ if(c >= 10) c += 'A'-10;
+ else c += '0';
+ sb.append((char)c);
+ }
+ }
+
+ /**
+ * use https://en.wikipedia.org/wiki/Caret_notation to avoid invisible control characters
+ */
+ public static CharSequence toCaretString(CharSequence s, boolean keepNewline) {
+ return toCaretString(s, keepNewline, s.length());
+ }
+
+ static CharSequence toCaretString(CharSequence s, boolean keepNewline, int length) {
+ boolean found = false;
+ for (int pos = 0; pos < length; pos++) {
+ if (s.charAt(pos) < 32 && (!keepNewline ||s.charAt(pos)!='\n')) {
+ found = true;
+ break;
+ }
+ }
+ if(!found)
+ return s;
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+ for(int pos=0; pos= '0' && c <= '9') sb.append(c);
+ if(c >= 'A' && c <= 'F') sb.append(c);
+ if(c >= 'a' && c <= 'f') sb.append((char)(c+'A'-'a'));
+ }
+ for(i=2; i
+ android:color="@color/white"/>
diff --git a/app/src/main/res/layout/activity_test.xml b/app/src/main/res/layout/activity_test.xml
index 200e9e1..456b8cd 100644
--- a/app/src/main/res/layout/activity_test.xml
+++ b/app/src/main/res/layout/activity_test.xml
@@ -62,4 +62,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..e578e0c 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,5 @@
#FF018786
#FF000000
#FFFFFFFF
+ #d84315
\ No newline at end of file