package com.eveningoutpost.dexdrip.Services;

/**
 * Created by jcostik1 on 3/15/16.
 */

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelUuid;
import android.preference.PreferenceManager;

import com.eveningoutpost.dexdrip.G5Model.AuthChallengeRxMessage;
import com.eveningoutpost.dexdrip.G5Model.AuthChallengeTxMessage;

import com.eveningoutpost.dexdrip.G5Model.AuthRequestTxMessage;
import com.eveningoutpost.dexdrip.G5Model.AuthStatusRxMessage;
import com.eveningoutpost.dexdrip.G5Model.BluetoothServices;
import com.eveningoutpost.dexdrip.G5Model.BondRequestTxMessage;
import com.eveningoutpost.dexdrip.G5Model.DisconnectTxMessage;
import com.eveningoutpost.dexdrip.G5Model.Extensions;
import com.eveningoutpost.dexdrip.G5Model.SensorRxMessage;
import com.eveningoutpost.dexdrip.G5Model.SensorTxMessage;
import com.eveningoutpost.dexdrip.G5Model.TransmitterStatus;
import com.eveningoutpost.dexdrip.Models.BgReading;
import com.eveningoutpost.dexdrip.Models.Sensor;
import com.eveningoutpost.dexdrip.Models.TransmitterData;
import com.eveningoutpost.dexdrip.Models.UserError.Log;
import com.eveningoutpost.dexdrip.G5Model.Transmitter;

import com.eveningoutpost.dexdrip.UtilityModels.ForegroundServiceStarter;
import com.eveningoutpost.dexdrip.utils.BgToSpeech;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class G5CollectionService extends Service {

    private final static String TAG = G5CollectionService.class.getSimpleName();
    private ForegroundServiceStarter foregroundServiceStarter;

    public Service service;
    private BgToSpeech bgToSpeech;
    private PendingIntent pendingIntent;

    private android.bluetooth.BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothLeScanner mLEScanner;
    private BluetoothGatt mGatt;
    private Transmitter defaultTransmitter;
    public AuthStatusRxMessage authStatus = null;
    public AuthRequestTxMessage authRequest = null;

    private BluetoothGattService cgmService;// = gatt.getService(UUID.fromString(BluetoothServices.CGMService));
    private BluetoothGattCharacteristic authCharacteristic;// = cgmService.getCharacteristic(UUID.fromString(BluetoothServices.Authentication));
    private BluetoothGattCharacteristic controlCharacteristic;//
    private BluetoothGattCharacteristic commCharacteristic;//

    private BluetoothDevice device;
    private Boolean isBondedOrBonding = false;

    private ScanSettings settings;
    private List<ScanFilter> filters;
    private SharedPreferences prefs;

    private boolean isScanning = false;
    private boolean isConnected = false;
    private boolean encountered133 = false;
    private Handler handler;
    public int max133Retries = 5;
    public int max133RetryCounter = 0;
    public boolean isIntialScan = true;
    public Timer scan_interval_timer = new Timer();
    public ArrayList<Long> advertiseTimeMS = new ArrayList<Long>();
    public long timeInMillisecondsOfLastSuccessfulSensorRead = new Date().getTime();
    private int maxScanIntervalInMilliseconds = 5 * 1000; //seconds *1k
    private int maxScanCycles = 24;
    private int scanCycleCount = 0;

    StringBuilder log = new StringBuilder();


    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            initScanCallback();
        }
        advertiseTimeMS.add((long)0);
        service = this;
        foregroundServiceStarter = new ForegroundServiceStarter(getApplicationContext(), service);
        foregroundServiceStarter.start();
//        final IntentFilter bondintent = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//        registerReceiver(mPairReceiver, bondintent);
        prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        listenForChangeInSettings();
        bgToSpeech = BgToSpeech.setupTTS(getApplicationContext()); //keep reference to not being garbage collected
        handler = new Handler(getApplicationContext().getMainLooper());
    }

    public SharedPreferences.OnSharedPreferenceChangeListener prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
            if(key.compareTo("run_service_in_foreground") == 0) {
                Log.d("FOREGROUND", "run_service_in_foreground changed!");
                if (prefs.getBoolean("run_service_in_foreground", false)) {
                    foregroundServiceStarter = new ForegroundServiceStarter(getApplicationContext(), service);
                    foregroundServiceStarter.start();
                    Log.i(TAG, "Moving to foreground");
                } else {
                    service.stopForeground(true);
                    Log.i(TAG, "Removing from foreground");
                }
            }

            if(key.compareTo("run_ble_scan_constantly") == 0 || key.compareTo("always_unbond_G5") == 0
                    || key.compareTo("always_get_new_keys") == 0 || key.compareTo("run_G5_ble_tasks_on_uithread") == 0) {
                Log.i(TAG, "G5 Setting Change");
                cycleScan(0);
            }


        }
    };

    public void listenForChangeInSettings() {
        prefs.registerOnSharedPreferenceChangeListener(prefListener);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.e(TAG, "onG5StartCommand");
        //Log.d(TAG, "SDK: " + Build.VERSION.SDK_INT);
        //stopScan();

        scanCycleCount = 0;
        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();

        if (mGatt != null) {
            mGatt.close();
            mGatt = null;
        }

        if (Sensor.isActive()){
            setupBluetooth();
            Log.d(TAG, "Active Sensor");

        } else {
            stopScan();
            Log.d(TAG, "No Active Sensor");
        }

        return START_STICKY;
    }

    private void getTransmitterDetails() {
        prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        Log.d(TAG, "Transmitter: " + prefs.getString("dex_txid", "ABCDEF"));
        defaultTransmitter = new Transmitter(prefs.getString("dex_txid", "ABCDEF"));
        isBondedOrBonding = false;
        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                if (device.getName() != null) {

                    String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                    String deviceNameLastTwo = Extensions.lastTwoCharactersOfString(device.getName());

                    if (transmitterIdLastTwo.equals(deviceNameLastTwo)) {
                        isBondedOrBonding = true;
                    } else {
                        isIntialScan = true;
                    }

                }
            }
        }
        Log.d(TAG, "Bonded? " + isBondedOrBonding.toString());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopScan();
//        close();
//        setRetryTimer();
//        foregroundServiceStarter.stop();
//        unregisterReceiver(mPairReceiver);
//        BgToSpeech.tearDownTTS();
        Log.i(TAG, "SERVICE STOPPED");
    }

    public void keepAlive() {
        long wakeTime = getNextAdvertiseTime() - 60*1000;
        //Log.e(TAG, "Delay Time: " + minuteDelay);
        Log.e(TAG, "OnStart Wake Time: " + wakeTime);
        AlarmManager alarm = (AlarmManager) getSystemService(ALARM_SERVICE);
        if (pendingIntent != null)
            alarm.cancel(pendingIntent);
        pendingIntent = PendingIntent.getService(this, 0, new Intent(this, this.getClass()), 0);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            alarm.setExact(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
        } else
            alarm.set(AlarmManager.RTC_WAKEUP, wakeTime, pendingIntent);
    }


    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public void setupBluetooth() {
        getTransmitterDetails();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            //First time using the app or bluetooth was turned off?
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            Timer single_timer = new Timer();
                single_timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        mBluetoothAdapter.enable();
                    }
                }, 1000);
            single_timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    setupBluetooth();
                }
            }, 10000);
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();
                filters = new ArrayList<>();
                //Only look for CGM.
                //filters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(BluetoothServices.Advertisement)).build());
                String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                filters.add(new ScanFilter.Builder().setDeviceName("Dexcom" + transmitterIdLastTwo).build());
            }

            cycleScan(0);
        }
    }

    public void stopScan(){
        if (!isScanning) {
            Log.d(TAG, "alreadyStoppedScanning");
            return;
        }
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            } else {

                try {

                    if (enforceMainThread()){
                        Handler iHandler = new Handler(Looper.getMainLooper());
                        iHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                stopLogic();
                            }
                        });
                    } else {
                       stopLogic();;
                    }


                } catch (NullPointerException e) {
                    //Known bug in Samsung API 21 stack
                    System.out.print("Caught the NullPointerException");
                }
            }
        }
    }

    private void stopLogic() {
        try {
            Log.e(TAG, "stopScan");
            mLEScanner.stopScan(mScanCallback);
            isScanning = false;
        } catch (IllegalStateException is) {

        }
    }

    public void cycleScan(int delay) {

        Timer single_timer = new Timer();
        single_timer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (scanConstantly()) {
                    startScan();
                } else {
                    if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                            mBluetoothAdapter.stopLeScan(mLeScanCallback);
                        } else {

                            try {
                                if (enforceMainThread()) {
                                    Handler iHandler = new Handler(Looper.getMainLooper());
                                    iHandler.post(new Runnable() {
                                        @Override
                                        public void run() {
                                            scanLogic();
                                        }
                                    });

                                } else {
                                   scanLogic();
                                }
                            } catch
                                    (NullPointerException e) {
                                //Known bug in Samsung API 21 stack
                                System.out.print("Caught the NullPointerException");
                            }
                        }
                    }
                }
            }
        }, delay);
    }

    private void scanLogic() {
        try {
            mLEScanner.stopScan(mScanCallback);
            isScanning = false;
            if (!isConnected) {
                mLEScanner.startScan(filters, settings, mScanCallback);
                Log.e(TAG, "scan cycle start");
            }
            isScanning = true;
        } catch (IllegalStateException is) {
            setupBluetooth();
        }


        scanCycleCount++;

        //Log.e(TAG, "MSSinceSensorRx: " + getMillisecondsSinceLastSuccesfulSensorRead());
        //if it isn't the initial scan, rescan for maxScanCycles
        if (!isIntialScan && scanCycleCount > maxScanCycles) {
            scan_interval_timer.cancel();
            scan_interval_timer = new Timer();
            scan_interval_timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //Log.e(TAG, "cycling scan to stop until expected advertisement");
                    if (isScanning) {
                        keepAlive();
                    }
                    stopScan();
                }
            }, maxScanIntervalInMilliseconds);
        }
        //last ditch
        else if (!isIntialScan && getMillisecondsSinceLastSuccesfulSensorRead() > 11 * 60 * 1000) {
            Log.e(TAG, "MSSinceSensorRx: " + getMillisecondsSinceLastSuccesfulSensorRead());
            isIntialScan = true;
            cycleBT();
        }
        //startup or re-auth, sit around and wait for tx to advertise
        else {
            scan_interval_timer.cancel();
            scan_interval_timer = new Timer();
            scan_interval_timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //Log.e(TAG, "cycling scan");
                    cycleScan(0);
                }
            }, maxScanIntervalInMilliseconds);
        }

    }

    public void startScan() {
        android.util.Log.e(TAG, "Initial scan?" + isIntialScan);
        if (isScanning) {
            Log.d(TAG, "alreadyScanning");
            scan_interval_timer.cancel();
            return;
        }

        getTransmitterDetails();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            setupBluetooth();
        } else {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                setupLeScanCallback();
                mBluetoothAdapter.startLeScan(new UUID[]{BluetoothServices.Advertisement}, mLeScanCallback);
            } else {
                if (enforceMainThread()){
                    Handler iHandler = new Handler(Looper.getMainLooper());
                    iHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            startLogic();
                        }
                    });
                } else {
                    startLogic();
                }
                Log.e(TAG, "startScan normal");
            }
        }
    }

    private void startLogic() {
        try {
            isScanning = true;
            mLEScanner.startScan(filters, settings, mScanCallback);
        } catch (Exception e) {
            isScanning = false;
            setupBluetooth();
        }
    }
    void cycleBT(){
        encountered133 = false;
        stopScan();
        mBluetoothAdapter.disable();
        Timer single_timer = new Timer();
        single_timer.schedule(new TimerTask() {
            @Override
            public void run() {
                mBluetoothAdapter.enable();
            }
        }, 1000);
        Log.e(TAG, "Cycling BT-gatt");
        keepAlive();
    }

    void forgetDevice() {
        Transmitter defaultTransmitter = new Transmitter(prefs.getString("dex_txid", "ABCDEF"));
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                if (device.getName() != null) {

                    String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                    String deviceNameLastTwo = Extensions.lastTwoCharactersOfString(device.getName());
                    Log.e(TAG, "removeBond");
                    if (transmitterIdLastTwo.equals(deviceNameLastTwo)) {
                        try {
                            Method m = device.getClass().getMethod("removeBond", (Class[]) null);
                            m.invoke(device, (Object[]) null);
                            getTransmitterDetails();
                        } catch (Exception e) { Log.e("SystemStatus", e.getMessage(), e); }
                    }

                }
            }
        }
    }

    // API 18 - 20
    //@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void setupLeScanCallback() {
        if (mLeScanCallback == null) {
            mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                    // Check if the device has a name, the Dexcom transmitter always should. Match it with the transmitter id that was entered.
                    // We get the last 2 characters to connect to the correct transmitter if there is more than 1 active or in the room.
                    // If they match, connect to the device.
                    if (device.getName() != null) {
                        String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                        String deviceNameLastTwo = Extensions.lastTwoCharactersOfString(device.getName());

                        if (transmitterIdLastTwo.toUpperCase().equals(deviceNameLastTwo.toUpperCase())) {
                            connectToDevice(device);
                        }
                    }
                }
            };
        }
    }

    private ScanCallback mScanCallback;

    //@TargetApi(21)
    private void initScanCallback(){
        mScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                android.util.Log.i(TAG, "result: " + result.toString());
                BluetoothDevice btDevice = result.getDevice();
//                // Check if the device has a name, the Dexcom transmitter always should. Match it with the transmitter id that was entered.
//                // We get the last 2 characters to connect to the correct transmitter if there is more than 1 active or in the room.
//                // If they match, connect to the device.
                if (btDevice.getName() != null) {
                    String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                    String deviceNameLastTwo = Extensions.lastTwoCharactersOfString(btDevice.getName());

                    if (transmitterIdLastTwo.equals(deviceNameLastTwo)) {
                        if (advertiseTimeMS.size() > 0)
                            if ((new Date().getTime() - advertiseTimeMS.get(advertiseTimeMS.size()-1)) > 2.5*60*1000)
                                advertiseTimeMS.clear();
                        advertiseTimeMS.add(new Date().getTime());
                        isIntialScan = false;
                        //device = btDevice;
                        device = mBluetoothAdapter.getRemoteDevice(btDevice.getAddress());
                        stopScan();
                        connectToDevice(btDevice);
                    } else {
                        //stopScan(10000);
                    }
                }
            }

            @Override
            public void onScanFailed(int errorCode) {
                Log.e(TAG, "Scan Failed Error Code: " + errorCode);
                if (errorCode == 1) {
                    android.util.Log.e(TAG, "Already Scanning: " + isScanning);
                    //isScanning = true;
                } else if (errorCode == 2){
                    cycleBT();
                }
            }
        };
    }

    public void fullAuthenticate() {

        if (alwaysUnbond()) {
            forgetDevice();
        }

        android.util.Log.i(TAG, "Start Auth Process(fullAuthenticate)");
        authRequest = new AuthRequestTxMessage();
        authCharacteristic.setValue(authRequest.byteSequence);
        android.util.Log.i(TAG, authRequest.byteSequence.toString());
        mGatt.writeCharacteristic(authCharacteristic);
    }

    public void authenticate() {
        mGatt.setCharacteristicNotification(authCharacteristic, true);
        if (!mGatt.readCharacteristic(authCharacteristic)) {
            android.util.Log.e(TAG, "onCharacteristicRead : ReadCharacteristicError");
        }
    }

    public void getSensorData() {
        android.util.Log.i(TAG, "Request Sensor Data");
        mGatt.setCharacteristicNotification(controlCharacteristic, true);
        BluetoothGattDescriptor descriptor = controlCharacteristic.getDescriptor(BluetoothServices.CharacteristicUpdateNotification);
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
        SensorTxMessage sensorTx = new SensorTxMessage();
        controlCharacteristic.setValue(sensorTx.byteSequence);
        mGatt.writeDescriptor(descriptor);
    }

    private BluetoothAdapter.LeScanCallback mLeScanCallback = null;

    private void connectToDevice(BluetoothDevice device) {
        if (mGatt != null) {
            Log.i(TAG, "BGatt isnt null, Closing.");
            mGatt.close();
            mGatt = null;
        }
        android.util.Log.i(TAG, "Request Connect");
        if (enforceMainThread()){
            Handler iHandler = new Handler(Looper.getMainLooper());
            final BluetoothDevice mDevice = device;
            iHandler.post(new Runnable() {
                @Override
                public void run() {

                    android.util.Log.i(TAG, "mGatt Null, connecting...");
                    android.util.Log.i(TAG, "connectToDevice On Main Thread? " + isOnMainThread());
                    mGatt = mDevice.connectGatt(getApplicationContext(), false, gattCallback);

                }
            });
        } else {
            android.util.Log.i(TAG, "mGatt Null, connecting...");
            android.util.Log.i(TAG, "connectToDevice On Main Thread? " + isOnMainThread());
            mGatt = device.connectGatt(getApplicationContext(), false, gattCallback);
        }


    }

    // Sends the disconnect tx message to our bt device.
    private void doDisconnectMessage(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        mGatt.setCharacteristicNotification(controlCharacteristic, false);
        DisconnectTxMessage disconnectTx = new DisconnectTxMessage();
        characteristic.setValue(disconnectTx.byteSequence);
        mGatt.writeCharacteristic(characteristic);
        mGatt.disconnect();
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                                  @Override
                                  public void run() { //Log.e(TAG, "last disconnect status? " + lastGattStatus);
                                      android.util.Log.i(TAG, "onConnectionStateChange On Main Thread? " + isOnMainThread());
                                      switch (newState) {
                                          case BluetoothProfile.STATE_CONNECTED:
                                              Log.e(TAG, "STATE_CONNECTED");
                                              isConnected = true;

                                              if (enforceMainThread()){
                                                  Handler iHandler = new Handler(Looper.getMainLooper());
                                                  iHandler.post(new Runnable() {
                                                      @Override
                                                      public void run() {
                                                          android.util.Log.i(TAG, "discoverServices On Main Thread? " + isOnMainThread());
                                                          if (mGatt != null)
                                                              mGatt.discoverServices();
                                                      }
                                                  });
                                              } else {
                                                  android.util.Log.i(TAG, "discoverServices On Main Thread? " + isOnMainThread());
                                                  if (mGatt != null)
                                                      mGatt.discoverServices();
                                              }


                                              stopScan();
                                              scan_interval_timer.cancel();
                                              keepAlive();
                                              break;
                                          case BluetoothProfile.STATE_DISCONNECTED:
                                              isConnected = false;
                                              if (isScanning) {
                                                  stopScan();
                                              }
                                              Log.e(TAG, "STATE_DISCONNECTED: " + status);
                                              if (mGatt != null)
                                                  mGatt.close();
                                              mGatt = null;
                                              if (status == 0 && !encountered133) {// || status == 59) {
                                                  android.util.Log.i(TAG, "clean disconnect");
                                                  max133RetryCounter = 0;
                                                  if (scanConstantly())
                                                      cycleScan(15000);
                                              } else if (status == 133 || max133RetryCounter >= max133Retries) {
                                                  Log.e(TAG, "max133RetryCounter? " + max133RetryCounter);
                                                  Log.e(TAG, "Encountered 133: " + encountered133);
                                                  max133RetryCounter = 0;
                                                  cycleBT();
                                              } else if (encountered133) {
                                                  Log.e(TAG, "max133RetryCounter? " + max133RetryCounter);
                                                  Log.e(TAG, "Encountered 133: " + encountered133);
                                                  if (scanConstantly())
                                                      startScan();
                                                  else
                                                      cycleScan(0);
                                                  max133RetryCounter++;
                                              } else if (status == 129) {
                                                  forgetDevice();
                                              } else {
                                                  if (scanConstantly())
                                                      startScan();
                                                  else
                                                      cycleScan(0);
                                                  max133RetryCounter = 0;
                                              }

                                              break;
                                          default:
                                              Log.e("gattCallback", "STATE_OTHER");
                                      }
                                  }
                              }
                );
            } else {
                android.util.Log.i(TAG, "onConnectionStateChange On Main Thread? " + isOnMainThread());
                switch (newState) {
                    case BluetoothProfile.STATE_CONNECTED:
                        Log.e(TAG, "STATE_CONNECTED");
                        isConnected = true;

                        if (enforceMainThread()){
                            Handler iHandler = new Handler(Looper.getMainLooper());
                            iHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    android.util.Log.i(TAG, "discoverServices On Main Thread? " + isOnMainThread());
                                    if (mGatt != null)
                                        mGatt.discoverServices();
                                }
                            });
                        } else {
                            android.util.Log.i(TAG, "discoverServices On Main Thread? " + isOnMainThread());
                            if (mGatt != null)
                                mGatt.discoverServices();
                        }


                        stopScan();
                        scan_interval_timer.cancel();
                        keepAlive();
                        break;
                    case BluetoothProfile.STATE_DISCONNECTED:
                        isConnected = false;
                        if (isScanning) {
                            stopScan();
                        }
                        Log.e(TAG, "STATE_DISCONNECTED: " + status);
                        if (mGatt != null)
                            mGatt.close();
                        mGatt = null;
                        if (status == 0 && !encountered133) {// || status == 59) {
                            android.util.Log.i(TAG, "clean disconnect");
                            max133RetryCounter = 0;
                            if (scanConstantly())
                                cycleScan(15000);
                        } else if (status == 133 || max133RetryCounter >= max133Retries) {
                            Log.e(TAG, "max133RetryCounter? " + max133RetryCounter);
                            Log.e(TAG, "Encountered 133: " + encountered133);
                            max133RetryCounter = 0;
                            cycleBT();
                        } else if (encountered133) {
                            Log.e(TAG, "max133RetryCounter? " + max133RetryCounter);
                            Log.e(TAG, "Encountered 133: " + encountered133);
                            if (scanConstantly())
                                startScan();
                            else
                                cycleScan(0);
                            max133RetryCounter++;
                        } else if (status == 129) {
                            forgetDevice();
                        } else {
                            if (scanConstantly())
                                startScan();
                            else
                                cycleScan(0);
                            max133RetryCounter = 0;
                        }

                        break;
                    default:
                        Log.e("gattCallback", "STATE_OTHER");
                }
            }


        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, final int status) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        android.util.Log.i(TAG, "onServicesDiscovered On Main Thread? " + isOnMainThread());
                        Log.e(TAG, "onServicesDiscovered: " + status);
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            cgmService = mGatt.getService(BluetoothServices.CGMService);
                            authCharacteristic = cgmService.getCharacteristic(BluetoothServices.Authentication);
                            controlCharacteristic = cgmService.getCharacteristic(BluetoothServices.Control);
                            commCharacteristic = cgmService.getCharacteristic(BluetoothServices.Communication);
                            mBluetoothAdapter.cancelDiscovery();

                            //TODO : ADD option in settings!
                            if (alwaysAuthenticate() || alwaysUnbond()) {
                                fullAuthenticate();
                            } else {
                                authenticate();
                            }

                        } else {
                            Log.w(TAG, "onServicesDiscovered received: " + status);
                        }

                        if (status == 133) {
                            encountered133 = true;
                        }
                    }
                });
            } else {
                android.util.Log.i(TAG, "onServicesDiscovered On Main Thread? " + isOnMainThread());
                Log.e(TAG, "onServicesDiscovered: " + status);
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    cgmService = mGatt.getService(BluetoothServices.CGMService);
                    authCharacteristic = cgmService.getCharacteristic(BluetoothServices.Authentication);
                    controlCharacteristic = cgmService.getCharacteristic(BluetoothServices.Control);
                    commCharacteristic = cgmService.getCharacteristic(BluetoothServices.Communication);
                    mBluetoothAdapter.cancelDiscovery();

                    //TODO : ADD option in settings!
                    if (alwaysAuthenticate() || alwaysUnbond()) {
                        fullAuthenticate();
                    } else {
                        authenticate();
                    }

                } else {
                    Log.w(TAG, "onServicesDiscovered received: " + status);
                }

                if (status == 133) {
                    encountered133 = true;
                }
            }


        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        android.util.Log.i(TAG, "onDescriptorWrite On Main Thread? " + isOnMainThread());
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            mGatt.writeCharacteristic(descriptor.getCharacteristic());
                            Log.e(TAG, "Writing descriptor: " + status);
                        } else {
                            Log.e(TAG, "Unknown error writing descriptor");
                        }

                        if (status == 133) {
                            encountered133 = true;
                        }
                    }
                });
            } else {
                android.util.Log.i(TAG, "onDescriptorWrite On Main Thread? " + isOnMainThread());
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    mGatt.writeCharacteristic(descriptor.getCharacteristic());
                    Log.e(TAG, "Writing descriptor: " + status);
                } else {
                    Log.e(TAG, "Unknown error writing descriptor");
                }

                if (status == 133) {
                    encountered133 = true;
                }
            }

        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "Success Write " + String.valueOf(status));
                        //Log.e(TAG, "Characteristic " + String.valueOf(characteristic.getUuid()));
                        android.util.Log.i(TAG, "onCharacteristicWrite On Main Thread? " + isOnMainThread());

                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            if (String.valueOf(characteristic.getUuid()).equalsIgnoreCase(String.valueOf(authCharacteristic.getUuid()))) {
                                android.util.Log.i(TAG, "Char Value: " + Arrays.toString(characteristic.getValue()));
                                android.util.Log.i(TAG, "auth? " + String.valueOf(characteristic.getUuid()));
                                if (characteristic.getValue() != null && characteristic.getValue()[0] != 0x6) {
                                    mGatt.readCharacteristic(characteristic);
                                }
                            } else {
                                android.util.Log.i(TAG, "control? " + String.valueOf(characteristic.getUuid()));
                                android.util.Log.i(TAG, "status? " + status);
                            }
                        }

                        if (status == 133) {
                            encountered133 = true;
                        }
                    }
                });

            } else {
                Log.e(TAG, "Success Write " + String.valueOf(status));
                //Log.e(TAG, "Characteristic " + String.valueOf(characteristic.getUuid()));
                android.util.Log.i(TAG, "onCharacteristicWrite On Main Thread? " + isOnMainThread());

                if (status == BluetoothGatt.GATT_SUCCESS) {
                    if (String.valueOf(characteristic.getUuid()).equalsIgnoreCase(String.valueOf(authCharacteristic.getUuid()))) {
                        android.util.Log.i(TAG, "Char Value: " + Arrays.toString(characteristic.getValue()));
                        android.util.Log.i(TAG, "auth? " + String.valueOf(characteristic.getUuid()));
                        if (characteristic.getValue() != null && characteristic.getValue()[0] != 0x6) {
                            mGatt.readCharacteristic(characteristic);
                        }
                    } else {
                        android.util.Log.i(TAG, "control? " + String.valueOf(characteristic.getUuid()));
                        android.util.Log.i(TAG, "status? " + status);
                    }
                }

                if (status == 133) {
                    encountered133 = true;
                }
            }


        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "ReadStatus: " + String.valueOf(status));
                        android.util.Log.i(TAG, "onCharacteristicRead On Main Thread? " + isOnMainThread());

                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            Log.e(TAG, "CharBytes-or " + Arrays.toString(characteristic.getValue()));
                            android.util.Log.i(TAG, "CharHex-or " + Extensions.bytesToHex(characteristic.getValue()));

                            byte[] buffer = characteristic.getValue();
                            byte code = buffer[0];
                            Transmitter defaultTransmitter = new Transmitter(prefs.getString("dex_txid", "ABCDEF"));
                            mBluetoothAdapter = mBluetoothManager.getAdapter();

                            switch (code) {
                                case 5:
                                    authStatus = new AuthStatusRxMessage(characteristic.getValue());
                                    if (authStatus.authenticated == 1 && authStatus.bonded == 1 && isBondedOrBonding == true) {
                                        isBondedOrBonding = true;
                                        getSensorData();
                                    } else if (authStatus.authenticated == 1 && authStatus.bonded == 2) {
                                        android.util.Log.i(TAG, "Let's Bond!");
                                        BondRequestTxMessage bondRequest = new BondRequestTxMessage();
                                        characteristic.setValue(bondRequest.byteSequence);
                                        mGatt.writeCharacteristic(characteristic);
                                        isBondedOrBonding = true;
                                        device.createBond();
                                    } else {
                                        android.util.Log.i(TAG, "Transmitter NOT already authenticated");
                                        authRequest = new AuthRequestTxMessage();
                                        characteristic.setValue(authRequest.byteSequence);
                                        android.util.Log.i(TAG, authRequest.byteSequence.toString());
                                        mGatt.writeCharacteristic(characteristic);
                                    }
                                    break;

                                case 3:
                                    AuthChallengeRxMessage authChallenge = new AuthChallengeRxMessage(characteristic.getValue());
                                    if (authRequest == null) {
                                        authRequest = new AuthRequestTxMessage();
                                    }
                                    android.util.Log.i(TAG, "tokenHash " + Arrays.toString(authChallenge.tokenHash));
                                    android.util.Log.i(TAG, "singleUSe " + Arrays.toString(calculateHash(authRequest.singleUseToken)));

                                    byte[] challengeHash = calculateHash(authChallenge.challenge);
                                    android.util.Log.d(TAG, "challenge hash" + Arrays.toString(challengeHash));
                                    if (challengeHash != null) {
                                        android.util.Log.d(TAG, "Transmitter try auth challenge");
                                        AuthChallengeTxMessage authChallengeTx = new AuthChallengeTxMessage(challengeHash);
                                        android.util.Log.i(TAG, "Auth Challenge: " + Arrays.toString(authChallengeTx.byteSequence));
                                        characteristic.setValue(authChallengeTx.byteSequence);
                                        mGatt.writeCharacteristic(characteristic);
                                    }
                                    break;

                                default:
                                    android.util.Log.i(TAG, code + " - Transmitter NOT already authenticated");
                                    authRequest = new AuthRequestTxMessage();
                                    characteristic.setValue(authRequest.byteSequence);
                                    android.util.Log.i(TAG, authRequest.byteSequence.toString());
                                    mGatt.writeCharacteristic(characteristic);
                                    break;
                            }

                        }

                        if (status == 133) {
                            encountered133 = true;
                        }
                    }
                });
            } else {
                Log.e(TAG, "ReadStatus: " + String.valueOf(status));
                android.util.Log.i(TAG, "onCharacteristicRead On Main Thread? " + isOnMainThread());

                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.e(TAG, "CharBytes-or " + Arrays.toString(characteristic.getValue()));
                    android.util.Log.i(TAG, "CharHex-or " + Extensions.bytesToHex(characteristic.getValue()));

                    byte[] buffer = characteristic.getValue();
                    byte code = buffer[0];
                    Transmitter defaultTransmitter = new Transmitter(prefs.getString("dex_txid", "ABCDEF"));
                    mBluetoothAdapter = mBluetoothManager.getAdapter();

                    switch (code) {
                        case 5:
                            authStatus = new AuthStatusRxMessage(characteristic.getValue());
                            if (authStatus.authenticated == 1 && authStatus.bonded == 1 && isBondedOrBonding == true) {
                                isBondedOrBonding = true;
                                getSensorData();
                            } else if (authStatus.authenticated == 1 && authStatus.bonded == 2) {
                                android.util.Log.i(TAG, "Let's Bond!");
                                BondRequestTxMessage bondRequest = new BondRequestTxMessage();
                                characteristic.setValue(bondRequest.byteSequence);
                                mGatt.writeCharacteristic(characteristic);
                                isBondedOrBonding = true;
                                device.createBond();
                            } else {
                                android.util.Log.i(TAG, "Transmitter NOT already authenticated");
                                authRequest = new AuthRequestTxMessage();
                                characteristic.setValue(authRequest.byteSequence);
                                android.util.Log.i(TAG, authRequest.byteSequence.toString());
                                mGatt.writeCharacteristic(characteristic);
                            }
                            break;

                        case 3:
                            AuthChallengeRxMessage authChallenge = new AuthChallengeRxMessage(characteristic.getValue());
                            if (authRequest == null) {
                                authRequest = new AuthRequestTxMessage();
                            }
                            android.util.Log.i(TAG, "tokenHash " + Arrays.toString(authChallenge.tokenHash));
                            android.util.Log.i(TAG, "singleUSe " + Arrays.toString(calculateHash(authRequest.singleUseToken)));

                            byte[] challengeHash = calculateHash(authChallenge.challenge);
                            android.util.Log.d(TAG, "challenge hash" + Arrays.toString(challengeHash));
                            if (challengeHash != null) {
                                android.util.Log.d(TAG, "Transmitter try auth challenge");
                                AuthChallengeTxMessage authChallengeTx = new AuthChallengeTxMessage(challengeHash);
                                android.util.Log.i(TAG, "Auth Challenge: " + Arrays.toString(authChallengeTx.byteSequence));
                                characteristic.setValue(authChallengeTx.byteSequence);
                                mGatt.writeCharacteristic(characteristic);
                            }
                            break;

                        default:
                            android.util.Log.i(TAG, code + " - Transmitter NOT already authenticated");
                            authRequest = new AuthRequestTxMessage();
                            characteristic.setValue(authRequest.byteSequence);
                            android.util.Log.i(TAG, authRequest.byteSequence.toString());
                            mGatt.writeCharacteristic(characteristic);
                            break;
                    }

                }

                if (status == 133) {
                    encountered133 = true;
                }
            }


        }

        @Override
        // Characteristic notification
        public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
            if (enforceMainThread()) {
                Handler iHandler = new Handler(Looper.getMainLooper());
                iHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "CharBytes-nfy" + Arrays.toString(characteristic.getValue()));
                        android.util.Log.i(TAG, "CharHex-nfy" + Extensions.bytesToHex(characteristic.getValue()));

                        android.util.Log.i(TAG, "onCharacteristicChanged On Main Thread? " + isOnMainThread());

                        byte[] buffer = characteristic.getValue();
                        byte firstByte = buffer[0];
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && gatt != null) {
                            mGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
                        }
                        if (firstByte == 0x2f) {
                            SensorRxMessage sensorRx = new SensorRxMessage(characteristic.getValue());

                            ByteBuffer sensorData = ByteBuffer.allocate(buffer.length);
                            sensorData.order(ByteOrder.LITTLE_ENDIAN);
                            sensorData.put(buffer, 0, buffer.length);

                            int sensor_battery_level = 0;
                            if (sensorRx.status == TransmitterStatus.BRICKED) {
                                //TODO Handle this in UI/Notification
                                sensor_battery_level = 206; //will give message "EMPTY"
                            } else if (sensorRx.status == TransmitterStatus.LOW) {
                                sensor_battery_level = 209; //will give message "LOW"
                            } else {
                                sensor_battery_level = 216; //no message, just system status "OK"
                            }

                            //Log.e(TAG, "filtered: " + sensorRx.filtered);
                            Log.e(TAG, "unfiltered: " + sensorRx.unfiltered);
                            doDisconnectMessage(gatt, characteristic);
                            processNewTransmitterData(sensorRx.unfiltered, sensorRx.filtered, sensor_battery_level, new Date().getTime());
                        }
                    }
                });
            } else {
                Log.e(TAG, "CharBytes-nfy" + Arrays.toString(characteristic.getValue()));
                android.util.Log.i(TAG, "CharHex-nfy" + Extensions.bytesToHex(characteristic.getValue()));

                android.util.Log.i(TAG, "onCharacteristicChanged On Main Thread? " + isOnMainThread());

                byte[] buffer = characteristic.getValue();
                byte firstByte = buffer[0];
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && gatt != null) {
                    mGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
                }
                if (firstByte == 0x2f) {
                    SensorRxMessage sensorRx = new SensorRxMessage(characteristic.getValue());

                    ByteBuffer sensorData = ByteBuffer.allocate(buffer.length);
                    sensorData.order(ByteOrder.LITTLE_ENDIAN);
                    sensorData.put(buffer, 0, buffer.length);

                    int sensor_battery_level = 0;
                    if (sensorRx.status == TransmitterStatus.BRICKED) {
                        //TODO Handle this in UI/Notification
                        sensor_battery_level = 206; //will give message "EMPTY"
                    } else if (sensorRx.status == TransmitterStatus.LOW) {
                        sensor_battery_level = 209; //will give message "LOW"
                    } else {
                        sensor_battery_level = 216; //no message, just system status "OK"
                    }

                    //Log.e(TAG, "filtered: " + sensorRx.filtered);
                    Log.e(TAG, "unfiltered: " + sensorRx.unfiltered);
                    doDisconnectMessage(gatt, characteristic);
                    processNewTransmitterData(sensorRx.unfiltered, sensorRx.filtered, sensor_battery_level, new Date().getTime());
                }
            }


        }
    };

    private void processNewTransmitterData(int raw_data , int filtered_data,int sensor_battery_level, long captureTime) {

        TransmitterData transmitterData = TransmitterData.create(raw_data, sensor_battery_level, captureTime);
        if (transmitterData == null) {
            Log.e(TAG, "TransmitterData.create failed: Duplicate packet");
            return;
        } else {
            timeInMillisecondsOfLastSuccessfulSensorRead = captureTime;
        }
        Sensor sensor = Sensor.currentSensor();
        if (sensor == null) {
            Log.e(TAG, "setSerialDataToTransmitterRawData: No Active Sensor, Data only stored in Transmitter Data");
            return;
        }

        //TODO : LOG if unfiltered or filtered values are zero

        Sensor.updateBatteryLevel(sensor, transmitterData.sensor_battery_level);
        android.util.Log.i("timestamp create", Long.toString(transmitterData.timestamp));

        BgReading.create(transmitterData.raw_data, filtered_data, this, transmitterData.timestamp);
    }

    @SuppressLint("GetInstance")
    private byte[] calculateHash(byte[] data) {
        if (data.length != 8) {
            android.util.Log.e("Decrypt", "Data length should be exactly 8.");
            return null;
        }

        byte[] key = cryptKey();
        if (key == null)
            return null;

        byte[] doubleData;
        ByteBuffer bb = ByteBuffer.allocate(16);
        bb.put(data);
        bb.put(data);

        doubleData = bb.array();

        Cipher aesCipher;
        try {
            aesCipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
            aesCipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] aesBytes = aesCipher.doFinal(doubleData, 0, doubleData.length);

            bb = ByteBuffer.allocate(8);
            bb.put(aesBytes, 0, 8);

            return bb.array();
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
            e.printStackTrace();
        }

        return null;
    }

    private byte[] cryptKey() {
        try {
            return ("00" + defaultTransmitter.transmitterId + "00" + defaultTransmitter.transmitterId).getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean isOnMainThread()
    {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    private long getNextAdvertiseTime() {
        long millisecondsSinceTx = getMillisecondsSinceTxLastSeen();
        long timeToExpected  = (300*1000 - (millisecondsSinceTx%(300*1000)));
        long expectedTxTime = new Date().getTime() + timeToExpected - 3*1000;
        Log.e(TAG, "millisecondsSinceTxAd: " + millisecondsSinceTx );
        //Log.e(TAG, "timeToExpected: " + timeToExpected );
        //Log.e(TAG, "expectedTxTime: " + expectedTxTime );

        return expectedTxTime;
    }

    private long getMillisecondsSinceTxLastSeen() {
        return new Date().getTime() - advertiseTimeMS.get(0);
    }

    private long getMillisecondsSinceLastSuccesfulSensorRead() {
        return new Date().getTime() - timeInMillisecondsOfLastSuccessfulSensorRead;
    }

    public boolean scanConstantly() {
        SharedPreferences sharedPreferences =
                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getBoolean("run_ble_scan_constantly", false);
    }

    public boolean alwaysUnbond() {
        SharedPreferences sharedPreferences =
                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getBoolean("always_unbond_G5", false);
    }

    public boolean alwaysAuthenticate() {
        SharedPreferences sharedPreferences =
                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getBoolean("always_get_new_keys", false);
    }

    public boolean enforceMainThread() {
        SharedPreferences sharedPreferences =
                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        return sharedPreferences.getBoolean("run_G5_ble_tasks_on_uithread", false);
    }

}