시연용 임시 업로드
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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<UsbSerialPort.ControlLine> 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<UsbSerialPort.ControlLine> 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<Bitmap, Integer, String> {
|
||||
@Override
|
||||
protected String doInBackground(Bitmap... mRelativeParams) {
|
||||
|
||||
16
app/src/main/java/com/qtagger/util/Constants.java
Normal file
16
app/src/main/java/com/qtagger/util/Constants.java
Normal file
@@ -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() {}
|
||||
}
|
||||
|
||||
166
app/src/main/java/com/qtagger/util/PositionData.java
Normal file
166
app/src/main/java/com/qtagger/util/PositionData.java
Normal file
@@ -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<String, String> resultBitmapPath;
|
||||
private HashMap<String, String> resultValue;
|
||||
private HashMap<String, String> 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<String, String>();
|
||||
resultValue = new HashMap<String, String>();
|
||||
resultDatetime = new HashMap<String, String>();
|
||||
|
||||
title = null;
|
||||
currentTypeMain = null;
|
||||
currentTypeSub = null;
|
||||
currentBitmapPath = null;
|
||||
}
|
||||
|
||||
public PositionData(String title){
|
||||
|
||||
resultBitmapPath = new HashMap<String, String>();
|
||||
resultValue = new HashMap<String, String>();
|
||||
resultDatetime = new HashMap<String, String>();
|
||||
|
||||
this.title = title;
|
||||
currentTypeMain = null;
|
||||
currentTypeSub = null;
|
||||
currentBitmapPath = null;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
resultBitmapPath = new HashMap<String, String>();
|
||||
resultValue = new HashMap<String, String>();
|
||||
resultDatetime = new HashMap<String, String>();
|
||||
|
||||
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<String, String> getResultValue() {
|
||||
return resultValue;
|
||||
}
|
||||
|
||||
public void setResultValue(HashMap<String, String> resultValue) {
|
||||
this.resultValue = resultValue;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getResultBitmapPath() {
|
||||
return resultBitmapPath;
|
||||
}
|
||||
|
||||
public void setResultBitmapPath(HashMap<String, String> resultBitmapPath) {
|
||||
this.resultBitmapPath = resultBitmapPath;
|
||||
}
|
||||
|
||||
public HashMap<String, String> getResultDatetime() {
|
||||
return resultDatetime;
|
||||
}
|
||||
|
||||
public void setResultDatetime(HashMap<String, String> resultDatetime) {
|
||||
this.resultDatetime = resultDatetime;
|
||||
}
|
||||
}
|
||||
8
app/src/main/java/com/qtagger/util/SerialListener.java
Normal file
8
app/src/main/java/com/qtagger/util/SerialListener.java
Normal file
@@ -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);
|
||||
}
|
||||
251
app/src/main/java/com/qtagger/util/SerialService.java
Normal file
251
app/src/main/java/com/qtagger/util/SerialService.java
Normal file
@@ -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<QueueItem> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
102
app/src/main/java/com/qtagger/util/SerialSocket.java
Normal file
102
app/src/main/java/com/qtagger/util/SerialSocket.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
157
app/src/main/java/com/qtagger/util/TextUtil.java
Normal file
157
app/src/main/java/com/qtagger/util/TextUtil.java
Normal file
@@ -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<s.length(); pos++) {
|
||||
if(nibble==2) {
|
||||
buf.write(b);
|
||||
nibble = 0;
|
||||
b = 0;
|
||||
}
|
||||
int c = s.charAt(pos);
|
||||
if(c>='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; pos<end; pos++) {
|
||||
if(sb.length()>0)
|
||||
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<length; pos++)
|
||||
if (s.charAt(pos) < 32 && (!keepNewline ||s.charAt(pos)!='\n')) {
|
||||
sb.append('^');
|
||||
sb.append((char)(s.charAt(pos) + 64));
|
||||
sb.setSpan(new BackgroundColorSpan(caretBackground), sb.length()-2, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
} else {
|
||||
sb.append(s.charAt(pos));
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
static class HexWatcher implements TextWatcher {
|
||||
|
||||
private final TextView view;
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
private boolean self = false;
|
||||
private boolean enabled = false;
|
||||
|
||||
HexWatcher(TextView view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
void enable(boolean enable) {
|
||||
if(enable) {
|
||||
view.setInputType(InputType.TYPE_CLASS_TEXT + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||
} else {
|
||||
view.setInputType(InputType.TYPE_CLASS_TEXT + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
}
|
||||
enabled = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if(!enabled || self)
|
||||
return;
|
||||
|
||||
sb.delete(0,sb.length());
|
||||
int i;
|
||||
for(i=0; i<s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if(c >= '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<sb.length(); i+=3)
|
||||
sb.insert(i,' ');
|
||||
final String s2 = sb.toString();
|
||||
|
||||
if(!s2.equals(s.toString())) {
|
||||
self = true;
|
||||
s.replace(0, s.length(), s2);
|
||||
self = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
app/src/main/java/com/qtagger/util/UsbItem.java
Normal file
17
app/src/main/java/com/qtagger/util/UsbItem.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.qtagger.util;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver;
|
||||
|
||||
public class UsbItem {
|
||||
UsbDevice device;
|
||||
int port;
|
||||
UsbSerialDriver driver;
|
||||
|
||||
UsbItem(UsbDevice device, int port, UsbSerialDriver driver) {
|
||||
this.device = device;
|
||||
this.port = port;
|
||||
this.driver = driver;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<!-- #f5f5f5" 색으로 채움 -->
|
||||
<solid
|
||||
android:color="#CFD9FB"/>
|
||||
android:color="@color/white"/>
|
||||
<corners
|
||||
android:bottomLeftRadius="12dp"
|
||||
android:bottomRightRadius="12dp"
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="64dp"
|
||||
android:background="#F5F5F5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/view"
|
||||
tools:ignore="SpeakableTextPresentCheck">
|
||||
|
||||
@@ -62,4 +62,26 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/receiveText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="receiveText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/button3"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/rtsBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="13dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:text="Button"
|
||||
app:layout_constraintEnd_toStartOf="@+id/receiveText"
|
||||
app:layout_constraintStart_toEndOf="@+id/button3"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -7,4 +7,5 @@
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="colorPrimary">#d84315</color>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user