public class IconButton extends Button implements HasOnViewAttachListener {

    private HasOnViewAttachListenerDelegate delegate;

    public IconButton(Context context) {
        super(context);
        init();
    }

    public IconButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public IconButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        setTransformationMethod(null);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        super.setText(Iconify.compute(getContext(), text, this), type);
    }


    @Override
    public void setOnViewAttachListener(HasOnViewAttachListener.OnViewAttachListener listener) {
        if (delegate == null) delegate = new HasOnViewAttachListenerDelegate(this);
        delegate.setOnViewAttachListener(listener);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        delegate.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        delegate.onDetachedFromWindow();
    }
} public class IconToggleButton extends ToggleButton implements HasOnViewAttachListener {

    private HasOnViewAttachListenerDelegate delegate;

    public IconToggleButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public IconToggleButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public IconToggleButton(Context context) {
        super(context);
        init();
    }

    private void init() {
        setTransformationMethod(null);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        super.setText(Iconify.compute(getContext(), text, this), BufferType.NORMAL);
    }

    @Override
    public void setOnViewAttachListener(HasOnViewAttachListener.OnViewAttachListener listener) {
        if (delegate == null) delegate = new HasOnViewAttachListenerDelegate(this);
        delegate.setOnViewAttachListener(listener);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        delegate.onAttachedToWindow();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        delegate.onDetachedFromWindow();
    }

} public class GrblOkEvent {

    private String message;

    public GrblOkEvent(String message){
        this.message = message;
    }

    public String getMessage(){ return this.message; }
    public void setMessage(String message){ this.message = message; }

} public class JogCommandEvent {

    private String command;
    private String status;

    public JogCommandEvent(String command){
        this.command = command;
    }

    public String getCommand(){ return this.command; }
    public void setCommand(String command){ this.command = command; }

    public String getStatus(){ return this.status; }
    public void setStatus(String status){ this.status = status; }

} public class UiToastEvent {

    private String message;
    private Boolean longToast;
    private Boolean isWarning;

    public UiToastEvent(String message){
        this.message = message;
        this.longToast = false;
        this.isWarning = false;
    }

    public UiToastEvent(String message, Boolean longToast){
        this.message = message;
        this.longToast = longToast;
        this.isWarning = false;
    }

    public UiToastEvent(String message, Boolean longToast, Boolean isWarning){
        this.message = message;
        this.longToast = longToast;
        this.isWarning = isWarning;
    }

    public String getMessage(){ return this.message; }
    public void setMessage(String message){ this.message = message; }

    public Boolean getLongToast(){ return this.longToast; }
    public void setLongToast(Boolean longToast){ this.longToast = longToast; }

    public Boolean getIsWarning(){ return this.isWarning; }
    public void setIsWarning(boolean isWarning){
        this.isWarning = isWarning;
    }

} public class EnhancedSharedPreferences implements SharedPreferences {

    private static EnhancedSharedPreferences enhancedSharedPreferences = null;
    private final SharedPreferences _sharedPreferences;

    private EnhancedSharedPreferences(SharedPreferences sharedPreferences) {
        _sharedPreferences = sharedPreferences;
    }

    public static EnhancedSharedPreferences getInstance(Context context, String prefsName) {
        if(enhancedSharedPreferences == null){
            enhancedSharedPreferences = new EnhancedSharedPreferences(context.getSharedPreferences(prefsName, Context.MODE_PRIVATE));
        }

        return enhancedSharedPreferences;
    }

    @Override
    public Map<String, ?> getAll() {
        return _sharedPreferences.getAll();
    }

    @Override
    public String getString(String key, String defValue) {
        return _sharedPreferences.getString(key, defValue);
    }

    @Override
    public Set<String> getStringSet(String key, Set<String> defValues) {
        return _sharedPreferences.getStringSet(key, defValues);
    }

    @Override
    public int getInt(String key, int defValue) {
        return _sharedPreferences.getInt(key, defValue);
    }

    @Override
    public long getLong(String key, long defValue) {
        return _sharedPreferences.getLong(key, defValue);
    }

    @Override
    public float getFloat(String key, float defValue) {
        return _sharedPreferences.getFloat(key, defValue);
    }

    @Override
    public boolean getBoolean(String key, boolean defValue) {
        return _sharedPreferences.getBoolean(key, defValue);
    }

    @Override
    public boolean contains(String key) {
        return _sharedPreferences.contains(key);
    }

    @Override
    public Editor edit() {
        return new Editor(_sharedPreferences.edit());
    }

    @Override
    public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
        _sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
    }

    @Override
    public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
        _sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
    }

    public Double getDouble(String key, Double defValue) {
        return Double.longBitsToDouble(_sharedPreferences.getLong(key, Double.doubleToRawLongBits(defValue)));
    }

    public static class Editor implements SharedPreferences.Editor {

        private final SharedPreferences.Editor _editor;

        public Editor(SharedPreferences.Editor editor) {
            _editor = editor;
        }

        private Editor ReturnEditor(SharedPreferences.Editor editor) {
            if(editor instanceof Editor)
                return (Editor)editor;
            return new Editor(editor);
        }

        @Override
        public Editor putString(String key, String value) {
            return ReturnEditor(_editor.putString(key, value));
        }

        @Override
        public Editor putStringSet(String key, Set<String> values) {
            return ReturnEditor(_editor.putStringSet(key, values));
        }

        @Override
        public Editor putInt(String key, int value) {
            return ReturnEditor(_editor.putInt(key, value));
        }

        @Override
        public Editor putLong(String key, long value) {
            return ReturnEditor(_editor.putLong(key, value));
        }

        @Override
        public Editor putFloat(String key, float value) {
            return ReturnEditor(_editor.putFloat(key, value));
        }

        @Override
        public Editor putBoolean(String key, boolean value) {
            return ReturnEditor(_editor.putBoolean(key, value));
        }

        @Override
        public Editor remove(String key) {
            return ReturnEditor(_editor.remove(key));
        }

        @Override
        public Editor clear() {
            return ReturnEditor(_editor.clear());
        }

        @Override
        public boolean commit() {
            return _editor.commit();
        }

        @Override
        public void apply() {
            _editor.apply();
        }

        public Editor putDouble(String key, double value) {
            return new Editor(_editor.putLong(key, Double.doubleToRawLongBits(value)));
        }

    }
} public class RepeatListener implements OnTouchListener {

    private final Handler handler = new Handler();

    private final boolean immediateClick;
    private final int initialInterval;
    private final int normalInterval;
    private boolean haveClicked;

    private View downView;

    private final Runnable handlerRunnable = new Runnable() {
        @Override
        public void run() {
            haveClicked = true;
            if(downView != null) downView.performClick();
            handler.postDelayed(this, normalInterval);
        }
    };

    public RepeatListener(boolean immediateClick, int initialInterval, int normalInterval){
        if (initialInterval < 0 || normalInterval < 0)
            throw new IllegalArgumentException("negative interval");

        this.immediateClick = immediateClick;
        this.initialInterval = initialInterval;
        this.normalInterval = normalInterval;
    }

    public RepeatListener(){
        immediateClick = false;
        initialInterval = android.view.ViewConfiguration.getLongPressTimeout();
        normalInterval = initialInterval;
    }

    public boolean onTouch(View view, MotionEvent motionEvent) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                handler.removeCallbacks(handlerRunnable);
                handler.postDelayed(handlerRunnable, initialInterval);
                downView = view;
                if (immediateClick && downView != null) downView.performClick();
                haveClicked = immediateClick;
                return true;
            case MotionEvent.ACTION_UP:
                // If we haven't clicked yet, click now
                if (!haveClicked && downView != null) downView.performClick();

            case MotionEvent.ACTION_CANCEL:
                handler.removeCallbacks(handlerRunnable);
                downView = null;
                return true;
        }

        return false;
    }
} public class MachineStatusListener extends BaseObservable {

    private static final String TAG = MachineStatusListener.class.getSimpleName();

    private final String emptyString = "";
    private final Double DEFAULT_FEED_RATE          = Constants.DEFAULT_JOGGING_FEED_RATE;

    public static final String STATE_IDLE           = Constants.MACHINE_STATUS_IDLE;
    public static final String STATE_JOG            = Constants.MACHINE_STATUS_JOG;
    public static final String STATE_RUN            = Constants.MACHINE_STATUS_RUN;
    public static final String STATE_HOLD           = Constants.MACHINE_STATUS_HOLD;
    public static final String STATE_ALARM          = Constants.MACHINE_STATUS_ALARM;
    public static final String STATE_CHECK          = Constants.MACHINE_STATUS_CHECK;
    public static final String STATE_SLEEP          = Constants.MACHINE_STATUS_SLEEP;
    public static final String STATE_DOOR           = Constants.MACHINE_STATUS_DOOR;
    public static final String STATE_HOME           = Constants.MACHINE_STATUS_HOME;
    public static final String STATE_NOT_CONNECTED  = Constants.MACHINE_STATUS_NOT_CONNECTED;

    private String state;
    private Integer plannerBuffer = 0;
    private Integer serialRxBuffer = 0;
    private Double feedRate = 0.0;
    private Double spindleSpeed = 0.0;
    private Position lastProbePosition = null;
    private Double toolLengthOffset = 0.0;

    private Position machinePosition = new Position(0.00, 0.00, 0.00);
    private Position workPosition = new Position(0.00, 0.00, 0.00);
    private Position workCoordsOffset = new Position(0.00, 0.00, 0.00);
    private Jogging jogging = new Jogging(0.00, 0.00, DEFAULT_FEED_RATE, false);
    private OverridePercents overridePercents = new OverridePercents(100, 100, 100);
    private EnabledPins enabledPins = new EnabledPins(emptyString);
    private AccessoryStates accessoryStates = new AccessoryStates(emptyString);
    private BuildInfo buildInfo = null;
    private CompileTimeOptions compileTimeOptions = null;
    private ParserState parserState = null;
    private Boolean verboseOutput = false;
	private Boolean laserModeEnabled = false;
	private Boolean ignoreError20 = false;
	private int usbBaudRate = 115200;
	private Boolean singleStepMode = false;

	private String customStartUpString = "";

    private static MachineStatusListener machineStatus = null;
    public static MachineStatusListener getInstance(){
        if(machineStatus == null) machineStatus = new MachineStatusListener();
        return machineStatus;
    }

    public static void resetClass(){
        machineStatus = new MachineStatusListener();
    }

    private MachineStatusListener(){
        this.state = STATE_NOT_CONNECTED;
        this.compileTimeOptions = new CompileTimeOptions(emptyString, Constants.DEFAULT_PLANNER_BUFFER, Constants.DEFAULT_SERIAL_RX_BUFFER);
        this.parserState = new ParserState("G0 G54 G17 G21 G90 G94");
    }

    @Bindable
    public String getCustomStartUpString(){ return this.customStartUpString; }
    public void setCustomStartUpString(String message){
        this.customStartUpString = message;
        notifyPropertyChanged(BR.customStartUpString);
    }

    @Bindable
    public Boolean getIgnoreError20(){ return this.ignoreError20; }
    public void setIgnoreError20(Boolean ignoreError20){
        if(this.ignoreError20 != ignoreError20){
            this.ignoreError20 = ignoreError20;
            notifyPropertyChanged(BR.ignoreError20);
        }
    }

    @Bindable
    public Boolean getSingleStepMode(){ return this.singleStepMode; }
    public void setSingleStepMode(Boolean singleStepMode){
        if(this.singleStepMode != singleStepMode){
            this.singleStepMode = singleStepMode;
            notifyPropertyChanged(BR.singleStepMode);
        }
    }

    @Bindable
    public Boolean getVerboseOutput(){ return this.verboseOutput; }
    public void setVerboseOutput(Boolean verboseOutput){
        if(this.verboseOutput != verboseOutput){
            this.verboseOutput = verboseOutput;
            notifyPropertyChanged(BR.verboseOutput);
        }
    }

    @Bindable
    public Boolean getLaserModeEnabled(){ return this.laserModeEnabled; }
    public void setLaserModeEnabled(Boolean laserModeEnabled){
        this.laserModeEnabled = laserModeEnabled;
        notifyPropertyChanged(BR.laserModeEnabled);
    }

    @Bindable
    public String getState(){ return this.state; }
    public void setState(String state){
        if(!this.state.equals(state)){
            this.state = state;
            notifyPropertyChanged(BR.state);
        }
    }

    @Bindable
    public Integer getPlannerBuffer(){ return this.plannerBuffer; }
    public void setPlannerBuffer(Integer plannerBuffer){
        if(this.plannerBuffer.compareTo(plannerBuffer) !=  0){
            this.plannerBuffer = plannerBuffer;
            notifyPropertyChanged(BR.plannerBuffer);
        }
    }

    @Bindable
    public Integer getSerialRxBuffer(){ return this.serialRxBuffer; }
    public void setSerialRxBuffer(Integer serialRxBuffer){
        if(this.serialRxBuffer.compareTo(serialRxBuffer) != 0){
            this.serialRxBuffer = serialRxBuffer;
            notifyPropertyChanged(BR.serialRxBuffer);
        }
    }

    @Bindable
    public Double getFeedRate(){ return this.feedRate; }
    public void setFeedRate(double feedRate){
        if(this.feedRate.compareTo(feedRate) != 0){
            this.feedRate = feedRate;
            notifyPropertyChanged(BR.feedRate);
        }
    }

    @Bindable
    public Double getSpindleSpeed(){ return this.spindleSpeed; }
    public void setSpindleSpeed(double spindleSpeed){
        if(this.spindleSpeed.compareTo(spindleSpeed) != 0){
            this.spindleSpeed = spindleSpeed;
            notifyPropertyChanged(BR.spindleSpeed);
        }
    }

    @Bindable
    public Position getLastProbePosition(){ return this.lastProbePosition; }
    public void setLastProbePosition(Position lastProbePosition){
        this.lastProbePosition = lastProbePosition;
        notifyPropertyChanged(BR.lastProbePosition);
    }

    @Bindable
    public Double getToolLengthOffset(){ return this.toolLengthOffset; }
    public void setToolLengthOffset(double toolLengthOffset){
        this.toolLengthOffset = toolLengthOffset;
        notifyPropertyChanged(BR.toolLengthOffset);
    }


    @Bindable
    public Position getWorkCoordsOffset(){ return this.workCoordsOffset; }
    public void setWorkCoordsOffset(Position workCoordsOffset){
        if(this.workCoordsOffset.hasChanged(workCoordsOffset)){
            this.workCoordsOffset = workCoordsOffset;
            notifyPropertyChanged(BR.workCoordsOffset);
        }
    }

    @Bindable
    public Position getWorkPosition() { return this.workPosition; }
    public void setWorkPosition(Position workPosition){
        if(this.workPosition.hasChanged(workPosition)){
            this.workPosition = workPosition;
            notifyPropertyChanged(BR.workPosition);
        }
    }

    @Bindable
    public Position getMachinePosition(){ return this.machinePosition; }
    public void setMachinePosition(Position machinePosition){
        if(this.machinePosition.hasChanged(machinePosition)){
            this.machinePosition = machinePosition;
            notifyPropertyChanged(BR.machinePosition);
        }
    }

    @Bindable
    public Jogging getJogging(){ return this.jogging; }
    public void setJogging(double stepXY, double stepZ, double feed, boolean inches){
        Jogging jogging = new Jogging(stepXY, stepZ, feed, inches);
        if(this.jogging.hasChanged(jogging)){
            this.jogging = jogging;
            notifyPropertyChanged(BR.jogging);
        }
    }


    @Bindable
    public OverridePercents getOverridePercents(){ return this.overridePercents; }
    public void setOverridePercents(int feed, int rapid, int spindle){
        OverridePercents v = new OverridePercents(feed, rapid, spindle);
        if(this.overridePercents.hasChanged(v)){
            this.overridePercents = v;
            notifyPropertyChanged(BR.overridePercents);
        }
    }

    @Bindable
    public EnabledPins getEnabledPins(){ return this.enabledPins; }
    public void setEnabledPins(String enabled){
        EnabledPins e = new EnabledPins(enabled);

        if(this.enabledPins.hasChanged(e)){
            this.enabledPins = e;
            notifyPropertyChanged(BR.enabledPins);
        }
    }

    @Bindable
    public AccessoryStates getAccessoryStates(){ return this.accessoryStates; }
    public void setAccessoryStates(String enabled){
        AccessoryStates a = new AccessoryStates(enabled);
        if(this.accessoryStates.hasChanged(a)){
            this.accessoryStates = a;
            notifyPropertyChanged(BR.accessoryStates);
        }
    }

    @Bindable
    public BuildInfo getBuildInfo(){ return this.buildInfo; }
    public void setBuildInfo(BuildInfo buildInfo){
        this.buildInfo = buildInfo;
        notifyPropertyChanged(BR.buildInfo);
    }

    @Bindable
    public CompileTimeOptions getCompileTimeOptions(){ return this.compileTimeOptions; }
    public void setCompileTimeOptions(CompileTimeOptions compileTimeOptions){
        this.compileTimeOptions = compileTimeOptions;
        notifyPropertyChanged(BR.compileTimeOptions);
    }

    @Bindable
    public ParserState getParserState(){ return this.parserState; }
    public void setParserState(String parserState){
        this.parserState = new ParserState(parserState);
        notifyPropertyChanged(BR.parserState);
    }

    public int getUsbBaudRate() {
        return usbBaudRate;
    }

    public void setUsbBaudRate(int usbBaudRate) {
        this.usbBaudRate = usbBaudRate;
    }

    public static class BuildInfo{
        public final Double versionDouble;
        public final Character versionLetter;

        public BuildInfo(double d, char l){
            this.versionDouble = d;
            this.versionLetter = l;
        }
    }

    public static class Jogging{
        public final Double stepXY;
        public final Double stepZ;
        public final Double feed;
        public final Boolean inches;

        public Jogging(double stepXY, double stepZ, double f, boolean i){
            this.stepXY = stepXY; this.stepZ = stepZ; this.feed = f; this.inches = i;
        }

        public boolean hasChanged(Jogging jogging){
            boolean stepChanged = ((jogging.stepXY.compareTo(this.stepXY) != 0) || (jogging.stepZ.compareTo(this.stepZ) != 0));
            boolean feedChanged = (jogging.feed.compareTo(this.feed) != 0);
            boolean inchesChanged = (jogging.inches.compareTo(this.inches) != 0);

            return (stepChanged || feedChanged || inchesChanged);
        }
    }

    public static class OverridePercents{
        public final Integer feed;
        public final Integer rapid;
        public final Integer spindle;

        public OverridePercents(int f, int r, int s){
            this.feed = f; this.rapid = r; this.spindle = s;
        }

        public boolean hasChanged(OverridePercents overridePercents){
            boolean feedChanged = (overridePercents.feed.compareTo(this.feed) != 0);
            boolean rapidChanged = (overridePercents.rapid.compareTo(this.rapid) != 0);
            boolean spindleChanged = (overridePercents.spindle.compareTo(this.spindle) != 0);

            return (feedChanged || rapidChanged || spindleChanged);
        }
    }

    public static class ParserState{
        public final String motionType;
        public final String coordinateSystem;
        public final String planeSelection;
        public final String unitSelection;
        public final String distanceMode;
        public final String feedRateMode;

        public ParserState(String parserState){
            String[] parts = parserState.split("\\s+");

            motionType = parts[0].toUpperCase();
            coordinateSystem = parts[1].toUpperCase();
            planeSelection = parts[2].toUpperCase();
            unitSelection = parts[3].toUpperCase();
            distanceMode = parts[4].toUpperCase();
            feedRateMode = parts[5].toUpperCase();
        }
    }

    public static class EnabledPins{
        final public boolean x;
        final public boolean y;
        final public boolean z;
        final public boolean probe;
        final public boolean door;
        final public boolean hold;
        final public boolean softReset;
        final public boolean cycleStart;

        public EnabledPins(String enabled){
            String enabledUpper = enabled.toUpperCase();
            x = enabledUpper.contains("X");
            y = enabledUpper.contains("Y");
            z = enabledUpper.contains("Z");
            probe = enabledUpper.contains("P");
            door = enabledUpper.contains("D");
            hold = enabledUpper.contains("H");
            softReset = enabledUpper.contains("R");
            cycleStart = enabledUpper.contains("S");
        }

        public boolean hasChanged(EnabledPins enabledPins){
            boolean xChanged = (enabledPins.x != this.x);
            boolean yChanged = (enabledPins.y != this.y);
            boolean zChanged = (enabledPins.z != this.z);
            boolean probeChanged = (enabledPins.probe != this.probe);
            boolean doorChanged = (enabledPins.door != this.door);
            boolean holdChanged = (enabledPins.hold != this.hold);
            boolean softResetChanged = (enabledPins.softReset != this.softReset);
            boolean cycleStartChanged = (enabledPins.cycleStart != this.cycleStart);

            return (xChanged || yChanged || zChanged || probeChanged || doorChanged || holdChanged || softResetChanged || cycleStartChanged);
        }

    }

    public static class AccessoryStates {
        final public boolean spindleCW;
        final public boolean spindleCCW;
        final public boolean flood;
        final public boolean mist;

        public AccessoryStates(String enabled) {
            String enabledUpper = enabled.toUpperCase();
            spindleCW = enabledUpper.contains("S");
            spindleCCW = enabledUpper.contains("C");
            flood = enabledUpper.contains("F");
            mist = enabledUpper.contains("M");
        }

        public boolean hasChanged(AccessoryStates accessoryStates){
            boolean spindleCWChanged = (accessoryStates.spindleCW != this.spindleCW);
            boolean spindleCCWChanged = (accessoryStates.spindleCCW != this.spindleCCW);
            boolean floodChanged = (accessoryStates.flood != this.flood);
            boolean mistChanged = (accessoryStates.mist != mist);

            return (spindleCWChanged || spindleCCWChanged ||floodChanged || mistChanged);
        }
    }

    public static class CompileTimeOptions{

        public final boolean variableSpindle;
        public final boolean lineNumbers;
        public final boolean mistCoolant;
        public final boolean coreXY;
        public final boolean parkingMotion;
        public final boolean homingForceOrigin;
        public final boolean homingSingleAxis;
        public final boolean twoLimitSwitchesOnAxis;
        public final boolean feedRageOverrideInProbeCycles;
        public final boolean restoreAllRom;
        public final boolean restoreRomSettings;
        public final boolean restoreRomParameterData;
        public final boolean writeUserString;
        public final boolean forceSyncOnRomWrite;
        public final boolean forceSyncOnCoordinateChange;
        public final boolean homingInitLock;
        public final int plannerBuffer;
        public final int serialRxBuffer;

        public CompileTimeOptions(String enabled, int plannerBf, int serialRxBf){
            String enabledUpper = enabled.toUpperCase();

            variableSpindle = enabledUpper.contains("V");
            lineNumbers = enabledUpper.contains("N");
            mistCoolant = enabledUpper.contains("M");
            coreXY = enabledUpper.contains("C");
            parkingMotion = enabledUpper.contains("P");
            homingForceOrigin = enabledUpper.contains("Z");
            homingSingleAxis = enabledUpper.contains("H");
            twoLimitSwitchesOnAxis = enabledUpper.contains("T");
            feedRageOverrideInProbeCycles = enabledUpper.contains("A");
            restoreAllRom = enabledUpper.contains("*");
            restoreRomSettings = enabledUpper.contains("$");
            restoreRomParameterData = enabledUpper.contains("#");
            writeUserString = enabledUpper.contains("I");
            forceSyncOnRomWrite = enabledUpper.contains("E");
            forceSyncOnCoordinateChange = enabledUpper.contains("W");
            homingInitLock = enabledUpper.contains("L");
            plannerBuffer = plannerBf;
            serialRxBuffer = serialRxBf;
        }

    }
} public interface Constants {

    double MIN_SUPPORTED_VERSION        = 1.1;

    long GRBL_STATUS_UPDATE_INTERVAL    = 150;
    String USB_BAUD_RATE = "115200";

    // Message types sent from the BluetoothService Handler
    int MESSAGE_STATE_CHANGE            = 1;
    int MESSAGE_READ                    = 2;
    int MESSAGE_WRITE                   = 3;
    int MESSAGE_DEVICE_NAME             = 4;
    int MESSAGE_TOAST                   = 5;
    int REQUEST_READ_PERMISSIONS        = 6;
    int PROBE_TYPE_NORMAL               = 7;
    int PROBE_TYPE_TOOL_OFFSET          = 8;
    int CONNECT_DEVICE_SECURE           = 9;
    int CONNECT_DEVICE_INSECURE         = 10;
    int FILE_PICKER_REQUEST_CODE        = 11;

    int CONSOLE_LOGGER_MAX_SIZE         = 256;
    double DEFAULT_JOGGING_FEED_RATE    = 2400.0;
    int DEFAULT_PLANNER_BUFFER          = 15;
    int DEFAULT_SERIAL_RX_BUFFER        = 128;
    int PROBING_FEED_RATE               = 50;
    int PROBING_PLATE_THICKNESS         = 20;
    int PROBING_DISTANCE                = 15;

    String MACHINE_STATUS_IDLE          = "Idle";
    String MACHINE_STATUS_JOG           = "Jog";
    String MACHINE_STATUS_RUN           = "Run";
    String MACHINE_STATUS_HOLD          = "Hold";
    String MACHINE_STATUS_ALARM         = "Alarm";
    String MACHINE_STATUS_CHECK         = "Check";
    String MACHINE_STATUS_SLEEP         = "Sleep";
    String MACHINE_STATUS_DOOR          = "Door";
    String MACHINE_STATUS_HOME          = "Home";
    String MACHINE_STATUS_NOT_CONNECTED = "Unknown";

    String[] SUPPORTED_FILE_TYPES       = {".tap",".gcode", ".nc", ".ngc", ".fnc", ".txt"};
    String SUPPORTED_FILE_TYPES_STRING  = "^.*\\.(tap|gcode|nc|ngc|cnc|txt|ncc|fnc|dnc|fan|gc|txt|ncg|ncp|fgc)$";

    String JUST_STOP_STREAMING          = "0";
    String STOP_STREAMING_AND_RESET     = "1";
    String DEVICE_NAME                  = "device_name";
    String TOAST                        = "toast";

    String SERIAL_CONNECTION_TYPE_BLUETOOTH         = "bluetooth";
    String SERIAL_CONNECTION_TYPE_USB_OTG           = "usbotg";

    int BLUETOOTH_SERVICE_NOTIFICATION_ID           = 100;
    int FILE_STREAMING_NOTIFICATION_ID              = 101;
    int USB_OTG_SERVICE_NOTIFICATION_ID             = 102;

    String TEXT_CATEGORY_UPDATE                     = "update";
    String TEXT_CATEGORY_LINK                       = "link";
    String TEXT_CATEGORY_PROMOTION                  = "promotion";

} public class GrblUtils {

    // Real time
    public static final byte GRBL_PAUSE_COMMAND = '!';
    public static final byte GRBL_RESUME_COMMAND = '~';
    public static final byte GRBL_STATUS_COMMAND = '?';
    public static final byte GRBL_DOOR_COMMAND = (byte)0x84;
    public static final byte GRBL_JOG_CANCEL_COMMAND = (byte)0x85;
    public static final byte GRBL_RESET_COMMAND = 0x18;
    // Non real time
    public static final String GRBL_KILL_ALARM_LOCK_COMMAND = "$X";
    public static final String GRBL_TOGGLE_CHECK_MODE_COMMAND = "$C";
    public static final String GRBL_VIEW_PARSER_STATE_COMMAND = "$G";
    public static final String GRBL_VIEW_SETTINGS_COMMAND = "$$";
    public static final String GRBL_RUN_HOMING_CYCLE = "$H";
    public static final String GRBL_SLEEP_COMMAND = "$SLP";
    public static final String GRBL_BUILD_INFO_COMMAND = "$I";
    public static final String GRBL_VIEW_GCODE_PARAMETERS_COMMAND = "$#";

    // Gcode Commands
    public static final String GCODE_RESET_COORDINATES_TO_ZERO = "G10 L20 P0 X0Y0Z0";
    public static final String GCODE_RESET_COORDINATE_TO_ZERO = "G10 P0 L20 %c0";
    public static final String GCODE_CANCEL_TOOL_OFFSETS = "G49";

    private static final String GCODE_RETURN_TO_ZERO_LOCATION_XY = "G90 G0 X0 Y0";
    private static final String GCODE_RETURN_TO_ZERO_LOCATION_Z0 = "G90 G0 Z0";
    private static final String GCODE_RETURN_TO_ZERO_LOCATION_Z0_IN_MACHINE_CORDS = "G53 G0 Z0";

    public static Boolean isGrblVersionString(final String response) {
        Boolean version = response.toLowerCase().startsWith("grbl");
        return version && (getVersionDouble(response) != -1);
    }

    public static Boolean isSmoothieBoard(final String response){
        return response.toLowerCase().startsWith("smoothie") || response.toLowerCase().startsWith("carbide");
    }

    private final static String VERSION_DOUBLE_REGEX = "[0-9]*\\.[0-9]*";
    private final static Pattern VERSION_DOUBLE_PATTERN = Pattern.compile(VERSION_DOUBLE_REGEX);
    public static double getVersionDouble(final String response) {
        double retValue = -1;
        Matcher matcher = VERSION_DOUBLE_PATTERN.matcher(response);
        if (matcher.find()) {
            retValue = Double.parseDouble(matcher.group(0));
        }
        return retValue;
    }

    private final static String VERSION_LETTER_REGEX = "(?<=[0-9]\\.[0-9])[a-zA-Z]";
    private final static Pattern VERSION_LETTER_PATTERN = Pattern.compile(VERSION_LETTER_REGEX);
    public static Character getVersionLetter(final String response) {
        Character retValue = null;

        // Search for a version.
        Matcher matcher = VERSION_LETTER_PATTERN.matcher(response);
        if (matcher.find()) {
            retValue = matcher.group(0).charAt(0);
            //retValue = Double.parseDouble(matcher.group(0));
        }

        return retValue;
    }

    private static final Pattern TLO_PATTERN = Pattern.compile("^\\[TLO:(.*)]$");
    public static boolean isGrblToolLengthOffsetMessage(final String response){
        return TLO_PATTERN.matcher(response).find();
    }

    public static Double getToolLengthOffset(final String response){
        Matcher matcher = TLO_PATTERN.matcher(response);
        return matcher.find() ? Double.valueOf(matcher.group(1)) : 0.0;
    }

    private static final String PROBE_REGEX = "^\\[PRB:(.*)]$";
    private static final Pattern PROBE_PATTERN = Pattern.compile(PROBE_REGEX);
    public static boolean isGrblProbeMessage(final String response) {
        return PROBE_PATTERN.matcher(response).find();
    }

    public static String getProbeString(final String response){
        Matcher matcher = PROBE_PATTERN.matcher(response);
        return matcher.find() ? matcher.group(1) : null;
    }

    private static final String STATUS_REGEX = "^<.*>$";
    private static final Pattern STATUS_PATTERN = Pattern.compile(STATUS_REGEX);
    public static boolean isGrblStatusString(final String response) {
        return STATUS_PATTERN.matcher(response).find();
    }

    private static final String FEEDBACK_REGEX = "^\\[MSG:.*]$";
    private static final Pattern FEEDBACK_PATTERN = Pattern.compile(FEEDBACK_REGEX);
    public static boolean isGrblFeedbackMessage(final String response) {
        return FEEDBACK_PATTERN.matcher(response).find();
    }

    private static final String BUILD_OPTIONS_REGEX = "^\\[OPT:(.*)]$";
    private static final Pattern BUILD_OPTIONS_PATTERN = Pattern.compile(BUILD_OPTIONS_REGEX);
    public static boolean isBuildOptionsMessage(final String response) {
        return BUILD_OPTIONS_PATTERN.matcher(response).find();
    }

    public static String getBuildOptionString(final String response){
        Matcher matcher = BUILD_OPTIONS_PATTERN.matcher(response);
        return matcher.find() ? matcher.group(1) : "";
    }

    private static final String PARSER_STATE_REGEX = "^\\[GC:(.*)]$";
    private static final Pattern PARSER_STATE_PATTERN = Pattern.compile(PARSER_STATE_REGEX);
    public static boolean isParserStateMessage(final String response){
        return PARSER_STATE_PATTERN.matcher(response).find();
    }

    public static String getParserStateString(final String response){
        Matcher matcher = PARSER_STATE_PATTERN.matcher(response);
        return matcher.find() ? matcher.group(1) : "";
    }

    private static final String SETTING_REGEX = "^\\$\\d+=.+";
    private static final Pattern SETTING_PATTERN = Pattern.compile(SETTING_REGEX);
    public static boolean   isGrblSettingMessage(final String response) {
        return SETTING_PATTERN.matcher(response).find();
    }

    public static boolean isGrblErrorMessage(final String response){
        return response.toLowerCase().startsWith("error:");
    }

    public static boolean isGrblOkMessage(final String response){
        return response.toLowerCase().startsWith("ok");
    }

    public static boolean isGrblAlarmMessage(final String response){
        return response.toLowerCase().startsWith("alarm:");
    }

    public static final Pattern machinePattern = Pattern.compile("(?<=MPos:)(-?\\d*\\..\\d*),(-?\\d*\\..\\d*),(-?\\d*\\..\\d*)");
    public static final Pattern workPattern = Pattern.compile("(?<=WPos:)(-?\\d*\\..\\d*),(-?\\d*\\..\\d*),(-?\\d*\\..\\d*)");
    public static final Pattern wcoPattern = Pattern.compile("(?<=WCO:)(-?\\d*\\..\\d*),(-?\\d*\\..\\d*),(-?\\d*\\..\\d*)");
    public static Position getPositionFromStatusString(final String status, final Pattern pattern) {
        Matcher matcher = pattern.matcher(status);
        if (matcher.find()) {
            return new Position(Double.parseDouble(matcher.group(1)), Double.parseDouble(matcher.group(2)), Double.parseDouble(matcher.group(3)));
        }
        return null;
    }

    public static ArrayList<String> getReturnToHomeCommands(Position currentPosition){
        ArrayList<String> commands = new ArrayList<>();
        //if(currentPosition.getCordZ() != 0) commands.add(GrblUtils.GCODE_RETURN_TO_ZERO_LOCATION_Z0_IN_MACHINE_CORDS);
        commands.add(GrblUtils.GCODE_RETURN_TO_ZERO_LOCATION_Z0_IN_MACHINE_CORDS);
        commands.add(GrblUtils.GCODE_RETURN_TO_ZERO_LOCATION_XY);
        commands.add(GrblUtils.GCODE_RETURN_TO_ZERO_LOCATION_Z0);

        return commands;
    }

    public static CompileTimeOptions getCompileTimeOptionsFromString(String buildOptions){
        int plannerBuffer = 15; int serialRxBuffer = 128;

        if (buildOptions.indexOf(",") > 0) {
            String[] parts = buildOptions.split(",");
            if (parts.length >= 3) {
                int tmpPBuffer = Integer.parseInt(parts[1]);
                if (tmpPBuffer > 0) plannerBuffer = tmpPBuffer;

                int tmpRxBuffer = Integer.parseInt(parts[2]);
                if (tmpRxBuffer > 0) serialRxBuffer = tmpRxBuffer;
            }
        }

        return new CompileTimeOptions(buildOptions, plannerBuffer, serialRxBuffer);
    }

    private final static String EEPROM_COMMAND_PATTERN = "G10|G28|G30|\\$x=|\\$I|\\$N|\\$RST=|G5[456789]|\\$\\$|\\$#";
    private final static Pattern EEPROM_COMMAND = Pattern.compile(EEPROM_COMMAND_PATTERN, Pattern.CASE_INSENSITIVE);
    public static boolean hasRomAccess(String command){
        return EEPROM_COMMAND.matcher(command).find();
    }

    public static String implode(String glue, String[] array) {
        if(array == null || array.length == 0) return "";

        StringBuilder sb = new StringBuilder();
        sb.append(array[0]);
        for(int i = 1; i < array.length; i++) sb.append(glue).append(array[i]);

        return sb.toString();
    }


    static public Byte getOverrideForEnum(final Overrides command) {
        switch (command) {
            //CMD_DEBUG_REPORT, // 0x85 // Only when DEBUG enabled, sends debug report in '{}' braces.
            case CMD_FEED_OVR_RESET:
                return (byte)0x90; // Restores feed override value to 100%.
            case CMD_FEED_OVR_COARSE_PLUS:
                return (byte)0x91;
            case CMD_FEED_OVR_COARSE_MINUS:
                return (byte)0x92;
            case CMD_FEED_OVR_FINE_PLUS :
                return (byte)0x93;
            case CMD_FEED_OVR_FINE_MINUS :
                return (byte)0x94;
            case CMD_RAPID_OVR_RESET:
                return (byte)0x95;
            case CMD_RAPID_OVR_MEDIUM:
                return (byte)0x96;
            case CMD_RAPID_OVR_LOW:
                return (byte)0x97;
            case CMD_SPINDLE_OVR_RESET:
                return (byte)0x99; // Restores spindle override value to 100%.
            case CMD_SPINDLE_OVR_COARSE_PLUS:
                return (byte)0x9A;
            case CMD_SPINDLE_OVR_COARSE_MINUS:
                return (byte)0x9B;
            case CMD_SPINDLE_OVR_FINE_PLUS:
                return (byte)0x9C;
            case CMD_SPINDLE_OVR_FINE_MINUS:
                return (byte)0x9D;
            case CMD_TOGGLE_SPINDLE:
                return (byte)0x9E;
            case CMD_TOGGLE_FLOOD_COOLANT:
                return (byte)0xA0;
            case CMD_TOGGLE_MIST_COOLANT:
                return (byte)0xA1;
        }
        return null;
    }


}
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import androidx.databinding.DataBindingUtil;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RelativeLayout;
import android.widget.TableRow;
import com.joanzapata.iconify.widget.IconButton;
import com.joanzapata.iconify.widget.IconToggleButton;
import com.warkiz.widget.IndicatorSeekBar;
import com.warkiz.widget.OnSeekChangeListener;
import com.warkiz.widget.SeekParams;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.LinkedList;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import in.co.gorest.grblcontroller.R;
import in.co.gorest.grblcontroller.databinding.FragmentJoggingTabBinding;
import in.co.gorest.grblcontroller.events.GrblOkEvent;
import in.co.gorest.grblcontroller.events.JogCommandEvent;
import in.co.gorest.grblcontroller.events.UiToastEvent;
import in.co.gorest.grblcontroller.helpers.EnhancedSharedPreferences;
import in.co.gorest.grblcontroller.helpers.RepeatListener;
import in.co.gorest.grblcontroller.listeners.MachineStatusListener;
import in.co.gorest.grblcontroller.model.Constants;
import in.co.gorest.grblcontroller.util.GrblUtils;
/*
 *  /**
 *  * Copyright (C) 2017  Grbl Controller Contributors
 *  *
 *  * This program is free software; you can redistribute it and/or modify
 *  * it under the terms of the GNU General Public License as published by
 *  * the Free Software Foundation; either version 2 of the License, or
 *  * (at your option) any later version.
 *  *
 *  * This program is distributed in the hope that it will be useful,
 *  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  * GNU General Public License for more details.
 *  *
 *  * You should have received a copy of the GNU General Public License along
 *  * with this program; if not, write to the Free Software Foundation, Inc.,
 *  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *  * <http://www.gnu.org/licenses/>
 *
 */

package in.co.gorest.grblcontroller.ui;






public class JoggingTabFragment extends BaseFragment implements View.OnClickListener, View.OnLongClickListener{

    private static final String TAG = JoggingTabFragment.class.getSimpleName();
    private MachineStatusListener machineStatus;
    private EnhancedSharedPreferences sharedPref;
    private BlockingQueue<Integer> completedCommands;
    private CustomCommandsAsyncTask customCommandsAsyncTask;

    public JoggingTabFragment() {}

    public static JoggingTabFragment newInstance() {
        return new JoggingTabFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        machineStatus = MachineStatusListener.getInstance();
        sharedPref = EnhancedSharedPreferences.getInstance(Objects.requireNonNull(getActivity()).getApplicationContext(), getString(R.string.shared_preference_key));
        EventBus.getDefault().register(this);
    }

    @Override
    public void onResume(){
        super.onResume();
        SetCustomButtons(Objects.requireNonNull(getView()));

        String joggingPadRotateAngle = sharedPref.getString(getString(R.string.preference_xy_jog_pad_rotation), "0");
        String[] joggingPadTags = rotateJogPad(Integer.valueOf(joggingPadRotateAngle));
        int jogPadIndex = 0;
        for(int resourceId : new Integer[]{R.id.jog_xy_top_left, R.id.jog_y_positive, R.id.jog_xy_top_right, R.id.jog_x_negative, R.id.jog_x_positive, R.id.jog_xy_bottom_left, R.id.jog_y_negative, R.id.jog_xy_bottom_right}){
            final IconButton jogButton = getView().findViewById(resourceId);